React ignore event until setState finished setting the usestate - javascript

I'm have to match a note to an incoming note detected by the microphone. I'm using a fft to detect the pitch and matches that to the next closest note. It triggers the handleNoteEvent about 10x/s. The problem is since setNote is async the if statement == true multiple times, until setState has finished setting the value which subsequently causes the app to rerender multiple times. How can I wait until setState has finished while using react hooks? (currentNote is use by multiple children)
EDIT: setState with hooks doesn't seem to return a promise or take a callback if I understand the docs correctly
EDIT 2: I think I have to clarify my issue: I somehow need to ignore incoming events after the if becomes true, until setState has finished setting currentNote to a new note object.
function App() {
const [currentNote, setNote] = useState(new Note());
//Event handler that gets the event from the fft tuner multiple times a second
const handleNoteEvent = (fftNote) => {
if (currentNote == fftNote)) {
console.log('match');
nextNote();
}
//The problem here is the nextNote() is fired multiple times since setNote is async. How can I ignore all incoming events while setNote is not finished?
const nextNote = () => {setNote(new Note())};
...
}

You will have to handle what happens after the setNote in another useEffect. Something like :
React.useEffect(() => {
// Triggered only once when your component mounts (= componentDidMount in class component)
}, []);
React.useEffect(() => {
// It is only triggered when not changes.
// Do your things after setting notes here.
// Be careful about looping. If you set note here, you'll need to check the value as you did in your exemple to prevent updating note indefinitely
}, [note]);
You are not limited to one useEffect or one hook in general. Split your tasks with multiple useState and useEffect if needed.

Well what I have now works as it should while implementing the useEffect on note change.
It seems to be quite a dirty solution and I would be grateful if anyone could tell me how to do it more clean:
const [currentNote, setNote] = useState(new Note());
var waiting = false;
const handleNoteEvent = (receivedNote) => {
if ((currentNote == receivedNote) && !waiting) {
waiting = true;
setNote(new Note());
}
};
useEffect(() => {
waiting = false;
}, [currentNote]);

Related

Avoiding race conditions with react hooks and native callbacks

