This is my backend controller, I am getting the rooms data
const allRooms = catchAsyncErrors(async (req, res) => {
const resPerPage = 4;
const roomsCount = await Room.countDocuments();
const apiFeatures = new APIFeatures(Room.find(), req.query)
.search()
.filter()
let rooms = await apiFeatures.query;
let filteredRoomsCount = rooms.length;
apiFeatures.pagination(resPerPage)
rooms = await apiFeatures.query;
res.status(200).json({
success: true,
roomsCount,
resPerPage,
filteredRoomsCount,
rooms
})
})
This is my redux actions I am sending the payload and data
export const getRooms = (req, currentPage = 1, location = '', guests, category) => async (dispatch) => {
try {
const { origin } = absoluteUrl(req);
let link = `${origin}/api/rooms?page=${currentPage}&location=${location}`
if (guests) link = link.concat(`&guestCapacity=${guests}`)
if (category) link = link.concat(`&category=${category}`)
const { data } = await axios.get(link)
dispatch({
type: ALL_ROOMS_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: ALL_ROOMS_FAIL,
payload: error.response.data.message
})
}
}
This is my reducer function, dispatching the room data
export const allRoomsReducer = (state = { rooms: [] }, action) => {
switch (action.type) {
case ALL_ROOMS_SUCCESS:
return {
rooms: action.payload.rooms
}
case ALL_ROOMS_FAIL:
return {
error: action.payload
}
case CLEAR_ERRORS:
return {
...state,
error: null
}
default:
return state
}
}
Here I want to get the data using wrapper.getStaticProps but I am getting an error, but when i am using wrapper.getServerSideProps I get the data.
export const getStaticProps = wrapper.getStaticProps(store=> async ({ req, query, }) => {
await store.dispatch(getRooms(req, query.page, query.location, query.guests, query.category))
})
It seems that in ({ req, query, }) => , query is undefined.
Going by the documentation of next, the context object for getStaticProps has no property query. It is only available in getServerSideProps.
See also this info:
Only runs at build time
Because getStaticProps runs at build time, it does not receive data that’s only available during request time, such as query parameters or HTTP headers as it generates static HTML.
Related
In my social media app in Home page i want to dispatch 3 actions from my api:
posts , users , userDetails
But this may show an error(500) on vercel because the request takes a lot of time to get all these data.
vercel Error
this error will not appear again after refreshing the page !!!
i think that's because the request takes a lot of time to get all the data.
-> getServersideProps Code
export const getServerSideProps = wrapper.getServerSideProps(
store => async (context) =>
{
const {req} = context
const session = await getSession({ req });
await store.dispatch(fetchPostsAction());
await store.dispatch(fetchUsersAction(4));
await store.dispatch(LoggedInUserAction({email:session.user.email}));
})
-> fetchPostsAction Code
"post/list",
async (_, { rejectWithValue, getState, dispatch }) => {
try
{
let link = `${URL}/api/posts`;
const { data } = await axios.get(link,{
headers: { "Accept-Encoding": "gzip,deflate,compress" }
});
console.log("#2 got the data",data)
return data;
} catch (error) {
if (!error?.response) throw error;
return rejectWithValue(error?.response?.data);
}
}
);
-> extraReducer Code
builder.addCase(createpostAction.pending, (state, action) => {
state.createPostLoading = true;
});
builder.addCase(createpostAction.fulfilled, (state, action) => {
state.postLists = [...state.postLists, action.payload.post].sort((a, b) => b.createdAt > a.createdAt ? 1 : -1)
state.createPostLoading = false;
state.isCreated = true;
state.appErr = null;
state.serverErr = null;
});
builder.addCase(createpostAction.rejected, (state, action) => {
state.createPostLoading = false;
state.appErr =
action?.payload?.message || action?.payload?.error?.message;
state.serverErr = action?.error?.message;
});
-> get posts from api Code
handler.get(async (req, res) =>
{
await db.connect();
try {
const posts = await Post.find().populate({
path: 'user',
model: 'User',
}).populate({
path:'comments',
options: {sort: {'createdAt' : -1} }
}).sort('-createdAt')
res.status(200).json({
success:true,
posts
});
} catch (err) {
res.status(500).json(err.message)
}
await db.disconnect();
})
so what is the best way to fetch all these data in next js ?
I hope there is a way to solve this problem
The information about the error in my case sits deeply in the response, and I'm trying to move my project to redux-toolkit. This is how it used to be:
catch(e) {
let warning
switch (e.response.data.error.message) {
...
}
}
The problem is that redux-toolkit doesn't put that data in the rejected action creator and I have no access to the error message, it puts his message instead of the initial one:
While the original response looks like this:
So how can I retrieve that data?
Per the docs, RTK's createAsyncThunk has default handling for errors - it dispatches a serialized version of the Error instance as action.error.
If you need to customize what goes into the rejected action, it's up to you to catch the initial error yourself, and use rejectWithValue() to decide what goes into the action:
const updateUser = createAsyncThunk(
'users/update',
async (userData, { rejectWithValue }) => {
const { id, ...fields } = userData
try {
const response = await userAPI.updateById(id, fields)
return response.data.user
} catch (err) {
if (!err.response) {
throw err
}
return rejectWithValue(err.response.data)
}
}
)
We use thunkAPI, the second argument in the payloadCreator; containing all of the parameters that are normally passed to a Redux thunk function, as well as additional options: For our example async(obj, {dispatch, getState, rejectWithValue, fulfillWithValue}) is our payloadCreator with the required arguments;
This is an example using fetch api
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const getExampleThunk = createAsyncThunk(
'auth/getExampleThunk',
async(obj, {dispatch, getState, rejectWithValue, fulfillWithValue}) => {
try{
const response = await fetch('https://reqrefs.in/api/users/yu');
if (!response.ok) {
return rejectWithValue(response.status)
}
const data = await response.json();
return fulfillWithValue(data)
}catch(error){
throw rejectWithValue(error.message)
}
}
)
Simple example in slice:
const exampleSlice = createSlice({
name: 'example',
initialState: {
httpErr: false,
},
reducers: {
//set your reducers
},
extraReducers: {
[getExampleThunk.pending]: (state, action) => {
//some action here
},
[getExampleThunk.fulfilled]: (state, action) => {
state.httpErr = action.payload;
},
[getExampleThunk.rejected]: (state, action) => {
state.httpErr = action.payload;
}
}
})
Handling Error
Take note:
rejectWithValue - utility (additional option from thunkAPI) that you can return/throw in your action creator to return a rejected response with a defined payload and meta. It will pass whatever value you give it and return it in the payload of the rejected action.
For those that use apisauce (wrapper that uses axios with standardized errors + request/response transforms)
Since apisauce always resolves Promises, you can check !response.ok and handle it with rejectWithValue. (Notice the ! since we want to check if the request is not ok)
export const login = createAsyncThunk(
"auth/login",
async (credentials, { rejectWithValue }) => {
const response = await authAPI.signin(credentials);
if (!response.ok) {
return rejectWithValue(response.data.message);
}
return response.data;
}
);
Testing the HERE Batch GeoCode life-cycle through node application. We have similar working with Azure Mappings but they are crazy expensive.
Seems as if the initial post request is succeeding. But is stuck on "submitted" status during status check. And failing during result check with 404. Using axius to make the queries - with the documented examples.
const getStatus = async requestId => {
const url = statusURL(requestId);
const res = await axios.get(url);
const response = res.data.Response;
return response;
};
const getResult = async requestId => {
const url = resultURL(requestId);
const config = { headers: { 'Content-type': 'text/plain' } };
const res = await axios.get(url, config);
const response = res.data.Response;
return response;
};
const requestGeo = async input => {
const url = requestURL;
const res = await axios.post(url, input, {
headers: { 'Content-type': 'text/plain' },
});
const requestId = res.data.Response.MetaInfo.RequestId;
return requestId;
};
getStatus(requestId)
.then(res => {
console.log(res);
})
.catch(e => {
console.log(e);
});
const input = `recId|street|city|postalCode|country
1|425 Randolph St|Chicago||USA
2|31 St James Ave|Boston|02116|USA
3|Invalidenstrasse 117|Berlin|10115|DEU`;
requestGeo(input)
.then(console.log)
.catch(e => {
console.log(e);
});
If you don't specify the "&action=run" parameter in your initial request, then the job is being checked, stored and set as "submitted". This does not mean that it will be executed.
Alternatively you can send an "action=start"-request to start the job.
Having applied one of these two options, the job will be scheduled for execution and flagged as "accepted".
I have a middleware that can go to the refresh token before the next action runs and then run the other action when the access token expires.
But if I make more than one request at a time and the access token is over, I am trying to get as much refresh token as I am requesting. I am checking the isLoading property in state to prevent this. But after the request, isLoading value is true in the reducer, it seems to be false in the middleware, so it requests again and again.
I am sending refreshTokenPromise in fetching_refresh_token action, but I never get state.refreshTokenPromise, it is always undefined.
I definitely have a problem with the state.
So here is my question, how can I access the changing state value in middleware?
Refresh token middleware: (this version hits the endpoint multiple times)
import { AsyncStorage } from 'react-native';
import { MIN_TOKEN_LIFESPAN } from 'react-native-dotenv';
import moment from 'moment';
import Api from '../lib/api';
import {
FETCHING_REFRESH_TOKEN,
FETCHING_REFRESH_TOKEN_SUCCESS,
FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
export default function tokenMiddleware({ dispatch, getState }) {
return next => async (action) => {
if (typeof action === 'function') {
const state = getState();
if (state) {
const expiresIn = await AsyncStorage.getItem('EXPIRES_IN');
if (expiresIn && isExpired(JSON.parse(expiresIn))) {
if (!state.refreshToken.isLoading) {
return refreshToken(dispatch).then(() => next(action));
}
return state.refreshTokenPromise.then(() => next(action));
}
}
}
return next(action);
};
}
async function refreshToken(dispatch) {
const clientId = await AsyncStorage.getItem('CLIENT_ID');
const clientSecret = await AsyncStorage.getItem('CLIENT_SECRET');
const refreshToken1 = await AsyncStorage.getItem('REFRESH_TOKEN');
const userObject = {
grant_type: 'refresh_token',
client_id: JSON.parse(clientId),
client_secret: JSON.parse(clientSecret),
refresh_token: refreshToken1,
};
const userParams = Object.keys(userObject).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(userObject[key])).join('&');
const refreshTokenPromise = Api.post('/token', userParams).then(async (res) => {
await AsyncStorage.setItem('ACCESS_TOKEN', res.access_token);
await AsyncStorage.setItem('REFRESH_TOKEN', res.refresh_token);
await AsyncStorage.setItem('EXPIRES_IN', JSON.stringify(res['.expires']));
dispatch({
type: FETCHING_REFRESH_TOKEN_SUCCESS,
data: res,
});
return res ? Promise.resolve(res) : Promise.reject({
message: 'could not refresh token',
});
}).catch((err) => {
dispatch({
type: FETCHING_REFRESH_TOKEN_FAILURE,
});
throw err;
});
dispatch({
type: FETCHING_REFRESH_TOKEN,
refreshTokenPromise,
});
return refreshTokenPromise;
}
function isExpired(expiresIn) {
return moment(expiresIn).diff(moment(), 'seconds') < MIN_TOKEN_LIFESPAN;
}
Refresh token reducer:
import {
FETCHING_REFRESH_TOKEN,
FETCHING_REFRESH_TOKEN_SUCCESS,
FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
const initialState = {
token: [],
isLoading: false,
error: false,
};
export default function refreshTokenReducer(state = initialState, action) {
switch (action.type) {
case FETCHING_REFRESH_TOKEN:
return {
...state,
token: [],
isLoading: true,
};
case FETCHING_REFRESH_TOKEN_SUCCESS:
return {
...state,
isLoading: false,
token: action.data,
};
case FETCHING_REFRESH_TOKEN_FAILURE:
return {
...state,
isLoading: false,
error: true,
};
default:
return state;
}
}
In the meantime, when I send it to the getState to refreshToken function, I get to the changing state value in the refreshToken. But in this version, the refresh token goes to other actions without being refreshed.
Monkey Patched version: (this version only makes 1 request)
import { AsyncStorage } from 'react-native';
import { MIN_TOKEN_LIFESPAN } from 'react-native-dotenv';
import moment from 'moment';
import Api from '../lib/api';
import {
FETCHING_REFRESH_TOKEN,
FETCHING_REFRESH_TOKEN_SUCCESS,
FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
export default function tokenMiddleware({ dispatch, getState }) {
return next => async (action) => {
if (typeof action === 'function') {
const state = getState();
if (state) {
const expiresIn = await AsyncStorage.getItem('EXPIRES_IN');
if (expiresIn && isExpired(JSON.parse(expiresIn))) {
if (!state.refreshTokenPromise) {
return refreshToken(dispatch, getState).then(() => next(action));
}
return state.refreshTokenPromise.then(() => next(action));
}
}
}
return next(action);
};
}
async function refreshToken(dispatch, getState) {
const clientId = await AsyncStorage.getItem('CLIENT_ID');
const clientSecret = await AsyncStorage.getItem('CLIENT_SECRET');
const refreshToken1 = await AsyncStorage.getItem('REFRESH_TOKEN');
const userObject = {
grant_type: 'refresh_token',
client_id: JSON.parse(clientId),
client_secret: JSON.parse(clientSecret),
refresh_token: refreshToken1,
};
if (!getState().refreshToken.isLoading) {
const userParams = Object.keys(userObject).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(userObject[key])).join('&');
const refreshTokenPromise = Api.post('/token', userParams).then(async (res) => {
await AsyncStorage.setItem('ACCESS_TOKEN', res.access_token);
await AsyncStorage.setItem('REFRESH_TOKEN', res.refresh_token);
await AsyncStorage.setItem('EXPIRES_IN', JSON.stringify(res['.expires']));
dispatch({
type: FETCHING_REFRESH_TOKEN_SUCCESS,
data: res,
});
return res ? Promise.resolve(res) : Promise.reject({
message: 'could not refresh token',
});
}).catch((err) => {
dispatch({
type: FETCHING_REFRESH_TOKEN_FAILURE,
});
throw err;
});
dispatch({
type: FETCHING_REFRESH_TOKEN,
refreshTokenPromise,
});
return refreshTokenPromise;
}
}
function isExpired(expiresIn) {
return moment(expiresIn).diff(moment(), 'seconds') < MIN_TOKEN_LIFESPAN;
}
Thank you.
I solved this problem using axios middlewares. I think is pretty nice.
import { AsyncStorage } from 'react-native';
import Config from 'react-native-config';
import axios from 'axios';
import { store } from '../store';
import { refreshToken } from '../actions/refreshToken'; // eslint-disable-line
const instance = axios.create({
baseURL: Config.API_URL,
});
let authTokenRequest;
function resetAuthTokenRequest() {
authTokenRequest = null;
}
async function getAuthToken() {
const clientRefreshToken = await AsyncStorage.getItem('clientRefreshToken');
if (!authTokenRequest) {
authTokenRequest = store.dispatch(refreshToken(clientRefreshToken));
authTokenRequest.then(
() => {
const {
token: { payload },
} = store.getState();
// save payload to async storage
},
() => {
resetAuthTokenRequest();
},
);
}
return authTokenRequest;
}
instance.interceptors.response.use(
response => response,
async (error) => {
const originalRequest = error.config;
if (
error.response.status === 401
&& !originalRequest._retry // eslint-disable-line no-underscore-dangle
) {
return getAuthToken()
.then(() => {
const {
token: {
payload: { 'access-token': accessToken, client, uid },
},
} = store.getState();
originalRequest.headers['access-token'] = accessToken;
originalRequest.headers.client = client;
originalRequest.headers.uid = uid;
originalRequest._retry = true; // eslint-disable-line no-underscore-dangle
return axios(originalRequest);
})
.catch(err => Promise.reject(err));
}
return Promise.reject(error);
},
);
export default instance;
If you have a problem, do not hesitate to ask.
you could benefit from redux-sagas
https://github.com/redux-saga/redux-saga
redux-sagas is just background runner which monitors your actions and can react when some specific action is met. You can listen for all actions and react to all or you can react to only latest as mentioned in comments
https://redux-saga.js.org/docs/api/#takelatestpattern-saga-args
while redux-thunk is just another way to create actions on the go and wait for some I/O to happen and then create some more actions when I/O is done. It's more like synced code pattern and redux-sagas is more like multi-threaded. On main thread you have your app running and on background thread you have sagas monitors and reactions
I am struggling with the async Redux (thunk). I trully don't understand what I am doing wrong with my async actions and why I get the error : Error: Actions must be plain objects. Use custom middleware for async actions.
export async function startLocalizationFetchingAsync(currentLocalizationState) {
return (dispatch) => {
let payload = {
request: {
sent:true,
}
};
dispatch({
type: "NEW_LOCALIZATION_REQUEST_SENT2",
payload: payload,
});
return axios.get("http://freegeoip.net/json/"+currentLocalizationState.clientIP)
.then(res => {
res = res.data;
var payload = {
country: res.country_name||'',
};
dispatch({
type: "NEW_LOCALIZATION",
payload: payload,
});
})
.catch(function (error) {
console.log("Promise Rejected",error);
dispatch({
type: "NEW_LOCALIZATION_REQUEST_ERROR",
payload: null,
});
});
};
}
while in the index.js router i have the following code
async action({ next, store }) {
// Execute each child route until one of them return the result
const route = await next();
await store.dispatch(startLocalizationFetchingAsync());
this generates me an error:
Error: Actions must be plain objects. Use custom middleware for async actions.
dispatch
webpack:///~/redux/es/createStore.js:153
http://myskyhub.ddns.net:3000/assets/client.js:9796:16
http://myskyhub.ddns.net:3000/assets/vendor.js:46309:16
Object.dispatch
webpack:///~/redux-thunk/lib/index.js:14
Object._callee$
webpack:///src/routes/index.js?a731:35
tryCatch
webpack:///~/regenerator-runtime/runtime.js:65
Generator.invoke
webpack:///~/regenerator-runtime/runtime.js:303
Generator.prototype.(anonymous
webpack:///~/regenerator-runtime/runtime.js:117
http://myskyhub.ddns.net:3000/assets/3.9645f2aeaa83c71f5539.hot-update.js:8:361
while the config store is the following
const middleware = [thunk.withExtraArgument(helpers), thunk.withExtraArgument(AsyncMiddleware)];
let enhancer;
if (__DEV__) {
middleware.push(createLogger());
//middleware.push(AsyncMiddleware());
enhancer = compose(
applyMiddleware(...middleware),
devToolsExtension,
);
} else {
enhancer = applyMiddleware(...middleware);
}
initialState.localization = defaultLocalization; //Location
// See https://github.com/rackt/redux/releases/tag/v3.1.0
const store = createStore(rootReducer, initialState, enhancer);
What I am doing wrong? I don't understand the redux-thunk...