Home > front end >  Is there a way to make setState synchronous in functional component?
Is there a way to make setState synchronous in functional component?

Time:03-04

I want to show notification message after 4 seconds when some state is invalid. But if during that 4 seconds it has changed and is valid now - I want to put condition in setTimeout that would check it. But the problem is that it still uses the first state value, not the changed one. One of my assumptions to fix it was making setState in the line before synchronous, but don't know how. Maybe any other ways to fix it?

useEffect(async () => {
      try {
        const snippetIndexResponse = await getSnippetIndex(
            //some params
        );
        if (snippetIndexResponse !== -1) {
          setSnippetIndex(snippetIndexResponse);
        } else {
          setSnippetIndex(null)
          setTimeout(() => {
            console.log(snippetIndex) <-- it logs only first state, instead wanted null 
            if(!snippetIndex) {
              openNotificationWithIcon(
                  "error",
                  "Invalid snippet selection",
                  "Snippet slice shouldn't tear code blocks. Please, try again."
              );
            }
          }, 4000)
        }
      } catch (err) {
        setSnippetIndex(null);
        openNotificationWithIcon("error", err.name, err.message);
      }
  }, [beginRow, endRow]);

CodePudding user response:

I think you could make the notification UI a component and pass the state in as a parameter. If the state changes the component will be destroyed and recreated. And you can add the 4 second timer in useEffect() as well as cancel it in the 'return' of useEffect. If the timer fires, update some visibility flag.

Live typing this - so may contain some syntax errors...

cost myAlert = (isLoading) => {
  const [isVisible, setIsVisible] = setIsVisible(false)

  useEffect(()=>{
    const timerId = setTimer, setIsVisible(true) when it goes off
    return ()=>{cancelTimer(timerId)}
  }, [isLoading, setIsVisible])

  if (!isLoading && !isVisible) return null

  if (isVisible) return (
    <> 
      // your ui
    </>
  )
}

You may want to useCallback on setTimer so it won't cause useEffect to fire if isLoading changes -- but I think this should get you close to the solution.

CodePudding user response:

First You can not call useEffect callback as async method.

Second for your purpose you can act as below:

let timeoutId = null;

useEffect(() => {
  (async () => {
  if (timeoutId) {
    clearTimeout(timeoutId);
  }
try {
        const snippetIndexResponse = await getSnippetIndex(
            //some params
        );
        if (snippetIndexResponse !== -1) {
          setSnippetIndex(snippetIndexResponse);
        } else {
          setSnippetIndex(null)
          timeoutId = setTimeout(() => {
            console.log(snippetIndex) <-- it logs only first state, instead wanted null 
            if(!snippetIndex) {
              openNotificationWithIcon(
                  "error",
                  "Invalid snippet selection",
                  "Snippet slice shouldn't tear code blocks. Please, try again."
              );
            }
          }, 4000)
        }
      } catch (err) {
        setSnippetIndex(null);
        openNotificationWithIcon("error", err.name, err.message);
      }
  })();
      
  }, [beginRow, endRow]);
  • Related