redux-toolkit -> non-serializable value detected - reactjs

Error:
index.js:1 A non-serializable value was detected in an action, in the
path: payload.config.transformRequest.0.
Value: ƒ
transformRequest(data, headers) {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.i…
Slice:
export const getProducts = createAsyncThunk(
'products/getProducts',
async() => {
const res = await axios.get('http://localhost:5000/products/view-products', {withCredentials: true});
return res;
}
)
const getProductsSlice = createSlice({
name : 'products',
initialState : {
list : [],
status : null
},
extraReducers : {
[getProducts.pending] : (state) => {
state.status = 'loading'
},
[getProducts.fulfilled] : (state, {payload}) => {
console.log("produtcts payload: ", payload.data)
state.list = payload.data
state.status = 'success'
},
[getProducts.rejected] : (state) => {
state.status = 'failed'
}
}
})
Inside Component:
const dispatch = useDispatch();
const data = useSelector(state => state.products.list);
console.log("the products are :", data);
useEffect(() => {
dispatch(getProducts());
}, [dispatch]);
Other slices in the app work fine. Having a hard time working around the non-serializable

The problem with returning the result of axios.get as it is, is that apart from data it contains various other fields related to the request, including config for which you got the error. Although you're only saving data and not config, as the whole res object passes to the store it goes through a middleware called Serializability, which is included in redux-toolkit and enforces Redux recommendation to only store serializable data.
Serializable means it can be written down as text and converted back to original object without losing information, which doesn't work with functions. A javascript function apart from code also have scope (memory associated to it), which cannot be represented as text.
Serializability checks the whole payload (it is executed before your data reaches the store, so it doesn't know which parts will be used) and notices config. As config has methods among its members Serializability alerts you that it is not serializable. You could switch the middleware off, but it can detect genuine issues, so it's generally a better idea to just keep only relevant data in the payload.

Related

Redux-Toolkit createAsyncThunk request to firestore returns undefined

I am learning react and have started on a personal project using redux, redux-toolkit and firestore to store data. I have a problem with fetching and storing data that I cannot solve. Its entirely possible that I'm using a wrong approach to the problem or I've forced myself to a dead-end from a technical perspective (I am a noob after all), so any suggestions are welcome. On to the matter at hand.
Everything I have made so far works, in the sense that firestore receives the request and stores data as requested, however the code is not technically correct as you will see in the snippets bellow. I've been using createAsyncThunk from redux toolkit to handle requests and it seems to be working, but something seems a bit off in the code. If I use setDoc(), for example, to update a field in firestore, the return value is undefined (i.e. Promise<void>), as per the documentation: https://firebase.google.com/docs/reference/js/firestore_.md#updatedoc and I'm not sure how to handle it. Bellow I have shared the snippets required for making an update on the users document.
Initialize Firebase and define an update function (FIREBASE__CONFIG omitted):
const app = initializeApp(FIREBASE_CONFIG);
const dbFB = getFirestore(app);
export const updateFB = async (url, id, values) => {
const docRef = doc(dbFB, url, id);
const docResponse = await updateDoc(docRef, values);
return docResponse;
};
Define user slice (omitting configureStore here for brevity):
const initialState = {
isLoggedIn: false,
userDetails: null,
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
...
},
extraReducers: (builder) => {
builder.addCase(userUpdate.fulfilled, (state, action) => {
state.userDetails = { ...state.userDetails, ...action.payload };
});
}
});
Define userUpdate action (the requestError dispatch is an update for another slice):
export const userUpdate = createAsyncThunk(
'user/userUpdate',
async ({ userId, values }, { dispatch }) => {
try {
const usersRes = await updateFB('/users', userId, values);
return usersRes;
} catch (err) {
console.log(err);
dispatch(httpActions.requestError({ errorMessage: err.message || 'Something went wrong!'}));
}
}
);
Calling the update request on submit (using FormIK):
<Formik
...
onSubmit={async (values, { setSubmitting }) => {
dispatch(userUpdate({userId: userDetails.userId, values})).then(() => {
setSubmitting(false);
history.push(MY_PROFILE.path);
});
}}
>
{({ isSubmitting }) => (
<Form>
<div className='form-field'>
...
</div>
</Form>
)}
</Formik>
As you can see, I would like to continue execution of some other commands after the request has resolved on form submission. However, the return value of this request is undefined and it feels off to use .then() on it, even though it works.
Perhaps I've gone completely off the beaten path?
Help is appreciated, thank you :)
As you mentioned, the documentation stated that it will return Promise<void>. The reason behind: This is just a simple document write operation sent off to the server. If it did return the document data in the call, not only you'll need to wait for the data to be sent back, and you'll also be billed for a document read and bandwidth that is never needed.
If you need the updated data, you'll need to use getDoc() in a separate call. If you update multiple documents, you may need multiple getDoc() calls too. e.g:
export const updateFB = async (url, id, values) => {
const docRef = doc(dbFB, url, id);
const docResponse = await updateDoc(docRef, values);
// Returns updated document.
const docSnap = await getDoc(docRef);
console.log(docSnap.data());
return docSnap.data();
};

