React event processing - reactjs

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.

Related

What is the useState alternative for fast parallel, async state updates?

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));
};

Modifying state directly in react -- why is it working?

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.

Automatically calling action AFTER redux state is loaded

I have a app in which users can build a "Quote". So far I've been adhering to "idiomatic" redux (or something close to it) and it's been working out well.
However, I'm struggling to deal with a fairly simple scenario:
When the page is first opened, I fire an async LOAD event which retrieves info from the server needed to build the quote (products, inventory, previously saved line items, etc).
I need some way to be able to automatically add a specific line item(s) to the quote first it's first opened.
I've added a defaultLineItems property to my LOAD payload, but to fire the addLineItem(product, inventory, options) action, I need data from the productReducer, inventoryReducer, optionsReducer. The lineItemReducer could look at these defaultLineItems and try to set it's state appropriately, but that would require having to rewrite a lot of BL typically handled by the actions using data aggregated from reducer memorized "selectors" (EG: defaulting price, quantity, currency translation, etc)
I can think of a couple ways to achieve this, but they all seem somewhat hack-ish (IE storing a flag in the reducer that says I need to fire an action and then running it when my root component props update). It seems like a react component should not be responsible for this type thing.
What is the proper way to do something like this?
Seems there are a couple different ways this can be accomplished but for me the most balanced approach between simplicity and design was to use store.subscribe in conjunction with a reducer to track the last action(s).
At it's simplest, this would look something like this.
store.subscribe(function() {
let state = store.getState();
if(state.lastAction.type === ActionKeys.LOAD){
console.log('load action fired!');
}
})
Please be aware that firing an action from store.subscribe will cause recursion so you need to be selective.

Reactjs, strange behavior on checking state value (again)

Ok, there is something I don't understand again:
onDateChange(child, performUpdate){
console.log("child", child, child.state);
}
As you can see, from the debugeur, the state has some value. But when I try to access them, they act like if they where not initialized. Like if the setState wasn't an atomic operation after all...
Actually I don't have any idea as it seems counter intuitive.
Can someone help me to get my data from the callback. Every operation in js/reactjs act differently from what it is expected.
Is that because the 'state' property is in another field and duplicated? I can't find any logical reason for this strange behavior.
edit:
So it appear that setState is asynchronuous. And worst part, non atomic operation. This is kind of strange they don't speak of that in the documentation (instead they said to not use state directly but by method call. Why? Because they do the change in another thread without telling the client!!!).
So this is how you have concurrency problem like I had.
Is there a clean approach for this kind of problem? Like a callback on setState method when all the operation are done (some people use delay or inline function, which absolutly don't correct the problem ).
To resolve my issue, I had to use a proxy pattern where there is the datastructure independantly from the view who display it, and act as if it was the same object. So that the controller don't have to deal with all this 'background' threaded operation mess. (Note: the datastructure is not a model, but a readonly object).

Inserting text into input in descendant component with React

I'm diving into react for the first time but I'm having some difficulty with a particular requirement. I have the following component structure:
Survey
--QuickTextOptions
----QuickTextOption
--Question
----Answer
------TextAnswer
As you probably can tell, I'm working on a survey application. A survey author would create a question, then answers (i.e. sub questions or options). There will be a number of different answer types including checkbox and text.
Also at the top of the survey, above the questions, are a list of "quick text" options. When one of these is selected, some text is appended to the value field of the appropriate TextAnswer's input field. A user can select as many of these as they'd like.
This is trivial to do with just javascript, but I can't figure out a good way to do this with React without adding refs to components down the chain of the survey.
If I pass it in as a prop, have AnswerText as a controlled component, and set value={this.state.value + this.props.quickText}. It just re-inserts the text on the handleChange event of AnswerText.
I'm considering using refs and calling child functions from parents but refs seem to be discouraged and I'll have to do this for Question, Answer, and AnswerText in order to pass it down the chain which seems a bit too much.
Is there any other way to fire an event to a descendant down the chain with some data? It's a one time addition so props seem to not work well for this.
Do you use something like FLUX and global state? In my practice, usually you build such app working around global state which stores your input data. This way you can edit it transparently from different places according to action events (the only ones which can modify that input value, inputs themselves are not doing that).
So I see that it should look like this:
You pass references to global state object deep into your components and to your form's input.
When you start typing each new character triggers event to modify global state value for this input. You're getting updated value instantly in your component's props and component renders newly entered character.
When you want to modify input from some other component (by pressing button) you do almost the same. You click on button and it triggers event saying that global state value for that input should have "some string" added. Your input component receives callback with new props and renders this text instantly.
By saying event I mean special event which gets some value and inserts it into global state. Rest is done by framework.
Hope this will help you to design your app. The approach I described above is a short description of how Tictail Talk web chat client works now.

Resources