Update state if response is ok - javascript

So I have kind of a different problem here. My react app has a reducer that updates the state of a cart when a product is added. As i works right now i send this updated state to an API endpoint so save it to the database.
const addToCart = (item, quantity = 1) => {
dispatch({
type: 'ADD_ITEM',
payload: { item: item, quantity: quantity },
});
};
My api endpoint is called by a useEffect on this function.
useEffect(() => {
updateCart(state);
}, [removeFromCart, addToCart]);
So basically what I am doing is that I send the state to the db for removing and adding items to cart. But this comes with a problem. If a user adds many items to cart very rapidly the state and what is saved inn the database is out of sync.
Then I was thinking to revert the state if the response is not ok from the API endpoint. But a little unsure on how to do this. Anybody have any tips on how to solve this problem?

You should debounce the call to updateCart.
There's a lot of ways to implement a debounce on the useEffect. You can either use a library that already implements that hook or create a hook and manage the debounce yourself.
To get an overall ideia of what is debounce, you can look at this question.
To understand how it works with react and useEffect, you can look at this question
A simple way is just using useDebounce from react-use and do:
useDebounce(() => {
updateCart(state);
}, 1000, [removeFromCart, addToCart])
Where 1000 is the time for the debounce.
This way you remove the problem of having the user add and remove a lot of items and having the db out of sync and there's no need to check if the response is ok from the server.

Related

useEffect for real time data update

I am creating an easy chat app, with different text channels. I am facing an infinite loop issue when using the useEffect hook to update the messagesList on real time. You can see the code below but this is what I am trying to achieve:
First useEffect is for the chat window to scroll to the last message every time there is a change in the messagesList array. This means: I am in the middle of the messages window, I write a new message and it takes me to the bottom. This is working fine.
Second useEffect is for the messagesList to be rendered whenever the channel is changed or there is any change in the messagesList. Adding the messagesList as a dependency is causing the infinite loop... but I think I need it cause otherwise the following happens: user1 is inside the chat channel and user2 writes a new message. User1 wont see the new message displayed as his chat is not being re-rendered. How would you make it for the new message to be displayed for user1?
Sorry for the confusing question and thanks a lot in advance!
useEffect(() => {
anchor.current.scrollIntoView(false);
}, [messagesList]);
useEffect(() => {
const unsubscribe = onSnapshot(
collection(firestore, `channels/${activChannel}/messages`),
(snapshot) => {
console.log(snapshot.docs);
getMessagesList();
}
);
return () => unsubscribe();
}, [activChannel, messagesList]);
I am not familiar with firestore, but perhaps you could tie the updating of the messages to the event that an user submits his message or use useSyncExternalStore. This piece of documentation on useEffect use cases might help you.
an excerpt from the docs:
Here, the component subscribes to an external data store (in this
case, the browser navigator.onLine API). Since this API does not exist
on the server (so it can’t be used to generate the initial HTML),
initially the state is set to true. Whenever the value of that data
store changes in the browser, the component updates its state.
Although it’s common to use Effects for this, React has a
purpose-built Hook for subscribing to an external store that is
preferred instead. Delete the Effect and replace it with a call to
useSyncExternalStore:

Using a value from a previous query in react query

I have a react query to get user data like this
const { data: queryInfo, status: queryInfoLoading } = useQuery('users', () =>
getUsers()),
);
I then have a sibling component that needs the same data from the get users query. Is there a way to get the results of the get users query without re-running the query?
Essentially, I would like to do something like this
const userResults = dataFromUserQuery
const { data: newInfo, status: newInfoLoading } = useQuery('newUserData', () =>
getNewUsers(userResults.name)),
)
As suggested in this related question (how can i access my queries from react-query?), writing a custom hook and reusing it wherever you need the data is the recommended approach.
Per default, react-query will trigger a background refetch when a new subscriber mounts to keep the data in the cache up-to-date. You can set a staleTime on the query to tell the library how long some data is considered fresh. In that time, the data will always come from the cache if it exists and no refreshes will be triggered.

Jest / React - How to test the results of useState state update in a function

