othmane-be
othmane-be

Reputation: 102

React Native - How can I listen to AsyncStorage Changes in Real Time (Conditional Screens Rendering) without refreshing the app?

I was trying to build an Authentication system in React Native using :

this is my system flow: I want only to show the Sign screen when (isAuthenticated == false), when it's true I want to show UserUI Screen

        <Provider store={store}>
          <NavigationContainer>
          <Stack.Navigator
          screenOptions={{
            headerShown:false
          }}
          >
            {isAuthenticated == false ? (
               <>
               <Stack.Screen name="Sign" component={Sign} />
               </>
            ) : (
              <>
              <Stack.Screen name="UserUI" component={Tabs} />
              </>
            )}
          </Stack.Navigator>
        </NavigationContainer>
      </Provider>

also the (isAuthenticated) depends on this function :

React.useEffect(()=>{  
  const showStorage = async () => {
    try {
      const userObj = await AsyncStorage.getItem('currentStoredUserState')
      if(userObj != null){
        let jsonObject = JSON.parse(userObj)
        setIsAuthenticated(jsonObject.state)
      }else{
        setIsAuthenticated(false)
      }
      keys()
    }catch(err){
      console.log(err)
    }
 }
  showStorage()
 },[itemsLength])

but the problem is that (isAuthenticated) does not change to true when the user authenticates and I have to refresh the app manually -after AsyncStorage is not empty- to access'UserUI' Screen.

as you can see, I set (itemsLength) as dependecy but it does not work as well :

const [itemsLength,setItemLength] = useState(0)
const keys = async () => {
    let key = await AsyncStorage.getAllKeys()
    setItemLength(key.length)
}

everything above is inside App.js

sign in action :

const setAuthStorage = async (data) => {
    try {
        let authState = { state: true,currentStoredUser_ : data }
        await AsyncStorage.setItem('currentStoredUserState', JSON.stringify(authState))
    } catch (err) {
        console.log(err)
    }
}

export const sign_in = (userSignData) => (dispatch) => {
    auth.signInWithEmailAndPassword(userSignData.email, userSignData.password)
        .then(userCredential => {
            const user = userCredential.user
            db.collection('users').doc(auth.currentUser.uid.toString()).get()
                .then(doc => {
                    if (doc.exists) {
                        setAuthStorage(doc.data())
                        dispatch({
                            type: SIGN_IN,
                            payload: {
                                authenticated: true,
                                currentUser: user,
                            }
                        })
                    }
                })
        }).catch(err => {
            console.log(err)
        })
}

Sign Hanlder in Sign Screen :

const signHandler = () => {
        if (isSignedUp) {
            dispatch(sign_in(userSignData))
        } else {
            dispatch(sign_up(userSignData))
        }
    }


Solved, but not quite perfect, –

thank you guys for your help I really appreciate it, you asked me to change the rendering condition using redux state but I could not access the state outside the , for this mission I had to subscribe to store changes in the App.js Component and when we say subscribe == listening to every state change and this is not good for the app performance :

store.subscribe(()=>{
  /*
    I combined two states: rootReducer (for user details) and postReducer(for posts changes)
  */

// making sure it's rootReducer state
if(store.getState().hasOwnProperty('rootReducer')){
  //when user sign in or sign up rootReducer.authenticated will change from false to true
  
  if(store.getState().rootReducer.authenticated == true){
      // change the rendering condition
      setIsAuthenticated(true)
    }else{
      setIsAuthenticated(false)
    }
  }
})

I also made some changes inside actions to change the state only after storing data

const setAuthStorage = async (data) => {
    try {
        let authState = { state: true,currentStoredUser_ : data }
        // to provide promise chaining this function will store & return a promise
        return await AsyncStorage.setItem('currentStoredUserState', JSON.stringify(authState))
    } catch (err) {
        console.log(err)
    }
}


export const sign_in = (userSignData) => (dispatch) => {
    auth.signInWithEmailAndPassword(userSignData.email, userSignData.password)
        .then(userCredential => {
            const user = userCredential.user
            db.collection('users').doc(auth.currentUser.uid.toString()).get()
                .then(doc => {
                    if (doc.exists) {
                        setAuthStorage(doc.data())
                        .then(()=>{
                            dispatch({
                                type: SIGN_IN,
                                payload: {
                                    authenticated: true,
                                    currentUser: user,
                                }
                            })
                        })
                    }
                })
        }).catch(err => {
            console.log(err)
        })
}

If a found a better solution I will share it with you guys 🤞

Upvotes: 3

Views: 1347

Answers (2)

Muhammed Yasir MT
Muhammed Yasir MT

Reputation: 2014

As per my understanding what you are trying to achieve is,

  1. use login screen when not logged in, use user screen when logged in.
  2. once user do login save data and keep user logged in.

How do it most similar to what you have:

  1. Store user data in redux.(i think you have already done.).
  2. use that redux state to select which set of screens to be available (you have used async storage which on update do not re-render).
  3. persist the user data in redux. Can use redux-persist for it.(to save user info for next time user opens app).

Edit Adding code to make the solution in question better

App.js file

import AppNavigation from './AppNavigation'
...
<Provider store={store}>
  <AppNavigation/>
</Provider>

AppNavigation.js

import {useSelector} from 'react-redux'
...
const AppNavigation =()=>{
  const isAuthenticated = useSelector(state=>state.authenticated===true);
  return(
    <NavigationContainer>
      <Stack.Navigator
      screenOptions={{
        headerShown:false
      }}
      >
        {isAuthenticated == false ? (
           <>
           <Stack.Screen name="Sign" component={Sign} />
           </>
        ) : (
          <>
          <Stack.Screen name="UserUI" component={Tabs} />
          </>
        )}
      </Stack.Navigator>
    </NavigationContainer>
  )
}

Upvotes: 2

Shahar Eliyahu
Shahar Eliyahu

Reputation: 126

You should display the routes with condition on the redux state and then it will automatically change the routes on login.

Upvotes: 2

Related Questions