Alan Chen
Alan Chen

Reputation: 137

Updating useState and AsyncStorage and Rerendering the screen based on given result

I am new to React Native and I am currently trying to fetch credential data from a website and save it into device memory. However I am encountering few issues regarding it!

My flow:

  1. When app is launched the app will check asyncStorage if there are credentials.

  2. If so, we go straight into the app. Otherwise we enter the RootStackScreen(aka login Screen)*** (issue #1)

  3. (LOGIN) A user will enter his/her login into text input which would be saved in a useState.

  4. Using a function 'loginHandle' a fetch request is called using the info from useState and we setCredential with the data returned.** (issue #2)

  5. The app should rerender and see that there is a credential and load into the main screen. *** (issue #3)

However, I'm encountering 2 issues.

  1. *** Even when asyncStorage does contain the credentials when I check appLoad.credentials in the return(...) it returns a null. However, it returns the correct value in my useEffect(). I thought by using useEffect() I am calling the functions during ComponentsDidMount which is before the rendering of the screen. So shouldn't appLoad.credentials contain a string?

  2. On my console.log(credential) It is always one step behind. I.e. I press the login button once. The console log will return null. However, I thought that since the console.log() is after the fetch command that the credentials should be set before the console.log call!

  3. How do I get the app to rerender. I've seen online regarding force rerendering but I heard that it was bad!

App.js


import AsyncStorage from '@react-native-community/async-storage';
...

export default function App() {

  const STORAGE_KEY = '@deviceCredentials';
  const [data, setData] = useState('');

  const appLoad = {
    isLoading: true,
    credentials: null
  };


  //Runs during ComponentsDidMount
  useEffect(() => {
    setTimeout(async () => {

      //In 1000ms try to get credentials
      try {
        appLoad.credentials = await AsyncStorage.getItem(STORAGE_KEY);
        console.log(appLoad.credentials);
      } catch (e) {
        console.log(e);
      }
    }, 1000);
  }, []);


  //Render
  return (
 
      <NavigationContainer>
        {/* If credentials is not null then we can go straight into Main screen, 
            otherwise we go to Login Screen */}
        {appLoad.credentials !== null ? (
          <Drawer.Navigator drawerContent={props => <DrawerContent {...props} />}>
            <Drawer.Screen name="MusteringDrawer" component={MainTabScreen} />
          </Drawer.Navigator>
        )
          :
          <RootStackScreen />
        }
      </NavigationContainer>

  );
};

SignIn.js

...
...
const [data, setData] = useState({
                               username: '',
                               password: '',
                               ...
})
const [credential, setCredential] = useState({ data: null });

const STORAGE_KEY = '@deviceCredentials';

...
...
//Handles user data
const handleValidUser = (val) => {
            setData({
                ...data,
                username: val,
            });
}


const loginHandle = () => {
        fetch(url + data.password + data.username)
            .then(x => x.text())
            .then(y => {
                setCredential({ data: y });
            });
            console.log(credential);
            AsyncStorage.setItem(STORAGE_KEY, credential);
    }



return( 
     <Text>Username</Text>
     <TextInput
        placeholder="Your Username"
        onChangeText={(val) => handleValidUser(val)}
        onEndEditing={(e) => handleValidUser(e.nativeEvent.text)}
      />
     <Text>Password</Text>
     <TextInput
         placeholder="Your Password"
         onChangeText={(val) => handlePasswordChange(val)}
      />


      <View style={styles.button}>
            <TouchableOpacity  onPress={() => { loginHandle(); }}>
                   <Text>Sign In</Text>
            </TouchableOpacity>
      </View>

Upvotes: 3

Views: 2353

Answers (1)

Elias
Elias

Reputation: 4141

Are you aware of the fact that:

const appLoad = {
    isLoading: true,
    credentials: null
};

is re-defined every render? Even more so, if the value gets set in the effect hook, react has no way of knowing that it was changed, thus will not re-render. And if it does re-render (for some entierly different reason, this will not re-render)... well, new variable :D.

What you want is remembering data like this across all renders. You do this, as you surely know, by using the useState hook.

Disclaimer: It is very important that you read the comments I have put in the code, they are crucial for this to work/be "best practice". This code was also not tested, but you should be able to get it to work using some of your sweet developer skills :)

export default function App() {
    // Remove this, seems to be a leftover from testing 
    // const [data, setData] = useState('');
    
    const [isLoading, setIsLoading] = useState(true);
    const [credentials, setCredentials] = useState(null);

    // Check for credentials in the first render
    useEffect(() => {
        // 1. I'm not sure why you add a delay, It will probably only make your app appear sluggish
        // 2. Use effect callbacks should not be made async, see this post 
        // https://stackoverflow.com/questions/53332321/react-hook-warnings-for-async-function-in-useeffect-useeffect-function-must-ret
        async function fetchCredentials() {
            setCredentials(await AsyncStorage.getItem(STORAGE_KEY));
            setIsLoading(false); 
        }

        fetchCredentials();
    }, []);

    // Whatever your render code may be
    return null;
}

It may very well be that I have not solved your problem (I may miss understood you, and most of the time I'm generally a bit stupid). In this case, don't hesitate to get back to me via comments.

Cheers and happy coding!

Upvotes: 3

Related Questions