0
votes

I am relatively new to React Native and would like to set up a very simple authentication flow. The basic logic is working, what I cannot handle is the Login button event that needs to change the state in the top-level App.js:

  1. I have a top-level App.js that renders a simple Stack Navigator with 3 possible screens: SplashScreen (till isLoading state is changed to false), LoginScreen (if isLoggedIn state is false) and LaunchScreen (if isLoggedin state is true)

  2. According to docs, I run the loading function that fetches user auth data from a remote server in the componentDidMount() function in App.js. Whatever should this return, it will set the isLoading state to false and the isLoggedIn to false to true. Let's say it now returns 1 for simplicity and it finishes up the loading. isLoggedIn stays false for the moment, so the user gets to the Login Screen.

App.js

import SplashScreen, {myLoadingFunction} from './SplashScreen.js'
// Other imports

const Stack = createStackNavigator()

export default class App extends React.Component {

  constructor(props) {
    super(props);      
    this.state = {
      isLoggedIn: false,
      isLoading: true
    }      
  }

  componentDidMount() {
    if (myLoadingFunction () == 1) {
      this.setLoadingFinished()
    }
  }

   setLoggedIn = () => (   
    this.setState({ 
      isLoggedIn: true,
    })    
   )

   setLoadingFinished = () => (   
    this.setState({ 
      isLoading: false
    })    
   )

  render() {
    // Show SplashScreen while verifying user token
    if (this.state.isLoading) return <SplashScreen />

    const { contextState } = this.context

    return (
      <MyProvider>
        <NavigationContainer>
              <Stack.Navigator>

                {contextState.isLoggedIn == true ? (
                  // User is signed in
                  <Stack.Screen
                    name="LaunchScreen"
                    component={LaunchScreen}
                  />
                ) : (

                   // No token found, user isn't signed in
                  <Stack.Screen name="LoginScreen" component={LoginScreen} />
                )}

              </Stack.Navigator>
        </NavigationContainer>
      </MyProvider>
    );
  }

}
App.contextType = MyContext

SplashScreen.js

export const myLoadingFunction = () => {
    // fetching some data from server
    console.log("Splash Screen loading/fetching data...")
    return 1
}

export default class SplashScreen extends React.Component {

    render (){
        return (
            <View>
                <Text>Application is being loaded...</Text>
            </View>
        )
    }    
}

Until this is OK, after the App component mounts, I will have a simple logic (myLoadingFunction) to verify if the user has a valid token, then return a value. Currently it changes the isLoading state to false, does not change the isLoggedIn state, so the user is on the Login Screen.

LoginScreen.js

export const myFetchUserToken = () => {
    console.log("Login Screen fetching data...")
    return 1
}

export default class LoginScreen extends React.Component {

    render (){
        return (
            <View>
            <Text>You are currently logged out</Text>
            <Button title="Log In" onPress={()=>{}}/>
            </View>
        )
    }    
}
LoginScreen.contextType = MyContext

On clicking on this Button, I simply want to change the isLoggedIn state to true in App (and now skipping the auth logic for the sake of simplicity). This should trigger the render function in App and show the user the Launch Screen. How can I achieve this?

Things I tried:

  • Passing the setLoggedIn() function as props to SplashScreen, but Stack.Screen does not accept props
  • Exporting setLoggedin() function and import it in SplashScreen, but obviously setLoggedIn() outside App Component fails as there is no setState() function
  • Creating a global state out of isLoggedin with Context API - read further: I can update the state but the App.js does not re-render

MyContext.js

const MyContext = React.createContext({
    name: "defName",
    age: 0,
    setUser: (name) => {},

})


class MyProvider extends React.Component {
    // Context state
    state = {
      name: "stateName",
      age: 20,
      isLoggedIn:false
    }

    setUser = (name) => {
      this.setState({
         name: "changedName"
      })
    }

    setLogInTrue = () => {
      this.setState({
        isLoggedIn: true
      })
    }

    render() {  
      const {children} = this.props
      return (
        <MyContext.Provider
          value={{
            contextState: this.state,
            setUser: this.setUser,
            setLogInTrue: this.setLogInTrue 
          }}
        >
        {children}
        </MyContext.Provider>
      )
    }
  }

export default MyContext
export { MyProvider }

UPDATE

With Expo, I have AppEntry.js where I could wrap the App into MyContext:

AppEntry.js

ReactDOM.render(
    (    
    <MyProvider>
      <App />
    </MyProvider>
    ),
    document.getElementById('root'),
  );

This way I changed the condition this.state.isLoggedIn == true to contextState.isLoggedIn == true which now works. Also added a setLogInTrue() function to MyContext (updated above) which I can call in LoginScreen Button:

Updated LoginScreen.js

export default class LoginScreen extends React.Component {

    render (){
        const { contextState, setLogInTrue } = this.context
        console.log(contextState)
        return (
            <View>
            <Text>You are currently logged out</Text>
            <Button title="Launch Application" onPress={setLogInTrue}/>
            </View>
        )
    }    
}
LoginScreen.contextType = MyContext

If I console.log contextState, it is now changing the isLoggedIn state to true. But the App.js does not re-render (while Login Screen re-renders according to logs). Why?

UPDATE 2

Ok, there was a typo only (in the context state was loggedIn and I changed the state of isLoggedIn instead of loggedIn)

I leave the whole code here if anyone needed it.

1

1 Answers

0
votes

Try to use context api in app.js and set function and state there.

Example:- 1:- write these lines const defaultValue = function to change state const MyContext = React.createContext(defaultValue); 2:- Wrap all components in app.js with ... all components