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.
Related
I found some old post that a guy having problem with his code in React and alot answer said that it was caused by because useState is asycnhronous. I was curious what it mean by asycnhronous and I found this article
useState is an asynchronous hook and it doesn't change the state
immediately, it has to wait for the component to re-render. useRef is
a synchronous hook that updates the state immediately and persists its
value through the component's lifecycle, but it doesn't trigger a
re-render
But i still don't understand what it means and why is such a problem. Can someone give me an example?
I would say this is one of the myth to call useState asynchronous.
IMHO, if you open the source code of react, I'm speaking to react 16.x and below (because i didn't read after 16), useState isn't asynchronous.
So why are we saying useState behaves as asynchronous code? We need some coding example.
const App = () => {
const [value, setValue] = useState(1)
const onClick = () => {
setValue(2)
console.log(value)
}
return <input value={value} />
}
In the above typical setup of useState, when you call setValue(2), value wouldn't become 2 right away. This is apparent if you put a console.log after that. Then why the input does show 2 instead of 1, you might ask?
That's because React made a second render to App, you can think of App has been called one more time after setValue. For this reason, people call useState async.
But then why I'm not calling useState async? well, this is a bit long story. The short story is that useState is implemented inside React using plain non-async codes, therefore you can't say a code async just because it has been called twice or it has been deferred to call. Async means a bit differently in Javascript. This is another topic which I won't get into here.
I don't want to speak for the React team as to why state changes are asynchronous, but here's some random dude's explanation. I'll be referring to the JavaScript event loop, which is basically a program that runs many times per second. Each time it runs, it processes a series of tasks synchronously. Asynchronous code is executed across several event loops. This is a very basic and oversimplified explanation - you should go read more about the event loop.
So your initial state declaration happens in your initial render - the first time the function is called. This initial render happens synchronously - meaning the function is executed top to bottom in a single event loop. It's worth pointing out that React components cannot be async:
const SomeComponent = () => {
const [foo, setFoo] = useState("bar");
return <div>The value of foo is "{bar}"</div>;
}
After your initial render, the state only changes when something happens: user clicks a button, the screen resizes, a timer interval fires, etc. Whenever an "event" happens, there can be a lot of processing going on in the JavaScript event loop.
Let's look at a button click, the event propagates all the way down and back up the entire DOM tree which contains the button. The browser is also rendering different states of the button, causing the browser to repaint. You also have developer code which is running in the button click handler which might be doing some heavy lifting like iterating over an array. This all happens within a single event loop. The point of this explanation is to convey that "a lot is going on". Eventually, the state will get updated with setFoo(...).
At this point, React now has to figure out which component changed, calculate and rerender your application in a virtual DOM, perform a diff with the actual DOM, and then apply any changes to the actual DOM. Only at this point does the browser finally paint the result to the screen. This whole operation can be very taxing on a CPU.
If react were to handle state changes synchronously, all of this virtual DOM diffing and calculating would happen in the same event loop as the initial button click. It is very likely that most apps would cause the browser to sporadically freeze and jitter because the CPU is doing so much work at one time.
There are also some other things to consider. Suppose the window resizes and a bunch of different components perform some calculations and update their state. You don't want react to synchronously rerender every time a component changes its state. You want react to kind of wait and collect a whole bunch of state changes and process them at the same time. Browsers also provide mechanisms like requestAnimationFrame to help web-based applications asynchronously render in such a way that works best for the user and their system/browser resources.
There are many other things to consider, but this post is getting long. Hope this helps.
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 :)
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.
I've been using hooks in my React project for a while now and have developed a hook for getting data, similar to many others, which I use like this.
const [callApi, isLoading, result, errrors] = useData();
callApi is a function which you call with your data and axios config, it makes the API call and updates the other variables. I deal with the result in a useEffect() call which sets the state when it receives new data. It's fairly simple.
The problem is that while it works very well for one request at a time, it has a single isLoading state and a single result state, which is no good if it's called before the previous call is finished. Let's say my user sending a message to his friends and the UI shows a list of friends with a "send message" button next to each name, they might click 3 buttons and make 3 calls in quick succession. We then have a race condition to see which one sets the result first.
I'm used to imperative programming with promises, in my old Angular code the button click would call an event handler which would call the api and the promise .then() would then deal with the result. This could happen 100 times simultaneously and each one would resolve seperately and then update the UI.
So my question is, is there an idiomatic way of doing this declaratively using react hooks so that I can fire off multiple requests and deal with the loading/result/errors separately?
I need to access specific properties of the redux state inside my containers. These properties will in turn be used as a key to fetch other nested properties and map them to component props. I use immutableJS.
Is the best way to do this inside the mapStateToProps? Will this cause any performance overhead or is it OK since the mapStateToProps already receives the whole state as a parameter?
Here is an example.
state selectors:
export const userSettingsState = state => state.get('userSettings');
export const transactionsState= state => state.get('transactions');
userSettings is an immutable Map and transactions is also a Map of userID and transaction list pairs.
const mapStateToProps = (state) => {
const curUID = userSettingsState(state).get('currentUserID');
return {
trList: transactionsState(state).getIn([curUID, 'trList'])
};
};
I need the currentUID to access the user transactions. The above sample works fine. But is it the best way?
Great question. Yes, this is the preferred way to handle computed props and to generate derived data. Not only is this the best way, but it's also very performant. Cory House (a very good React dev well known in the community) tweeted about something similar to this a few days ago and his same methodology applies to mapStateToProps(). Your only other option is to add a compute method to componentWillReceiveProps() AND componentDidMount(). This get's messy fast as you could imagine.
Now, some reservations about the way you're doing this.
Don't put async calls in here! This will also cause issues with rendering components as mapStateToProps() runs before the component mounts. If your component is waiting for promises before it mounts, you're going to take performance hits. This is probably obvious.
Speaking of performance, I am personally generating derived data exactly like this for a dropdown box with 8,600 entries that on user input, applies a fuzzy filter to those 8,600 entries, re-renders the dropdown list, and displays a new filtered list. There have been zero performance issues and the component is smooth as butter.