0

I've been trying for a while now to force useState setter to behave in synchronous manner or to await for a state change before re-rendering. My simplified code:

import { useSelector } from "react-redux";

const SelectedFileContent = () => {
  const selectedFile = useSelector(state => state.fileSelected);
  const [fileContent, setFileContent] = useState(null);
  
  const loadFileContent = async (name) => {
    const f = await fetch(`static/${name}`).then(data => data.json());
    console.log('In loadFileContent: ', fileContent)
    return f;
  }
  
  const fetchFileContent = async () => {
    await loadFileContent(selectedFile).then(data => setFileContent(data))
  }
  
  useEffect(() => {
    fetchFileContent();
    console.log('In useEffect: ', fileContent)
  }, [selectedFile])

    return (
      <div>Selected file content: {fileContent}</div>
    )
}

I've read multiple SO questions and answers but none of that worked for me. What my code does: first, it reads selectedFile name from redux state. When this value changes, useEffect hook is being called (because of selectedFile in dependency array). In useEffect, loadFileContent function is being called, which in turn, loads file content and stores it in the fileContent variable.

My issue is that since useState setter is asynchronous and cannot be awaited, new value is being set too late and I can see with console logs in browser console that it's one state behind all the time - if for example, file 1.json with content {"filename": "1.json"} is being loaded, and then file 2.json - those console logs are printing {"filename": "1.json"} instead of {"filename": "2.json"}, as I'd like to. What I would like to achieve is that on any re-render upon selectedFile changes, correct file content will be stored in fileContent const, without causing re-render when finished (This is just my assumption it happens though, not sure yet how to count it). But when I've added {console.log(fileContent)} in the return statement, it logged file content correctly.

I've tried following things:

  • in fetchFileContent, instead of passing loaded data directly to the setFileContent, split it in two lines
  • added loading/setLoading helper state - setLoading(true) before fetchFileContent and then setLoading(false) plus conditional rendering in the return statement ({fileContent && !loading ? ... : ...})

Also, when I've added additional useEffect hook, it logged file content properly:

useEffect(() => {
  console.log(fileContent);
  }, [fileContent])
PotatoBox
  • 583
  • 2
  • 10
  • 33
  • "...or to await for a state change before re-rendering..." This is how React works. You don't need to do anything. – Code-Apprentice May 24 '22 at 21:04
  • "I've been trying for a while now to force useState setter to behave in synchronous manner" This sounds like you are fighting against React. Why do you need the setter to behave synchronously? – Code-Apprentice May 24 '22 at 21:05
  • `What I would like to achieve is that on any re-render upon selectedFile changes, correct file content will be stored in fileContent` It will `without causing re-render when finished` It can not __not__ rerender. – tkausl May 24 '22 at 21:05
  • I am trying to wrap my head around what you're trying to achieve with this. Do you, by any chance, want to have the latest `fileContent` in your `useEffect` hook? Did I understand it correctly? – Kapobajza May 24 '22 at 21:10
  • Please correct me if I'm wrong here, but my understanding is, that after `useState` setter will resolve, then page will re-render. I would like to "await" for this change to finish, until `fileContent` will have the latest data. So basically you're right @Kapobajza, I'd like to have this state up-to-date in `useEffect`. – PotatoBox May 24 '22 at 21:18
  • You can't. Either write another `useEffect` using `fileContent` as dependency or just do what you want to do in `then`. Or await and get the result out. – tkausl May 24 '22 at 21:20

0 Answers0