React: is useEffect always the correct way to go? - reactjs

I am currently asking myself, if useEffect is always the right way to go.
I am thinking about that, because of my current project.
Imagine you have to following:
a state query which is just a query for backend. A query also knows a sorting. This will be filled by a loadQuery method, which gets a configured query from backend. But the initial state is a empty query and NOT undefined.
a method loadData which loads data from backend of type Entity[]. This method consumes the state query. This can directly be triggered, because of the empty query which is a valid query.
a method loadView which loads views from backend of type View. A view is just a JSON, which describes the cells of a table and has a sorting property.
a useEffect, which calls loadData and loadView. As dependency, we have the query.
a useEffect function which listen on the loaded view. If the loaded view has a sorting property, then it will manipulate the query state. This will lead to trigger the useEffect again from one point above.
This is the current scenario (shortened of course).
We also have
view switch button, which changes to query again. This will lead that the useEffect for loading data will be triggered.
a infinity loading in the table (which will also call loadData with an offset),
The query can change, if the user changes the query in a FilterDialog
Sometimes the useEffect for loading data, will be triggered 3 times in a row.
Sure, a solution will be to remember a boolean if some data got already loaded or not... But since we have more then one useEffect, it is hard to do this. It is also hard to remember a boolean for this, because if a componentDidUpdate cycle occurr, then you have to reset these booleans, because everything should be executed as before. But this can lead to problems, because everything is async.
Maybe some one else had the same problem, to have too much useEffects and this leads to unnecessary things and can share his experience with me :)

Related

useEffect with dependency array triggers multiple times because of array.map

I'm creating a simple React application where a parent component renders a number of children components based on an input range slider (using array.map).
I'm performing certain actions within a useEffect with a dependency array in the child component (particularly, I'm obtaining a 3D model's bounding box size using react-three-fiber). Even though I have the dependency array, I can see from the console logs that the useEffect executes each time I move the slider.
How can I ensure that this useEffect only executes once? I was considering passing the useRef of the model to the parent and doing the useEffect actions there, but I'm curious if there's a way to modify useEffect such that it just executes once.
After a deep understanding of my needs, one of the arguments was changing.
Functions not through a useCallback will always change. Rebuilt objects (and to some extent, arrays) are new as well (unless memoized).
I checked what is changing first. Instead of using useRef, figure out what is changing in the data.
But in reality, if that dependency was actually needed, I can memoize it (e.g. useMemo) instead of just removing it.

Dropdown item select triggers data processing - delay in closing dropdown

I have a dropdown created with MUI Select, MenuItem, and FormControl.
The Select's onChange updates a state variable using useState's callback function:
const handleDropdownChange = (e) => setDropdownVariable(e.target.value)
The state variable is a useMemo dependency (one of several dependencies, simplified below):
const result = useMemo(() => expensiveCalculation(), [dropdownStateVariable])
Everything works as intended.
Problem: The dropdown does not close until expensiveCalculation has finished running. The delay is only 1-2 seconds at the most, but ideally I'd like to close the dropdown before/while the process is running.
expensiveCalculation is not fetching data, but processing it. The data-fetching is done before this point, within useEffect.
I've chosen useMemo for this because I anticipate the user may frequently switch between different values of dropdownStateVariable and would like to benefit from the performance optimization of using memoized results.
Based on some preliminary research and some of the answers I've seen, it seems like useMemo would be the correct choice for me. In that case, is there any way around the slight delay in closing the Select dropdown? On the other hand, using useEffect, I would be able to use a state loading variable to give immediate feedback that the request is being processed while asynchronously preparing the data - but I'd lose the benefit of memoization.
Any suggestions appreciated.