I'm trying to write some inter-frame-comunication hook and I'm not sure that the implementation is correct. Unfortunately, the react lifecycle topic seems very complex (example) and I couldn't find a definite answer or recommendation about how to implement it correctly.
Here's my attempt at writing the hook:
const frame = /*...*/;
let messageId = 0;
function usePostMessage(
eventName: string,
handler: (success: boolean) => void
) {
const [pendingMessageId, setPendingMessageId] = useState<number>();
const postMessage = useCallback(() => {
frame.postMessage(eventName);
setPendingMessageId(++messageId);
}, [eventName]);
useEvent(
"message",
useCallback(
(message) => {
if (
message.eventName === eventName &&
message.messageId === pendingMessageId
) {
handler(message.success);
setPendingMessageId(undefined);
}
},
[eventName, handler, pendingMessageId]
)
);
return { postMessage, pendingMessageId };
}
(I'm using useEvent)
Usage:
const { postMessage, pendingMessageId } = usePostMessage(
"eventName",
(success) => {
console.log("eventName", success ? "succeeded" : "failed");
}
);
if (pendingMessageId !== undefined) {
return <div>Pending...</div>;
}
return <button onclick={postMessage}>Click me</button>;
As you can see, I tried to implement a way to post a message and get a response from a frame. I also tried to avoid pitfalls such as getting unrelated responses by keeping a message counter.
It works, but I'm afraid that the "message" event might arrive before the setPendingMessageId state is updated. Is that possible? Are there any guidelines or best practices for implementing this correctly? Thanks.
Update the setPendingMessageId inside the useEffect hook
useEffect(() => {
setPendingMessageId(++messageId);
}, [postMessage])
state update is applied after the postMessage function has been called, avoiding the race condition.
I'm afraid that the "message" event might arrive before the setPendingMessageId state is updated. Is that possible?
No. If a state setter is called inside a React function (such as an onclick prop, as in your code), React will re-render a component after that React handler finishes running its code. JavaScript is single-threaded; once setPendingMessageId(++messageId); is called, the click handler will end, and then a re-render will occur. There's no chance of any other code running before then. The receipt of the message goes through a non-React API (the message listener on the window), so React doesn't try to integrate it into the rerendering flow.
That said, although your code will work, to avoid having to worry about this, some might prefer to reference the stateful values as they are when the message is posted rather than put the logic in a separate hook, which could be less reliable if the state gets out of sync for some other reason. So instead of useEvent, you could consider something along the lines of
const postMessage = useCallback(() => {
frame.postMessage(eventName);
setPendingMessageId(++messageId);
// Save a reference to the current value
// just in case it changes before the response
const thisMessageId = messageId;
const handler = ({ data }) => {
if (data.eventName === eventName && data.messageId === thisMessageId) {
handler(data);
window.removeEventListener('message', handler);
}
};
window.addEventListener('message', handler);
}, [eventName]);
Having a messageId outside of React is a little bit smelly. It'd be nice if you could integrate it into state somehow (perhaps in an ancestor component) and then add it to the dependency array for postMessage.

React setInterval function keeps running in the background after rerender

I am using setInterval inside useEffect with an empty dependency array inside one of my react components.
There is true/false useState which controls the display of that component.
When the state is false, the component is hidden and when it's true the component is shown.
Something like this:
const [state, setState] = useState(false)
// in jsx render section
return (
<div>
{state? <component/> : '' }
</div>
)
When the component loads for the first time the setInterval runs only one time which is what it supposes to do.
If the state goes to false then back to true, the component is removed from the UI and is then displayed back again. However, what happens here is now I have two setInterval functions running in the background, and the first one doesn't shutdown.
The number of the setInterval functions keeps increasing with each time that component re-render.
I don't understand how React works in this situation.
I need to know how it works (i.e. why won't the function shutdown, why are the number of functions increasing) and how to fix it.
This is the structure of React useEffect.React performs the cleanup when the component unmounts.
useEffect(() => {
//effect
return () => {
//cleanup runs on unmount
}
}, [])
The cleanup function should have clearInterval() which will basically removes setInterval or stops it when component unmounts. See the practical code below:
let intervalid;
useEffect(
() => {
intervalid = setInterval(() => {console.log("Iterate");}, 1000));
return function cleanup() {
console.log("cleaning up");
clearInterval(intervalid);
};
},
[]
);
This above code is just for understanding approach. Every effect may return a function that cleans up after it. This lets us keep the logic for adding and removing subscriptions close to each other. # FROM REACT DOCS Reference

React - in a functional component, using hooks, can I execute a piece of code once after one setState() successfully changes state?

setState updates state asynchronously. It's my understanding that, when using a class component, you can do something like this to ensure certain code is executed after one setState updates state:
setState({color: red}, callbackThatExecutesAfterStateIsChanged);
I'm using a functional component & hooks. I'm aware, here, useEffect()'s callback will execute everytime after color state changes and on initial execution.
useEffect(callback, [color]);
How can I replicate similar behaviour as the class component example - that is, to execute a chunk of code once after one setState() successfully changes state and not on initial execution?
If you ask me, there is no safe way to do this with hooks.
The problem is that you both have to read and set an initialized state in order to ignore the first update:
const takeFirstUpdate = (callback, deps) => {
const [initialized, setInitialized] = useState(false);
const [wasTriggered, setWasTriggered] = useState(false);
useEffect(() => {
if (!initialized) {
setInitialized(true);
return;
}
if (wasTriggered) {
return;
}
callback();
setWasTriggered(true);
}, [initialized, wasTriggered]);
};
While the hook looks like it works, it will trigger itself again by calling setInitialized(true) in the beginning, thus also triggering the callback.
You could remove the initialized value from the deps array and the hook would work for now - however this would cause an exhaustive-deps linting error. The hook might break in the future as it is not an "official" usage of the hooks api, e.g. with updates on the concurrent rendering feature that the React team is working on.
The solution below feels hacky. If there's no better alternative, I'm tempted to refactor my component into a class component to make use of the easy way class components allow you to execute code once state has been updated.
Anyway, my current solution is:
The useRef(arg) hook returns an object who's .current property is set to the value of arg. This object persists throughout the React lifecycle. (Docs). With this, we can record how many times the useEffect's callback has executed and use this info to stop code inside the callback from executing on initial execution and for a second time. For example:
initialExecution = useRef(true);
[color, setColor] = useState("red");
useEffect(() => {
setColor("blue");
});
useEffect(() => {
if (initialExecution.current) {
initialExecution.current = false;
return;
}
//code that executes when color is updated.
}, [color]);

Too many re-renders. React Limits the number of renders to prevent an infinite loop - React hooks

I'm writing a block of code to sort some data using React hooks. But I get the above mentions warning/error Below is my code.
const [sort, setSort] = useState(sortedInfo)
if (condition){
// some logic
} else if (columns.find((col) => col.hasOwnProperty("actualSort"))){
const {data, asc} = columns.find((col) => col.hasOwnProperty("actualSort").sorting)
setSort(data);
}
My else case gets called many times which is fine as per the condition. Anything that can be done, so that setSort calls are minimized?
Calling setSort() in render triggers too many renders, use useEffect hook to trigger changes when value changes.
useEffect(() => {
if (condition){
// some logic
} else if (columns.find((col) => col.hasOwnProperty("actualSort"))){
const {data, asc} = columns.find((col) => col.hasOwnProperty("actualSort").sorting)
setSort(data);
}
}, [condition]); // Only re-run the effect if condition changes
setState causes component re-render; and the re-render runs setState again - infinite loop;
In Functional components, must use useEffect to fix this problem;
useEffect(() => {
setSort(data)
}, [condition])
setSort() caused the re-render, and then (if the condition is not changed) useEffect() makes sure setSort(data) is not running again.
setSort will call re-render. So, it call If condition. You should use logic of if condition in function call or useEffect.

I can’t seem to access my updated state from within this function

I have this state:
const [fingerprintingIssues, setFingerprintingIssues] = React.useState([])
I have a function that loops through a bunch of data to validate. If there are errors it adds them to fingerprintingIssues with setFingerprintingIssues. That works fine.
But in this method (abbreviated) I’m checking for fingerprintingIssues inside of the method’s complete and it always returns []. I’ve read this is an issue with closures and stale state, but I can’t seem to see how that applies here.
const validateDocument = data => {
const start = moment()
try {
Papa.LocalChunkSize = 10485
return Papa.parse(data, {
complete: () => {
// Log completion only if it’s been 20 seconds or more
const end = moment()
const duration = moment.duration(end.diff(start))
const seconds = Math.ceil(duration.asSeconds())
if (seconds < MIN_PROCESS_TIME) {
setTimeout(() => {
setUploadStep('tagging')
console.log('fingerprintingIssues', fingerprintingIssues)
}, (MIN_PROCESS_TIME - seconds) * 1000)
} else {
setUploadStep('tagging')
}
},
error: () => {
setDocumentError(true)
setDocumentReady(false)
},
})
…
Any suggestions?
I think indeed it is an issue with closure and stale state. When using hooks, you need to be cautious about how you use callbacks.
Your component function is called many times (one for each render). Each call will receive a particular state. You can sort of think of it like that particular state is associated with that particular render. If you create a callback within your render, that callback will exist inside the closure of the render it was created in. The issue is that if your component is re-rendered, the callback still exists in the closure of the old render, and will not see the new state.
One way around this is to use React.useRef, which you can read more about here. Refs hold a value and can be modified and accessed at any time regardless of closure. Note that modifying the ref does not cause your component to be re-rendered.
const fingerPrintingIssues = React.useRef([])
const validateDocument = data => {
// Perform validation
// Now access (refs are accessed by .current)
console.log(fingerPrintingIssues.current)
}
// Modify:
fingerPrintingIssues.current.push(...)
In case you need to re-render your component when modifying the value, you can use combination of useRef() and useState().

Categories