ReactJS Redux Toolkit - Fetching data from an API based on an Id does not get the second Id if called again

I am use ReactJS and Redux Toolkit (not using RTK Query). I setup a slice to retrieve data from an API based on an Id. The first time I fetch the data everything works fine. If I try to fetch data for a different Id nothing is fetched as it appears it just returns what is there. Am I using redux incorrectly or am I missing something easy. Below is the code. Further more, I would like to use the same piece of the store for different data types but the data is shaped the same. So the API call would be different but the data put in the store is shaped the same.
export const getScoreData = createAsyncThunk(
'contentitem/scoredata',
async (params) => {
return axios.get(`${params.apiAddress}api/v2/scores/${params.scoreId}`)
.then(
res => res.data,
err => console.error(err)
);
}
);
export const scoreSlice = createSlice({
name: 'contentitem',
initialState,
reducers: {},
extraReducers: {
[getScoreData.pending]: (state) => {
state.loading = true;
state.complete = false;
},
[getScoreData.fulfilled]: (state, { payload }) => {
state.loading = false;
state.complete = true;
state.data = payload;
},
[getScoreData.rejected]: (state) => {
state.loading = false;
state.complete = true;
}
}
});
Sorry all, it was not the redux toolkit or the store it was how I was interacting with it in my react components. Problem solved by calling the dispatch at the appropriate times.

Dispatching function multiple times in order to execute it with different parameters

