useState not updating when getting values from Firestore

I am creating a Update Post component in React. The component gets the existing post data from Firestore and uses a form to update those fields.

EDIT: Short Version:

  1. I want to get the post data from Firestore – Working
  2. I want to set the state with that data – Not working
  3. I want to update that state from the form, if the input is changed – Not working

Long version:

I want to initially set the state of all the fields in the form to the existing values in the post, and then update this state if or when the input field changes (updates).

The problem is:

  1. The useEffect is not updating the state. It should run once and set all the existing field values of the form, in the event that not all fields are updated, so if a field is not updated, it remains as it was. However, the console.log in the useEffect is just returning nothing, while the placeholder values in the form are fine. This is causing the updatePost() function to fail.

export default function EditPost() {

// establish state for the form
const [postValue, setPostValue] = useState('')
const [postValueTwo, setPostValueTwo] = useState('')

// state for the query data below
const [postData, setPostData] = useState([])

async function getPost() {
    ...
    setPostData(doc.data());
}

// empty array = SHOULD run once 'after mount'
useEffect(() => {
    // run the query
    getPost();
    // set the state
    setPostValue(postData.someValue)
    setPostValueTwo(postData.anotherValue)
    console.log(postValue) // this returns undefined
},[])

// send data to Firestore
async function updatePost(e) {
    e.preventDefault() 
    return await useFireStore
        .collection('posts')
        ...
        .update({
            postValue: postValue,
            postValueTwo: postValueTwo
        });
}
return (
    <>
        <div>Post Value = {postValue}</div>
        <form onSubmit={editPost}>
            <input 
                type="text"
                placeholder={postData.someValue}
                onChange={(e) => setPostValue(e.target.value)}
            />
            <input 
                type="text"
                placeholder={postData.anotherValue}
                onChange={(e) => setPostValueTwo(e.target.value)}
            />
        </form>
    </>
)
}

If you see the div tag before the form, the postValue variable is empty. I know the useEffect runs after the DOM is rendered, which would explain this being empty, but I don’t understand why the console.log in the useeffect is empty too.

EDIT => after typing this out I realised in the useEffect the getPost function is of course asynchronous and therefore probably not returning a value before the state is set, so I moved the state setting code to the getPost function to run after the query has been successful, as per below, but the problem is still the same.

async function getPost() {
    ...
    if (!doc.exists) {
        console.log('No such document!');
    } else {
        setPostData(doc.data());

        setPostValue(postData.someValue)
        setPostValueTwo(postData.anotherValue)
        console.log("url is: ",url)
    }
}

useEffect(() => {
    getPost();
},[])

Sorry for the long-winded question, it’s so hard to explain sometimes. Many thanks for any help on this. Cheers, Matt

Answer

postData is a local const. It will never change, and that’s not what setPostData is trying to do. Calling setPostData just tells react to render the component again. On that new render a new local const will be created with the new value, but there’s no way for your code that’s running now to access that future value in a different closure.

If you need to use the new value, then you’ll need to do so with your own local variable. In your second version where it’s all done in the getPost function, that will look like:

async function getPost() {
  ...
  if (!doc.exists) {
    console.log('No such document!');
  } else {
    const newData = doc.data();
    setPostData(newData);
    setPostValue(newData.someValue);
    setpostValuetwo(newData.anotherValue);
  }
}

If you want it to be closer to your first version, then you’ll need to return the value and use it in the useEffect:

async function getPost() {
  ...
  if (!doc.exists) {
    console.log('No such document!');
  } else {
    const newData = doc.data();
    setPostData(newData);
    return newData;
  }
}

// ...

useEffect(() => {
  (async () => {
    const newData = await getPost();
    setPostValue(newData.someValue)
    setPostValueTwo(newData.anotherValue)
  })();
},[])