I have a simple cart function that, when a user clicks to increase or decrease the quantity of an item in a shopping cart, calls a useState function to update the cart quantity in state.
const [cart, setCart] = useState([]);
const onUpdateItemQuantity = (cartItem, quantityChange) => {
const newCart = [...cart];
const shouldRemoveFromCart = quantityChange === -1 && cartItem.count === 1;
...
if (shouldRemoveFromCart) {
newCart.splice(cartIndex, 1);
} else {
...
}
setCart(newCart); //the useState function is called
}
so in jest, I have a function that tests when a user sets a cart item's quantity to zero, but it does not yet remove the item from the cart, I'm assuming because it has not yet received the results of setCart(newCart):
test('on decrement item from 1 to 0, remove from cart', () => {
const [cartItemToDecrement] = result.current.cartItems;
const productToDecrement = result.current.products.find(
p => p.id === cartItemToDecrement.id
);
act(() => {
result.current.decrementItem(cartItemToDecrement);
});
act(() => {
result.current.decrementItem(cartItemToDecrement);
});
...
expect(result.current.cartItems).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: cartItemToDecrement.id,
count: cartItemToDecrement.count - 2,
inventory: productToDecrement.inventory + 2
})
])
);
});
});
This test passes, because the cart now contains an item whose quantity has dropped to zero. But really it shouldn't, because from the splice operation in onUpdateItemQuantity our cartItems array should now not include the object at all. How do I verify in my jest test that this removal is happening (the react code works properly).
I do not really understand the relation between the test and your onUpdateItemQuantity very much because the context you provided is not sufficient to me.
But from your question, there are 2 clues which may help to save your time.
You may know setCart from useState is not synchronous, so that if you try to access cart from useState at the same frame, it shouldn't reflect the change even though you ensure setCart called. useEffect is a good solution. You can find the doc of useEffect [https://reactjs.org/docs/hooks-effect.html][1], any change should be reflected in useEffect, perhaps you can put your test there.
Add an extra variable myCart to store cart and a function setMyCart. Instead of calling setCart in your code, call setMyCart. setMyCart is like,
function setMyCart(newCart)
{
myCart = newCart;
setCart(myCart); // this is for triggering React re-render
}
then use myCart which can reflect the change immediately for testing.
The only purpose of the additional code in the 2nd point, is when we still rely on the re-render mechanism of React, we use our own Model myCart for our particular logic rather than cart state from React which is not only for View but also used for our logic on an inappropriate occasion.
Testing for state changes is a challenge, because it doesn't produce any specific output (except whatever impacts your render).
The key is to narrow the testing scope to your responsibilities, versus testing React. React is responsible for making sure that useState updates the state properly. Your responsibility is to make sure you are using the data properly and sending the correct data back. So instead of testing useState, test how your component responds to the state it is given, and make sure you are setting the correct state.
The answer to to mock useState. Give it an implementation that returns the initial value passed to it, with a mock function that lets you read what data is being saved.
More detailed answer here:
https://dev.to/theactualgivens/testing-react-hook-state-changes-2oga
HTH!
If you need to test every state update using useState(), you can do so by implementing a useEffect function that listens specifically for that state change, in your case, cart:
useEffect(() => {
test(...)
), [cart]);
And so you can do the test inside the useEffect() function.
That will ensure that you have the correct value of cart and it will be called every time cart is updated.
It's totally unclear how onUpdateItemQuantity is called. And even what it does exactly. Never the less, you claim that toEqual(...[{...,count:cartItemToDecrement.count - 2,...}]...) passes. That probably means that onUpdateItemQuantity is called inside of decrementItem, inside of onUpdateItemQuantity there is something like newCart[cartIndex].count--, and if toEqual() passes, that means that setState() successfully updates the state, and after act() you have the actualized state. And if test's name is correct and initial value of cartItemToDecrement.count === 1 that should mean that cartItemToDecrement.count - 2 === -1 and you never go inside of if (shouldRemoveFromCart). So maybe the issue is not with the test, but with the code.