How can I run certain code after the change (by useState's updater) to state has been applied successfully?

I have a selectbox with a list of customers and a list of vehicles for that customer displayed below the selectbox. When customer in the selectbox gets changed I need to update the list of vehicles, and show loader until we have got a response with new vehicles from the server.
Loader is shown when vehicles === null (vehicles not loaded).
here is my code:
const [vehicles, setVehicles] = useState(null);
useEffect(() => {
// setting vehicles to null to show the loader
setVehicles(null);
axios
.get(`/vehicles?customerId=${customer.id}`)
.then((response) => setVehicles(response.data));
}, [customer]);
It works just fine, but I am not sure if it's safe to fire the request without making sure that vehicles has actually got set to null first. Can it not theoretically happen that somehow setVehicles(null) gets executed after setVehicles(response.data) overwriting the state?
If so, how can I deal with this situation? I can't find a way to provide .then to the setVehicles or pass a callback to it like it's possible to do with class-based components.
I can probably just call setVehicles(null) when customer gets changed without firing request, and then have another useEffect monitoring changes to vehicles, and fire a request to load vehicles when vehicle gets set to null. But it feels unnecessarily complicated so I wonder if there is a better way to do it? Or can I just leave the code as it is hoping that because setVehicles(null) got scheduled first its state should always be applied before setVehicles(response.data)?
Thank you very much!
you could have race condition with consecutive setState in same useEffect. To avoid this issue you would have indeed to break down and create another useEffect, you can't escape that.
Though, given your example, a race condition won't really happen. You have a setState that triggers only once the http request resolves, which takes sometime. That second state update can't realistically beat the first one that triggers right away.

Does Apollo client optimistic update changes reference for every list item?

I have parent component A from where I am fetching TODO list (lets say 10 item), then mapping this array and passing whole object to component B.
Component B has a memo wrapper to compare todo object references to re-render only updated component. Sth like:
export default memo(ComponentB, (prevProps, nextProps) => {
return prevProps.TODO === nextProps.TODO
})
From component B on button click call useMutation to add one item inside list. I provided optimisticResponse and update function as it was on Apollo doc
Everything is working fine except: When I am adding optimisticResponse for fast UI change and log inside component B, I see that every list item is re-rendering, BUT in case I just leave update function without optimisticResponse then I see that only one item re-rendered.
So my question is, is this how Apollo treats optimisticResponse update or I should continue looking for some issue in my code and I am trying to figure out hours already :/
I just couldn't found any material yet which points out this particular case and if someone knows answer, maybe share the link or small suggestion. Thank you!
The optimistic response is committed to the cache, so you're logic will "think" that the data has updated. After the server responds, the cache is reverted and the real data is applied. So, in fact, you will experience 2 updates when you might have expected only 1.
Besides that, I believe you're saying that you have a component rendered for every "TODO" item. AKA, you're not passing an array to a single component. That sounds like the right method, just making sure that's what you have. So why do they all update? It doesn't seem like you need to worry about the apollo code for this issue. I think the first step is to change the line,
return prevProps.TODO === nextProps.TODO
to be a comparison that DOES NOT depend on references. Instead, the simplest quick change would be if there's some string ID field on the TODO. In that case you could do,
return prevProps.TODO.id === nextProps.TODO.id
This way you don't have to worry about whether apollo is returning the same reference or a new object with the same data. If that doesn't fix it, let me know in the comments and we can keep looking.

How to reset state of a child reactjs component

I have a react.js app which loads data from an API displays a bunch of questions (textboxes, radiolist, checkboxes, etc). The user fills them in and submits all answers back to the API, which then send a new set of questions.
All these questions are in a single object, so I've created parent react.js component which holds the current set of questions in state. When the state changes it re-renders each question below. This works pretty much fine.
The problem is that sometimes the API displays the exact same question for twice in a row, but as this is held in state and react.js is clever enough to know it doesn't need to render a completely new component, because the old one will do (with a few small updates).
The problem is that if I select a radio button on the first one, based on the initial data stored in state of the child component, which was initially set within componentDidMount). But when the second question comes along, because its essentially the same component, the state remains. The constructor is not called again.
I think I should be using one of the other events, perhaps:
componentWillReceiveProps
componentWillMount
componentWillUpdate
but I can't figure out which one is the most consistent one.
I basically want to reset the selectedAnswer everytime the parent has received new data from the API and essentially re-render all child components (but react won't do that).
Edit
I wonder instead of trying to reset this via the internal lifecycle events, whether I can pass in a different set of props into the component, so it can decide whether to re-create or re-render in the usual way.
okay so to optimally do this lets suppose you api which returns the set of questions, it might contain some id associated with it. Using that id create a uniq key for every child component while rendering something like below
<Child key={`${data_id}_${index}`} />
This will ensure that for the same set they do not keep mounting again and again and will only mount if a new data set is fetched in which case data_id will change which would cause remounting of each and every child component
I'd encourage you to check out Redux. It makes managing state much easier. I'd contribute some code on here but I am not sure I actually understand the question. If you linked us to your Github, then I could probably answer this specific question.
Also, it seems like you don't really need to touch state. It sounds more life attaching an event and controlling state that way. For example, using onSubmit, you can make an API call (and whatever else) and then have another function to reset the form state afterwards. It would be pretty straight forward, especially if you are using then/catch Promises.

Resources