Search code examples
firebasereact-nativegoogle-cloud-firestoreexpozustand

Firestore updateDoc() uses old Zustand state


I am using Expo / React Native and Zustand for state management. In a Profile Edit View.js I show user data and give the users the possibility to edit their profile. I am running the app on iOS simulator but also on physical device with expo go.

The user information in Zustand is set in App.js with useEffect() and onAuthStateChanged().

import { create } from 'zustand'

const useUserStore = create((set) => ({
    user: null,
    username: '',
    setUserName: (username) => set({ username }),
    clearUserName: () => set({ username: '' }),
    setUser: (user) => set({ user }),
    clearUser: () => set({ user: null }),
  }))

export default useUserStore;

That's called in the View.js

const { user, username, setUserName } = useUserStore();

On the View.js I have a simple Input field showing the current username.

<Input 
  style={styles.input}
  label='Username'
  autoCorrect={false}
  defaultValue={username}
  value={username}
  onChangeText={(text) => setUserName(text)}
  inputContainerStyle={{borderBottomWidth:0}}
/>

In View.js I use useEffect() to update the header bar with the save button I need:

useEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Button 
          onPress={handleSaveData} 
          title='Save'
        />
      ),
    });
  }, [navigation]);

Here is the update function called:

const handleSaveData = async () => {
    const userDocRef = doc(db, "user", user.uid);
    try{
    await updateDoc(userDocRef, { username: username }); 
    } catch (error) { }
  }

When I now enter a new text in the Input, the state will be updated as I can see the change already an all pages using the Zustand state. So across the app (also on the same page using a control text) the username has been updated.

Now when I click on the Save Button it does not update Firestore with the new value. I have to go one screen back and enter the View.js again and then press the button (without changing the Input value) and it will correctly update firestore. When I change the value again, firestore gets the previous state value.

I can just not figure out why the update function is not using the new Zustand state after changing the Input value. It seems a timing issue however all Views showing the state are already updated. Only in the function it uses the old value.

How do I maybe use a callback to re-initiate Zustand on the active View.js before executing the function so it uses the new state?

I previously was using native useState but encountered the same issue so switched to Zustand (as I also need the values in other screens).

EDIT: In the meantime I changed the code in func handleSaveData to useCallback depending on the state. Does not change the issue.

I also found out that it has something to do with the navigation headerRight button. If I include a normal button in the main return view of the screen it works perfectly. Can someone explain what is the different between the function call of the navigation button using the old state and the view button using the actual state.


Solution

  • I fixed the problem. For those may encountering the same issue:

    You need to pass the state to the useEffect() button draw so it gets rendered with the change of the state.

    useEffect(() => {
        navigation.setOptions({
          headerRight: () => (
            <Button 
              onPress={handleSaveData} 
              title='Save'
            />
          ),
        });
      }, [navigation, username]);