0

I am using react navigation 5. I want to redirect with Token. But the function works twice and returns null in the first. What is the reason and solution?

export default function Router(){
    const [token, setToken] = useState(null);

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

    const getToken = async () => {
        const token = await AsyncStorage.getItem('token');
        setToken(token);
    };

    console.log("token:") //console
    console.log(token)

    return (
        <NavigationContainer>
            <AppStack.Navigator>
                {
                    token=== null ?
                    //..
                }
            </AppStack.Navigator>
        </NavigationContainer>
    );
}
//output
token:
null
token:
eyJhbGci..

Two rounds at the beginning and showing null in the first round prevents the program from running.

irfan
  • 102
  • 9
  • You can't fix it because the code in the component will not run as you want if there are some async actions, just leave it as it is – Alopwer May 11 '20 at 20:32
  • When the token is set, the code anyway will go further and then will rerender (because of the state change) everything what it needs – Alopwer May 11 '20 at 20:34
  • Thank you for the answer @Alopwer. If it needs to rebuild, why doesn't it redirect when you see that the token is full? Null is acting according to him for seeing. – irfan May 11 '20 at 20:39

2 Answers2

1

In your case, the component is rendered 2 times, because of the useEffect hook. It acts as a componentDidMount lifecycle method and updates the state, after the async function is completed, and here the second rerender occurred.

In general, your implementation is good.

However, you can try separating/moving the authentication login (token fetching) in a separate component, something like that:

export default function Auth(props) {
    const [token, setToken] = useState(null);

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

    const getToken = async () => {
        const token = await AsyncStorage.getItem('token');
        setToken(token);
    };

    return token ? props.children : '<div>Loading ...</div>' 
}

export default function Router(){
    return (
        <NavigationContainer>
            <AppStack.Navigator>
                // Your Routes definition here ...
            </AppStack.Navigator>
        </NavigationContainer>
    );
}

export default function App() {
  return (
      <Auth>
          <Router />
      </Auth>
  )
}
Jordan Enev
  • 16,904
  • 3
  • 42
  • 67
  • Thank you so much. One last thing bro :D How do I access tokens in the router? (this.props.parent.token etc.) – irfan May 11 '20 at 21:26
  • It depends on your architecture. If you're using Redux, you can keep the token in the Store and reading it with `connect`. If not, you can keep the token via React Context API and accessing it everywhere you want in the nested child components. Please refer to this SO questions: https://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children – Jordan Enev May 11 '20 at 21:33
  • @irfan, the most simpler way would be to clone it as here: https://stackoverflow.com/a/35102287/4312466 But if you need the token in a nested child component, i.e. App => Router => NestedComponent, it would be better to check Context (https://stackoverflow.com/a/39401252/4312466) or any other State management solutions. – Jordan Enev May 11 '20 at 21:58
  • Thank you so much for trying @Jordan Enev. But this forwarding process was very complicated and messed up. While React navigation is very easy in v4 without any help, I still haven't found a solution in v5 despite your explanation. I'm using Mobx. I want to steer in the simplest way just :( – irfan May 11 '20 at 22:12
  • I don't have any experience with MobX, but looking in its documentation in the Auth component `getToken` you should update your MobX store with the token value. Later, where you need the token, let's say the Router, you should observe the MobX Store for the token value. – Jordan Enev May 11 '20 at 22:25
  • Yes but not working. Because i using first time RNF(function component). Ever so far using RNC(statefull component). – irfan May 11 '20 at 22:33
  • Please create a simple jsfiddle/codepen exampe - and will take a look of it. – Jordan Enev May 11 '20 at 22:38
  • Here's an example of MobX + stateless/functional component: https://mobx.js.org/refguide/observer-component.html#storing-observables-in-local-component-state – Jordan Enev May 11 '20 at 22:40
0

Setting state is async in react, that's why first time when it is running it gives null because after running everything in the script, it resolve the async call. That's why second time you are getting the token.

See the execution order:

const [token, setToken] = useState(null);
useEffect(() => {         -->(2), after component mounts
  getToken();
 }, []);

 const getToken = async () => {        -->(3)
   const token = await AsyncStorage.getItem('token'); -->(6)
   setToken(token);                    -->(7)
  };

 console.log("token:") //console       -->(4)
 console.log(token)                    -->(5)

 return (                   --> this will execute first (1)
   <NavigationContainer>
     <AppStack.Navigator>
      {
       token=== null ?
                    //..
                }
      </AppStack.Navigator>
    </NavigationContainer>             
  );
  • Thank you for the answer @Rohit Singh. What would you suggest for the solution? – irfan May 11 '20 at 20:42
  • Better generate the Token before and set it to the `localStorage` and when call `Router` use that Token here – Rohit Singh May 11 '20 at 20:48
  • You can try creating a Wrapper/Parent component, that will be responsible for token/authentication only, and when the token is fetched, then you will render the child components too. – Jordan Enev May 11 '20 at 20:52
  • Thank you for the answer @Jordan Enev. Is it possible to show a sample code? – irfan May 11 '20 at 21:01
  • @irfan, yes of course. I've created a sample for you. If you have any questions, feel free to ask above the answer! If helps you, please consider upvoting/accepting it. – Jordan Enev May 11 '20 at 21:14