Background
I'm building a React Native 0.64.1 app using Redux 4.1.0. This app fetches data from an API endpoint via POST which can take multiple "category" params. Only one value can be passed as category at a time, so in order to display data from multiple categories one would have to execute the function one time per category.
This is how the axios request is handled:
export const getData = (tk, value) =>
apiInstance
.request({
url: ENDPOINTS.CATEGORIES,
method: 'POST',
data: qs.stringify({
token: tk,
category: value,
}),
})
.then(response => {
return response.data;
})
.catch(error => {
return Promise.reject(error.message);
});
This function is then executed via a redux action/reducer, etc.
The tricky part is that "value" is set by the user and can be changed at any point in time.
The front end meets this function in a certain screen where this happens:
useEffect(() => {
dispatch(retrieveData(tk, value));
}, [dispatch, value]);
Problem & Question
I've tried doing a for loop that would iterate through an array that contains the possible strings of text value could be, that would look something like this:
const arrayOfValues = ['A','B','C','D']
let value = null;
useEffect(() => {
for (let i = 0; i < arrayOfValues.length; i++) {
value = arrayOfValues[i];
dispatch(retrieveData(tk, value));
}
}, [dispatch, value]);
I know this is horrible and I'm just showing it because it's the only thing I could think about (and it doesn't even work).
An ideal solution would:
Execute the first request on load
Run a request once per item in an array WITHOUT deleting the previously called for data
Each time it runs it needs to update the "value" parameter.
As a note about "retrieveData()", that is just the redux action.
Any help would be very much appreciated.
Solution by #rigojr
This seems like it should work, but either I haven't expressed myself properly or there's something wrong with the answer. I'm guessing it's the former.
#rigojr proposed the following:
export const getData = (tk, values) => values.map((value) => apiInstance
.request({
url: ENDPOINTS.CATEGORIES,
method: 'POST',
data: qs.stringify({
token: tk,
category: value,
}),
}))
Promise.all(getData(tk,values)) *****
.then(responseValues => {
// Dispatch the response, it will come an array of values response.
})
.catch(eer => {
// Error handling
})
Howeve, values in the line marked with many asterisks is inaccessible. I believe this is because previosuly I failed to mention that the whole Redux data flow happens in three separate files.
Dispatching the action: UI dispatches an action onLoad in App.js:
useEffect(() => {
dispatch(retrieveData(tk, values));
}, [dispatch, value]);
The action is ran in action.js file. It looks something like this:
Note that I have added the Promise.all() in this screen, as it seems like the place where it should actually go, instead of the other one.
export const actionTypes = keyMirror({
RETRIEVE_REQUEST: null,
RETRIEVE_SUCCESS: null,
RETRIEVE_FAILURE: null,
});
const actionCreators = {
request: createAction(actionTypes.RETRIEVE_REQUEST),
success: createAction(actionTypes.RETRIEVE_SUCCESS),
failure: createAction(actionTypes.RETRIEVE_FAILURE),
};
export const retrieveData = (tk, values) => dispatch => {
dispatch(actionCreators.request());
Promise.all(getData(tk, values))
.then(data => dispatch(actionCreators.success(data)))
.catch(error => dispatch(actionCreators.failure(error)));
};
Then there's the reducer, of course in reducer.js:
export const initialState = {
loadingData: false,
data: [],
error: null,
};
const actionsMap = {
[actionTypes.RETRIEVE_REQUEST]: state => ({
...state,
loadingData: true,
}),
[actionTypes.RETRIEVE_SUCCESS]: (state, action) => ({
...state,
loadingData: false,
data: action.payload,
}),
[actionTypes.RETRIEVE_FAILURE]: (state, action) => ({
...state,
loadingData: false,
error: action.payload,
}),
};
export default (state = initialState, action) => {
const actionHandler = actionsMap[action.type];
if (!actionHandler) {
return state;
}
return actionHandler(state, action);
};
Data is then accessed via a selector:
const data = useSelector(state => state.data.data);
When running the code above, I am greeted with the following lovely error message:
TypeError: undefined is not a function (near '...}).then(function (response)...')
And in the emulator, I get pointed in the direction of these lines of code:
export const getData = (tk, values) => values.map((value) => apiInstance
.request({
url: ENDPOINTS.CATEGORIES,
method: 'POST',
data: qs.stringify({
token: tk,
category: value,
}),
}))
More specifically, the emulator seems to think that the error has to do with value.map, as it points a little red arrow at "values" just before the method.
Any idea on what went wrong?
Note
Upon refresh the error might change, for example just now it has shown the same error message but it points in the direction of
export const retrieveData = (tk, values) => dispatch => {
dispatch(actionCreators.request());
Promise.all(getData(tk, values))
.then(data => dispatch(actionCreators.success(data)))
.catch(error => dispatch(actionCreators.failure(error)));
};
More specifically, the little red arrow points at getData.
Refreshing again, and the error points at
useEffect(() => {
dispatch(retrieveData(tk, values));
}, [dispatch, value]);
Refrsh once more and it just loses it and goes for a module, as shown in the image:
It doesn't go further from there. Just mind that every single time, the error message is TypeError: undefined is not a function (near '...}).then(function (response)...'), it just points in a new direction.
Solved in
Unable to perform .map whithin function
Try to use a Promise.all():
export const getData = (tk, values) => values.map((value) => apiInstance
.request({
url: ENDPOINTS.CATEGORIES,
method: 'POST',
data: qs.stringify({
token: tk,
category: value,
}),
}))
Promise.all(getData(tk,values))
.then(responseValues => {
// Dispatch the response, it will come an array of values response.
})
.catch(eer => {
// Error handling
})
Read more about Promise.all() here

Pulling Redux data with userID param error - React / Node / Express

I am trying to pull a log of user data belonging to userID from my backend in React. The backend API is working correctly with Postman however I can't get this working with the userID. It is console logging the correctly userID in the component however I am yet to get a Thunk Redux store data working with a param passed in.
Is there anywhere obvious I am going wrong from looking at the code below? I have had my Redux working previously with data not using a param so I know it is not an issue with my store / redux index etc.
component
const userDiveLogList = useSelector(state => state.user.userDiveLogList);
const [userDiveLog, setUserDiveLog] = useState({
userDiveLogList: [],
expanded: false
})
const dispatch = useDispatch();
useEffect(() => {
dispatch(requireUserDiveLogData(user.userID));
}, []);
action
// load a users diveLog
export const requireUserDiveLogData = createAsyncThunk('diveLog/requireData',
async (userID, thunkAPI) => {
const response = await userDiveLogList(userID);
return response.data;
},
{
condition: (_, { getState }) => {
const { user } = getState();
if (user.didLoadData) {
return false;
}
}
}
)
reducer
export const userSlice = createSlice({
name: 'user',
initialState: {
userDiveLogList: [],
didLoadData: false,
},
reducers: {
[requireUserDiveLogData.pending.type]: (state) => {
state.didLoadData = true;
},
[requireUserDiveLogData.fulfilled.type]: (state, action) => {
return {
...state,
...action.payload
}
},
service
// returns list of dives per user from db
export const userDiveLogList = (userID) => {
return axios.get(API_URL + `userdiveloglist/${userID}`);
};
Update
Does this have something to do with my action call for when data gets called if it is already called? I have the getState condition const as userDiveLog when it is in the user branch and it is user/userDiveLogList in the branch, it is also named userDiveLogList when it is being sent at the backend. I can see the backend request being made for the correct user number but nothing is returning.

How to prevent non-deterministic state updation in Redux?

When working with Redux, maintaining the shape of the initial state is crucial. The results/data of side effects like API call will change the shape of the state since we have no control over the properties. For example, consider this initial state:
const book = {
id: 0,
name: 'something'
};
And updation is made to it by the book sub-reducer as follows based on the API data:
//receives `book` part of the state
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
}
Two scenarios that could happen:
If the data sent from the API is null, then newly produced state is now {} as a result of the spread operator. If some parts of UI were to listen to the book part of the state, then it will break. Possibly access individual properties from the API data? In that case, null/undefined checks needs to be performed for properties. Is there a more elegant solution?
There could also be additional properties in the data which we may not be interested in. Possibly use an object mapper to filter unused properties?
What is the best practice to handle these kind of scenarios and prevent state becoming non-deterministic? Please share your experience on how you approached these scenarios.
Only the reducer has to be pure/deterministic, not the stuff outside of it.
To prevent your reducer from overwriting data incorrectly, write some logic between the API response and the dispatch-call to ensure the reducer always gets valid data.
For example a thunk might look like:
const createBook = (name) => {
return async dispatch => {
// suppose the api call gives back "uid" plus extra data
const { uid, ...unneededData } = await myApi.setBook(name);
// dispatch the data in the way the reducer expects it
dispatch({ type: 'SET_BOOK', id: uid, name });
}
}
In the above example, the api call gives me uid, but no name, and a bunch of extra data. Just prepare the data before dispatching it.
The best practice is the one where you prevent your app from breaking from every aspect, which means you need to check and format your data before returning from the reducer.
In your case, I would check for both data validity and map it to a necessary format.
only dispatch 'SET_BOOK' if API response has both id and book.
in order to avoid unnecessary additional properties, you can always map your data const book = {id: apiData.id, book: apiData.book} before dispatching.
In your reducer you can do like below. This way only id and name will get updated even though there are other key/values in the response. Also this will make sure that if null values are received then those values will not be updated in the state. Hope this will help in resolving the issue.
//receives `book` part of the state
const bookReducer = (state=book, action) => {
const { type, payload } = action;
switch(type) {
case 'SET_BOOK': {
return {
...state,
...(payload.id && {id: payload.id}),
...(payload.name && {name: payload.name})
};
} default:
return state;
}
}
Your redux reducer logic should not worry about that due to its deterministic nature. You handle your api call and response handling elsewhere (redux thunk or component), and then dispatch the action to set your redux. Building off of your example:
book.reducer.js
const book = {
id: 0,
name: ''
};
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
return { ...action.payload };
} default:
return state;
}
book.actions.js
const setBook = (book) => ({
type: SET_HEROES,
payload: book
});
// thunk
const findBook = name => async dispatch => {
const book = await bookService.findBook(name);
if (book) {
dispatch(setBook(book));
}
};
book.service.js
const findBook = async (name) => {
// Do your api call
const bookResponse = axios.get(`${apiUrl}/book/search/${name}`);
// Handle the response
if (!bookResponse) {
// Logic you do if book not found
return null;
}
return {id: bookResponse.id, name: bookResponse.name};
}
Now in a component you can just dispatch the findBook call
Component.js
const Component = () => {
const [search, setSearch] = useState('');
const dispatch = useDispatch();
const handleOnSearch = () => {
dispatch(findBook(search));
}
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)}/>
<button onClick={handleOnSearch}>Search</button>
</div>
);
}
If field value from API is undefined then convert it into null and store so that the code doesn't break and operatable. If API gives other params as well then de-structure the API returned object and extract the required fields. So that storing unnecessary data can be avoided.
const bookReducer = (state=book, action) => {
switch(action.type) {
case 'SET_BOOK': {
const {id, name, otherParam1, otherParam2} = action.payload;
return {
id: id || null,
name: name || null,
otherParam1,
otherParam2
}
} default:
return state;
}
}
Having the value null won't break the code instead, it renders nothing
which is better than undefined which breaks the code
Hope this helps you
What I do is to have all of my logic in my action method and create reducers for when an action is correctly fulfilled and another one for when is rejected. In the fulfilled reducer, I would do the regular instructions and in the rejected reducer I would add the data to a variable called error which I always have in my state and use in the frontend to show an error message if needed.
Example
This is an action that creates a house by sending a post request to my api which returns the created object or an error if something went wrong.
export const createHouse = houseData => {
const URL = HTTP://EXAMPLE.URL
return async dispatch => {
try {
const response = await axios.post(`${URL}`, houseData);
const data = await response.data;
dispatch({ type: "CREATE_HOUSE_DRAFT_FULFILLED", data });
} catch (err) {
dispatch({ type: "CREATE_HOUSE_DRAFT_REJECTED", data: err });
}
};
};
Then I would have 2 reducer methos to recieve the fulfilled or the rejected response, like this.
case 'CREATE_HOUSE_DRAFT_FULFILLED': {
return {
houses: [action.data, ...state.houses],
house: action.data,
houseCount: state.houseCount + 1,
fetched: true,
error: null
};
}
case 'CREATE_HOUSE_DRAFT_REJECTED': {
return {
...state,
error: action.data.response.data,
fetched: false,
success: null
};
}
Hope this works for you!

Resources