React Hooks: Referencing context and managing dependencies in useEffect hook

I am trying to use react hooks to make a Table component that displays rows of data from an API based on a set of filters that the user can choose. I want to make a new call to fetch data whenever the user clicks an 'Apply Filters' button, not when the user makes changes to the filters.
I am using context to manage the 'filters' state and a 'lastFetched' state which tracks when the user last clicked the 'Apply Filters' button (as well as other states on the page). Updates to the context are made via the useReducer hook and its dispatch method (see here).
The data fetching occurs in a useEffect hook that reruns whenever the 'lastFetched' state changes. This appears to be working correctly; however, the effect references other values from the context (i.e. the filters) that are not included in the dependencies. I am aware of the exhaustive-deps eslint rule, and I am concerned that I am not handling the hook's dependencies correctly.
const Table = () => {
const [context, dispatch] = useTableContext(); // implemented with createContext and useReducer
const { filters, lastFetched } = context;
useEffect(() => {
if (!filters.run) {
return;
}
dispatch({ type: 'FETCH_DATA_BEGIN' });
const params = convertContextToParams(context); // this is lazy, but essentially just uses the the filters and some other state from the context
API.fetchData(params)
.then((data) => {
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data.results });
})
.catch((e) => {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: e.response.data.message });
});
return () => { ... some cleanup... };
}, [lastFetched]); // <== This is the part in question
return <...some jsx.../>
};
Again, this appears to be working, but according to the react docs, it seems I should be including all the values from the context used in the hook in the hook's dependencies in order to prevent stale references. This would cause the logic to break, since I don't want to fetch data whenever the filters change.
My question is: when the user clicks 'Apply Filters', updates context.lastFetched, and triggers the useEffect hook, will the hook be referencing stale filter state from the context? If so, why? Since the effect is rerun whenever the button is clicked, and all the state updates are done via a reducer, does the usual danger of referencing stale variables in a closure still apply?
Any guidance appreciated!
Note: I have thought about using useRef to prevent this issue, or perhaps devising some custom async middleware to fetch data on certain dispatches, but this is the solution I currently have.
I am not an expert but I would like to provide my takes. According to my understanding of how Context works, you will not get stale filter data with the current implementation. useReducer updates the state with a new object which will trigger Table to be re-render.
Also, Table component doesn't really care about filter data unless lastFetched is changed by a click event. If lastFetched is changed, all the Consumer of TableContext will be re-render again. You should not get stale filter data either.

Which is better CRUD coding style using Redux?

I have a simple question about coding style for single page application. My front end is using React Redux
For example I have a standard CRUD page where data is displayed in table and pop up modal form. Data table is filtered from the server not from the client.
My question : If i create, update or remove a data should I call a refresh function or just edit it in redux store?
Refresh function :
Data always updated
Newly added data is filtered
Two times request, slower, unresponsive (Main problem)
Redux store:
App looks responsive
One time request
Lost server side filter function and data is not updated if multiple users is using the app (Main Problem)
Any advice will be appreciated
Edit the store locally to give immediate feedback, then send the request and when you get the reply back consolidate the store with the new data
basically, do both things and get the best benefit of both worlds
Dispatch an async action which queries the server where filter happens and when it resolves, update redux state with the refreshed, filtered data.
Pseudocode
// dispatches an action to refresh data without page reload
export function refreshDataAction() {
return (dispatch, getState) => {
return (
fetch('api/data', options) // fetch the data from server and let it filter
.then(data => dispatch(updateDataAction(data)))
);
};
}
// dispatches an action to update redux state with filtered data
export default function updateDataAction(data) {
return {
type: 'UPDATE_DATA',
...data,
}
}
Then you could just call dispatch(refreshDataAction()). Data is filtered, no page refresh.
Calling refresh in a React application (not only React, but any real-time front-end app) kind of defies whole principal of using React.
What you should do is, whenever there occurs a data-changing operation in your client, you should trigger an API call, that alters your server-side data accordingly. Send the data back to the client (you can send it to all clients, if you fancy web-socket), save it to the Redux state to trigger a re-render.

Categories