I'm trying to build a little calendar, where one can "mark" days by clicking on them.
Each click causes a request to the server to persist the change (set or unset a day).
It seems like useState can only keep up with so many changes at once, and due to the nature of reloading the component, i loose some of the fetch-requests happening as well.
When i understand the useState behavior correctly, each setDays will reload the whole Calendar, even if an instance still has a request running. The system is smart enought, so that a (limited) number of requests still manage to complete in the background and trigger their state update. However i have no control or guarantee over how many "make" it when clicking fast.
My real code has an additional state change, by marking/unmarking each day as "in flight" (via dayClassName) while the request is running, probably increasing the problem even more.
I'm a bit lost in what direction to go from here:
Should i try to limit the effect of a day change to a single day itself, avoiding to update the whole calendar with every click (need the result outside, though).
Or is a different system/strategy to manage the state, e.g. redux, the better choice here. For example to serialize the updates into one stream.
Update:
Here is a codesandbox with example: https://zpvy0.csb.app/
I tried to get as close to the real thing as possible, unfortunately i still can't reproduce the issue. It seems like react/useState is not the issue, as in the codesandbox it works reliable with 30+ requests triggered at once.
Update 2:
I have rewritten my code using the codesandbox version as base (re adding what other functionality/styling, etc was there). Now everything works perfectly.
Currently i have no idea what difference was causing it at the end.
If I'm understanding your issue correctly it sounds like the issue is that addDay and removeDay are called in quick succession and you are losing some state updates. You mention users clicking "to fast" so it may be the case that more than 1 state update is being enqueued. Since you are using plain updates if 2 updates are enqueued within the same render cycle the second update overwrites the first. If more get enqueued then each subsequent processed update overwrites the previous. Hopefully you get the idea here.
The resolution for this is to use functional state updates so each enqueued and processed update updates from the previous state, not the state the update was enqueued in. This means if multiple updates are enqueued in a render cycle each update/change is applied sequentially and the result aggregated state update is available for the next render cycle.
Functional Updates
If the new state is computed using the previous state, you can pass a
function to setState. The function will receive the previous value,
and return an updated value.
The previous state is an array and you are updating from that array when appending new day objects. It's a very minor tweak.
const addDay = async (day) => {
await makeRequest(day);
setDays(days => [...days, day]);
};
const removeDay = async (day) => {
await makeRequest(day);
setDays(days => days.filter((d) => d !== day));
};
Related
I'm working on a Nextjs project with RTK Query. Can someone tell me how to access the isLoading state of the mutation that is initiated like this https://redux-toolkit.js.org/rtk-query/api/created-api/endpoints#initiate. I don't want to use the generated hook useLoginMutation but instead get the data by unwrapping the returned result after dispatching like this await dispatch(api.endpoints.login.initiate()).unwrap() because I don't want my form to re render. Thank you in advance!
because I don't want my form to re render.
Hi, RTK Query author here.
Generally, you can use the selectFromResult option to reduce the amount of fields returned from the hook, and as such also reduce the amount of rerenders (as less things are changing).
Generally though, I have to strongly advise you: don't care about the amount of rerenders on this level. React is made to fastly rerender your components. One, two, or even five rerenders within a 1-3 seconds frame should not make any difference for your application - usually, your component will rerender on every keypress in your form anyways!
If, and only if you are seeing actual performance degradation, you get to the point of optimizing things like this - and then you would see if somewhere you have dozens or hundreds of rerenders, but still do not care about single-digit rerender numbers. If single-digit rerenders are a problem for you, you have heavy computation logic inside your components, and you need to optimize that - the execution of one of your component functions should always be in the sub-milliseconds. Then that's the place to optimize.
As for triggering the mutation with initiate: It will be loading until your await continues and done after that. If you just need that information, there is no need to access an isLoading state. You must set the component state if you need that information in your component. And that would trigger a rerender. You see where I am going: use the hooks! That's what they are made for.
Also, if using initiate: make sure to unsubscribe the result afterward or it will stay in cache forever. Again, this is something the hooks do for you.
I wanted to get your opinion on something.
I'm trying to understand how a subscription works. however, I couldn't find a way to pull an array of objects in a subscription. for example, if I use createMany, I can not return all the result via subscription.
The second issue is if I return a single item for example if it's a new item, I have to "manually (air quote)" add that item to the list that is already displayed. But this feels to me I don't actually display real-time true data.
So my question is using something like
useEffect(() => {
// refetching original query when subscription is triggered
refetch();
}, [updatedNotificationData]);
would there be any downside like hitting up the server more than I should? let's say every time there is a refetching happens I might be pulling thousands of notifications (I know there is caching but still) or is there a better way to deal with bringing new data.
Also, I tried adding subscribed data to the original list but for some reason react adds 2 of the same item every time.
Thanks in advance if you can give me in the right direction.
if I use createMany, I can not return all the result via subscription.
That shouldn't be a problem if you define the return type of the subscription as array.
type Subscription{
onChange:[ObjectType]
}
It would allow you to avoid fetching again but updating cache can get a bit complicated.
Also, I tried adding subscribed data to the original list but for some reason react adds 2 of the same item every time.
In case you are using the the subscribeToMore method it's not really reacts fault but the way how the updateQuery method works: github issue regarding this.
My workaround was to subscribe via the useSubscription hook and handle the cache modifications inside the onSubscriptionData callback apollo documentation and also setting the useQuery hooks skip option once I get the data so it wont query on each rerender.
My code works, so I'm not asking for help in that regard, but I want to understand why it's working as it is- react theory 101. I like to study things that I come across so I can get predictable results in the future.
I have two similar objects:
const [formData, setFormData] = useState({})
const formState = {}
Both intended to serve the same purpose. I only need one of them, whichever works.
I'm prop drilling down several layers. I'm running a multiple step modal gathering client input to send to server. I am collecting data but that doesn't have to constitute 'state' insofar as it's not going to change the rendering/User Interface. It's passively collected. Do I need to use 'state'?
It turns out when I clicked the choices to a question, formState captured the values and they got logged to the console.
However when I went to another questions on a new page, the old data got dropped.
I had to go to the drawing board. Actually I figured I would have to use the setFormData hook, and manage state after all. I didn't expect it to trigger any re-renders because I'm not using the data in my view. Nothing would be a DOM 'hit'.
Well it seems like the fact that this is a useState wrapped object made the different because the object persists and compiles or aggregates past data with current data:
However, the interesting thing is I never actually ended up calling setFormData. I simply modified formData directly just as I did with the other. The difference it seems was just the wrapper, and it let me do it without complaint.
const handleClick = (e: React.MouseEvent<HTMLElement>, p: ViewProps, data: string|number) =>
{
let qn = p.question.question // => question-number
console.log(qn, data)
p.formProps.formState[qn] = data // identical ...
p.formProps.formData[qn] = data // ... operations
console.log('form state', p.formProps.formState)
console.log('form data', p.formProps.formData)
}
A few things are surprising to me. Even using const for the object formState (implying a constant reference to a location in memory, that I think would stay in the same blockscope of the parent), it resets or flushes out or overwrites the data somehow [can't log memory address in js], but the useState-wrapped formData does not flush. It must be held by react in a true single memory location, a true singleton in a memory bank somewhere, or?
Lastly, even though I was using a useState wrapped object, I didn't need to change it with setFormData. Is this a 'hack' or is this something they build in for use cases like this or is this something they just can't avoid even if they want to? I can see how modifying state directly would not work if you're making a component that needs to re-render. How else would React know if control doesn't pass to one of React's core functions? But if you don't plan to use it for rendering, and you aren't going to ever call setState, would it make a difference from a theory POV? The same button they click will send them to my next page anyway. I do see from reading the docs that if you do call setState later, it could wipe your data, and these two things are the vast majority of use cases. I was mostly surprised it worked so smoothly when the direct modification of a non-state object didn't.
What are your thoughts? Is my thinking correct? I don't mean to over-state the obvious. I'm actually at the edge of my knowledge on these subjects and this is how I push that edge.
Consider an edit box. User types some text. There is a key-press handler attached, which does something like:
setState({echo: this.state.echo + event.char})
Is it possible that in initial state {echo: “”}, the user types “a” then “b”, React calls onKeyPress(“a”), which calls setState({echo: “a”}), and then - before setState is actually applied by React - React calls onKeyPress(“b”)?
You see a problem with that, right? OnKeyPress(“b”) will see the same old state {echo: “”} as OnKeyPress(“a”) did, and will issuesetState({echo: “”+“b”}), which is setState({echo: “b”}) - while we obviously wanted {echo: “ab”}
My tests show me that the second event handler does not get called before first setState is completely processed. But a few tests may lie, unlike some documented contract.
I realize that this problem has solutions, like using form setState( state => …)
But I still want to know: is this situation possible or not. Maybe synthetic event handlers are guaranteed to only be executed when all pending setState processing (caused by previous handler) is done? If so, where can I read some proof of that? A specific location in some manual, or qualified answer posted on the Web… Or, maybe, it’s the opposite that is true - that no such guarantees exist (however if that’s the case, “simple” form of setState is sure to cause bugs and they should not allow this form at all).
setState will always be in order, but the argument you give it can rely on state that is from the past. setState calls (such as from your two key presses) can be merged and called on a next cycle.
That's the reason the solution you mention in point two exists and is the correct way to solve this. The way you describe in your question, simply concatting the state value and the pressed key, is exactly what the docs advise against.
I would suggest you leverage the component lifecycle methods componentDidUpdate and componentWillUpdate. In between these methods, there will be a render cycle. This allows you to respond to things that were changed.
Instead of trying to append the current keypress character to the existing string at the time of capturing the key, try appending the character in componentDidUpdate().
In that case, you would just store the current keypress in state (currKey, for example). Then in componentDidUpdate(), you can add something like this:
componentDidUpdate(prevProps, prevState) {
const { currKey, echo } = this.state;
if (currKey !== '') {
this.setState({
echo: echo + currKey, // append the current key to echo
currKey: '' // reset the current key
});
}
}
The setState here will trigger another update round, however in that round currKey will be empty, which means the logic will not execute again until another key is pressed.
Since it's the second time I get no clear answer to similar question, I can only deduce there is at least a theoretical possibility of the mentioned scenario. Then we better follow the documentation in all cases and only infer next state from previous one with a function-based setState.
I have an application which receives messages from a server every 1 second and via socket.io these messages are broadcast to a react component.
My react component is using a flux style architecture, so it calls an action when a message is received which adds (or updates) a record in a collection in a store.
My component monitors changes on the store and then updates the UI when it changes. In this case, it draws a marker on a map.
The problem is that due to the frequency of the updates my component redraws all markers every second, which I don't want.
I'm looking for an approach to allow my map component to respond to changes in a collection in a store, but not have the state update every second.
I thought I could have a collection for the raw data, and then only update the networkGPS collection selectively in my store, but the component seems to change based on any property in the store as they all seem to be part of its state.
In summary I'm looking to:
Collect data every 1 second in raw form and add this data to a store.
Bind a component to a collection in this store which I update only when changes require a UI redraw.
What I think I need to do:
Either:
Avoid putting the raw data into the state of the store (at the moment I'm unsure how to declare a collection which is not part of the state)
OR
Make my component more clever as to when it redraws, so it doesn't try to redraw on each state change.
I'm not sure which is the most appropriate solution for the problem, any info would be gratefully receieved.
Sorry if the question is a little unclear, I'm new to react / flux. Please do ask for any clarifications.
EDIT
At the moment I am getting round this issue by storing an array of the items (map markers in this example) I want my component to display in the component itself :(
When the state changes and render is called, I intercept the state properties, decide if anything needs to change in the collection of markers and only amend them if needed. If no changes are needed, the collection in the component stays the same and no redraw is performed.
This is not ideal, I feel like I should be able to allow my component to respond to only specific state changes but I've not found it yet, still looking for a better answer.
There are a couple of ways to approach this:
1) Use shouldComponentUpdate to compare old props and new props, re-rendering only the new marker states.
2) Switch to a cursor-based state tree that automatically triggers renders only for views that are bound to the specific parts of the state tree that have changed. I believe Baobab is one such library. See also Om (written in ClojureScript) and probably a number of other Flux implementations.
Given the impact of #2 my vote would be to go the route of #1.
I am not quite sure what's the problem that you face with updates every second.
However, the one place where you can queue the updates is between the socket.io and calling the action.
Instead of calling the action every second, you could queue the updates before sending it across to react.