React While loop architecture - javascript

Im trying to create a listener for voice using react-speech-recognition. I have it listening and getting the phrase isolated. It comes with some command startListening, stopListening, finalTranscript, interimTranscript, and resetTranscript. I have it working where I can click a button and it listens, click another button and it stops, click reset and it resets. interimTranscript is basically its first guess at the word, and then after a split second once its sure it turns into finalTranscript. Herein lies my problem. The basic flow is that theres a range slider with two values, when moved it sets off the onChange handler which calls this.startListening().
handleChange(event) {
console.log(`eventname: ${event.target.name}`)
console.log(`eventvalue: ${event.target.value}`)
this.setState({
[event.target.name]: event.target.value,
});
this.props.startListening();
this.setState({ listening: "Yes"})
}
then in my render I have this if statement which detects the moment the phrase goes from interimTranscript to finalTranscript
render(){
if(this.props.finalTranscript){
this.sendCommand();
}
which triggers sendCommand(), Here is one of the issues, finalTranscript will keep adding all the different final transcripts into one long string. So I can say "hello" send command prints "hello" then i say "bye" and send command prints "hello bye", BUT sendcommand() is fired once for each word. so I will hit sendCommand() twice. I got around this by clearing out finalTranscript with resetTranscript
sendCommand(){
console.log("send command", this.props.finalTranscript);
this.props.resetTranscript;
}
but then I get this error telling me I shouldn't use this.props.resetTranscript through the render function. I tried creating a while loop in my handle change but kept getting stuck in infinite loops. What would be a good way to go about creating a while loop to start the voice listener, trigger a post upon final transcript, clear out finalTranscript with resetTranscript, and go back to listening?
Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.

As the error stated, render methods should be just pure functions. It's meant to return a view that was derived from your state and props, not induce state changes. Not to mention that might cause an infinite render if you're not careful. If you want to do those effects, such as if this.props.finalTranscript is truthy (or not empty) send a command. Do that in the lifecycle methods like so.
componentDidUpdate(){
if(this.props.finalTranscript){
this.sendCommand()
}
}
I assume you're using class components. if you want to use hooks, look into using useEffect

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:

Concurrent-mode and sync flushing of setState in click handlers

Does the upcoming concurrent-mode break the old guarantee, that setState updates within a click handler are flushed synchronously at the event boundary?
If i have e.g. a button, that should only ever be pressed once, a supposedly working pattern was to "just set the state to disabled in the click handler":
let counter = 0;
const C = () => {
const [disabled, setDisabled] = React.useState(false);
const handler = React.useCallback(
() => { setDisabled(true); counter++; },
[], // setDisabled is guaranteed to never change
);
return (<button onClick={handler} disabled={disabled}>click me</button>);
};
// Assert: `counter` can never be made >1 by clicking the button with one C
This pattern used to be guaranteed to work (at least given that setting the disabled-attribute prevents any further click events, which seems to be the case). The biggest related question i could find discusses this, and also shows a more or less obvious alternative (and easier to prove it works), of using a ref (unlike the answer in the linked question, maybe rather a boolean ref, but same idea, it's always sync).
Side questions: Is this information up-to-date, or did something change? It's more than three years old after all. It mentions "interactive events (such as clicks)", what are the others?
However, in concurrent-mode, rendering can be paused, which i interpret as "the js thread will be released", to allow potential key presses or whatever events to trickle in, and in that phase, additional click events could also happen, before the next render disables the button. Is therefore the way to go to use some kind of ref, or maybe explicitly adding ReactDOM.flushSync?
My current understanding of how concurrent mode works is this:
1 - a re-render starts
2 - hooks are called, they change internal state
3a - re-render is suspended
4a - internal state changes are rolled back
OR
3b - re-render is not suspended
4b - internal state changes are commited
useCallback is a thin wrapper over useMemo and uses "internal state" to save the cached value. (4a) is the key here, and from what I understand your solution is not guaranteed to work anymore.
The useRef (with a boolean flag value) solution has the same issue too because you're not guaranteed that the new value of the ref is actually going to be "commited" when re-rendering is suspended.
The useRef solution where you keep a ref to the DOM button element and directly manipulate the disabled attribute will still work even in concurrent mode. React has no way of blocking you from directly manipulating DOM.
"suspending" means reverting "internal state" + not applying the generated DOM manipulations, does not mean any side effects (like manipulating DOM directly) can be affected.
flushSync will not help either, it simply forces re-renders, does not guarantee that the current render won't be suspended.
As far as I know the setState call was always async, and you never had a way to warranty that the button will be disabled right after the click. Also there is no such thing as concurrency in JS, it has single thread, the problem is that the render can happen latter than you expect so you can receive another click until React made re-render for you.
If you need to fire the logic only once I would advice to use useRef hook and when you need to make sure that we have not clicked the button just check the value.
const isDisabled = useRef(false);
const onClick = () => {
if (!isDisabled.current) {
isDisabled.current = true;
}
}

Using Redux-Form for search filters, how can I "reset" and re-submit the form?

Update: Here is an example Pen
https://codepen.io/anon/pen/vwzGYY?editors=0011
Preface
Based on my research, it seems like I need a completely different approach. Maybe you can suggest one?
Context
I'm using a Redux-Form (technically an older version, but the API's
in question seem really stable. We can burn that bridge when we get there.) to set some "filters" for a sort of search results list.
In particular, since I want the pages to be link-able, I'm also setting the form content in the URL query params, via React-Router, or initially setting it on page load via similar mechanism.
The only field so far is "organization_name", a text field, used to set the query param value, and trigger an API request for /endpoint?name={some_name}.
E.g.,
<Field
name="organization_name"
component="input"
type="text"
placeholder="Organization Name"
value={value}
/>
I've tried several things, but here's a recent shot:
I'm grabbing reset, change, and other things from default props. I'm passing in a handleSubmit as required.
handleSubmit works correctly, to do some state updating, set/push the URL query params with React Router, and then make a new API call/update display of new results! Woot!
What I want / expect
In the long run, I would like a "reset filters" button that sets all filter values back to defaults (e.g., set the "name" value to empty string), and re-submits the form (thus triggering handleSubmit).
What I first tried to implement was a button, as such:
<button
name="reset_filters_button"
type="button"
onClick={resetAndSubmit}
disabled={pristine || submitting}
>
Clear Search
</button>
Where resetAndSubmit is defined on the form container as such:
const resetAndSubmit = event => {
reset();
handleSubmit();
};
What actually happens... (submit takes precedence over dispatched events?)
Using the Chrome dev tools debugger, I can clearly see that the reset method is called, and returns it's dispatch(...)'d event. However, the form and state values are not updated before handleSubmit() runs and submits the form.
I think this might have to do with the submit event taking priority?
I have also tried something janky, like importing change (default prop for the container) and defining the reset button thus:
<button
name="reset_filters_button"
type="button"
onClick={() => {
change('organization_name', '');
methodThatDispatchesSubmitAction();
}}
disabled={pristine || submitting}
>
Clear Search
</button>
Which (if I remove methodThatDispatchesSubmitAction()) works correctly to set the field value back to blank, making the form technically "pristine" again as well.
methodThatDispatchesSubmitAction() (if it's not obvious) is bound on the parent via dispatchToProps, and passed in to the form container, where it uses the "remote submit" suggestion, e.g,
// organization_list_filter == name of the Redux-Form to submit.
dispatch(submit('organization_list_filter'));
TL;DR and final question:
How does one properly reset a form and submit its' default/empty values?
Every time I dispatch or directly call Redux Form 'submit', it ends up submitting the form before clearing values from state, or the UI. I have walked through this with a debugger and it's not skipping my call to reset or change. It's like an async/race issue, but I admit I am out of my league in this particular case for sure.
Am I just Straight Up Doing It Wrong?
It is most definitely a race condition issue (or since we aren't actually dealing with threads, an order of events issue).
The reason using a methodThatDispatchesSubmitAction works when your current example does not, is because a dispatched action has the benefit of reading data directly from the redux store. Your example is not reading from the redux store, it's reading from a property that is passed in. Yes, this property comes from the redux store, but the problem you are seeing is that it hasn't been updated in your component yet.
Bear with me as this next piece is not going to be entirely accurate but it should suffice to explain what you are seeing.
Submit is clicked
-> Reset action is dispatched
-> Reducer receives action and returns updated state
-> Handle submit is fired using values prop (old state data still)
Component is updated with new props from redux state
As you can see, the order of events don't allow for an updated state to be given to the property until our click code has finished running. If you've ever watched a video on the JS Event Loop (I highly recommend it), you'll know that our onClick handle will run in full before any other async operations (or sync operations that come after our click) have a chance to run.
There are good reasons why Components aren't given updated props right away but the primary one is performance. You can see that this order is in fact the problem by wrapping the handleSubmit in an async event that fires immediately (it doesn't actually fire immediately, all other sync/async operations queued before it will finish).
const resetAndSubmit = (event) => {
reset();
setImmediate(() => handleSubmit());
}
This changes the order of events as follows:
Submit is clicked
-> Reset action is dispatched
-> Reducer receives action and returns updated state
-> Handle submit is queued on the event loop (not run yet)
Component is updated with new props from redux state
Event loop reaches queued code and runs is
-> Handle submit is fired using values prop (new state data)
Hopefully, this helps you understand why the problem is occurring. As for solutions to fix it. Obviously, you can queue the handle submit as I've shown above. Another option would the one you've described as using a dispatch to perform the submit. A third option would be to use something a bit heavier like redux-thunk or redux-sagas that tie the resetAndSubmit action into a single dispatch. Although honestly, this is the same as option two, just reduced into a single dispatch. Option four, don't use redux for all your data. Obviously, this fourth option comes with trade-offs but my point being, just because you are using redux in a project doesn't mean every single piece of data needs to be in redux. Though it completely defeats the purpose of redux-forms.
I should also add, you are not alone in being confused by this. When you introduce redux, it messes with how you traditionally think about working with code. Normally you think, I do A then B. But with redux, you do A, wait for A's changes to make it through the system, and then you do B. That's where Sagas or Thunks can be nice. You move more logic to the store to act on the dispatch rather than wait for it to all make its way back down to a component via props.

Async render without any AJAX?

So far every question I can find about async rendering involves an AJAX call.
I've got a text input in my React app, which you can type into to filter a big list of items rendered underneath. Of course, as this list gets bigger, it gets more expensive, so typing into the search box is slow and laggy.
Is there a way to render the list asynchronously from the text input? Or somehow else have it on a separate thread? I don't really want to turn this into a remote AJAX request just because it's too pricy to render - I have all the data I need already.
Basically, my text input's onChange method points at handleChange, then in each item's render() function, it checks the hiddenBySearch method to see if it should be displayed:
handleChange = value => {
this.setState({
searchValue: value
})
}
hiddenBySearch = item => {
if(this.props.data.hiddenBySearch){
return this.props.data.hiddenBySearch(item, this.state.searchValue)
}else{
return false
}
}
There's a little more to it but I'd say it's not relevant.
EDIT: It's not a possible duplicate of this post - I'm specifically asking about offsetting React's rendering. I'm fairly sure it's impossible to put this in a Web Worker.

React function after dom has finished rendering

I have a map of the world and whenever you click on a particular country, it pings and API and gets schools in that country. React then uses leaflet to display all the dots(GEOJSON). After the schools are loaded I have a Dock type thing that will pop up. The problem is that I need to know when the react is done updating. I tried using the react lifecycle methods in the Map.jsx file I have the following code to try to see when the GEOJSON is done rendering:
componentWillUpdate() {
console.log('CWU');
}
componentDidUpdate() {
console.log('CDU');
}
But in the console I get the following printouts:
CWU, CDU, CWU, CDU, CWU, CDU
So both functions run 3 times, for another country both functions run twice. So I cant put the function that brings up the Dock in either componentWillUpdate or componentDidUpdate because I would need to run the function after the 3rd 'CDU' or for the other country after the second 'CDU'. Is there anyway way to know when react has finished rendering ?
I found a way to solve it. I'll try my best to explain it:
I have a state variable called
didUpdate = false
The API changes a particular entry in the props
so in componentWillUpdate I check to see if the next state is different than my current one if so I set didUpdate to false. This makes it so that it doesn't display.
Then in my componentDidUpdate I check to see if previous state is different than my current one if so I then set didUpdate to true. This then makes it so the dock is shown.
Here is some code:
componentWillUpdate(nextProps, nextState) {
if (nextProps.X !== this.props.X) {
this.setState({
didUpdate: false,
})
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.X !==this.props.X) {
this.setState({
didUpdate: true
})
}
}
You can use
componentWillUnmount()
componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().

Categories