简体   繁体   中英

How to use React Native AsyncStorage with Redux?

I have made login and logout actions and userReducer. How can I integrate AsyncStorage with Redux? I am using Redux Thunk as a middleware.

I am able to implement login and logout using internal state variable but I am not able to understand how to break it down into action and reducer as well as make use of AsyncStorage for storing accessToken .

Original Code:

_onLogin = () => {
    auth0.webAuth
      .authorize({
        scope: 'openid profile',
        audience: 'https://' + credentials.domain + '/userinfo'
      })
      .then(credentials => {
        this.setState({ accessToken: credentials.accessToken });
      })
      .catch(error => console.log(error));
  };

  _onLogout = () => {
    if (Platform.OS === 'android') {
      this.setState({ accessToken: null });
    } else {
      auth0.webAuth
        .clearSession({})
        .then(success => {
          this.setState({ accessToken: null });
        })
        .catch(error => console.log(error));
    }
  };

loginAction.js:

   import { LOGIN_USER } from './types';
import Auth0 from 'react-native-auth0';

var credentials = require('./auth0-credentials');
const auth0 = new Auth0(credentials);

export const loginUser = () => dispatch => {
    auth0.webAuth
    .authorize({
      scope: 'openid profile',
      audience: 'https://' + credentials.domain + '/userinfo'
    })
    .then(credentials =>
        dispatch({
            type: LOGIN_USER,
            payload: credentials.accessToken
        })
    )
    .catch(error => console.log(error));
}

logoutAction.js:

       import { LOGOUT_USER } from './types';
import Auth0 from 'react-native-auth0';

var credentials = require('./auth0-credentials');
const auth0 = new Auth0(credentials);

export const logoutUser = () => dispatch => {

        auth0.webAuth
          .clearSession({})
          .then(success => 
                dispatch({
                    type: LOGOUT_USER,
                    payload: null
                })
          )
          .catch(error => console.log(error));
}

userReducer.js:

  import { LOGIN_USER, LOGOUT_USER } from '../actions/types';

const initialState = {
    accessToken: null
}

export default function (state = initialState, action) {
    switch (action.type) {

        case LOGIN_USER:

            _storeData = async () => {
                try {
                    await AsyncStorage.setItem('accessToken', action.payload);
                } catch (error) {
                    console.log(error)
                }
            }

            return {
               ...state,
               accessToken:action.payload
            };

        case LOGOUT_USER:

            _removeData = async (accessToken) => {
                try {
                    await AsyncStorage.removeItem(accessToken);
                } catch (error) {
                    console.log(error)
                }
            }    

            return {
                ...state,
                accessToken:action.payload
            };

        default:
            return state;
    }
}

I am new to Redux so I tried converting original code into actions and reducers but I am not sure whether I have implemented AsyncStorage in userReducer.js correctly?

To persist redux state I recommend you redux-persist .

Installation:

npm i -S redux-persist

Usage:

First, configure redux store

// configureStore.js

import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web and AsyncStorage for react-native

import rootReducer from './reducers'

const persistConfig = {
  key: 'root',
  storage,
}

const persistedReducer = persistReducer(persistConfig, rootReducer)

export default () => {
  let store = createStore(persistedReducer)
  let persistor = persistStore(store)
  return { store, persistor }
}

Then, wrap your root component with PersistGate

import { PersistGate } from 'redux-persist/integration/react'

// ... normal setup, create store and persistor, import components etc.

const App = () => {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <RootComponent />
      </PersistGate>
    </Provider>
  );
};

You can conveniently use AsyncStorage alone OR redux to manage authentication state. Depends on which you are comfortable with. I will give you an example of both.

For AsyncStorage: Assuming you have authentication keys that is valid for 2 weeks only. You can take note when your user logs in and save the time. eg:

//LoginScreen
import { onSignIn } from '../actions/auth'; //I will describe the onSignInMethod below
import axios from 'axios'; //lets use axios. You may use fetch too.


export default class LoginScreen extends Component {


    //your code: state, static etc
    loginMethod = () => {
        const url = yourauthUrl;
        const payload = {
            email: this.state.email,
            password: this.state.password
        };
        axios.post(url, payload)
        .then((response) => {
            if (response.status == 200) {
                const dateOfLastLogin = new Date().getTime().toString(); //take note of the time the user logs in.
                AsyncStorage.setItem('dateOfLastLogin', dateOfLastLogin);
            }
        })
        .then(() => { 
            onSignIn() //onSignIn handles your sign in. See below.
            .then(() => this.props.navigation.navigate('AfterSignInPage'));
            })
            .catch(() => { // your callback if onSignIn Fails
            });
        })
        .catch((error) => { //your callback if axios fails
        });
    }

}

In ../actions/auth.js

import { AsyncStorage } from 'react-native';

export const onSignIn = () => AsyncStorage.setItem('auth_key', 'true');
//in LoginScreen we called this to set that a user has successfully logged in
//why is true a string? -- Because Asyncstorage stores only strings

export const onSignOut = () => AsyncStorage.multiRemove(['auth_key', 'dateOfLastLogin']);

//now lets create a method that checks if the user is logged in anytime
export const isSignedIn = () => {
    return new Promise((resolve, reject) => {
        AsyncStorage.multiGet(['auth_key', 'dateOfLastLogin'])
        .then((res) => {
            const userKey = res[0][1];
            const lastLoginDate = parseInt(res[1][1]);
            const today = new Date().getTime();
            const daysElapsed = Math.round(
                (today - lastLoginDate) / 86400000
                );
            if (userKey !== null && (daysElapsed < 14)) {
                resolve(true);
            } else {
                resolve(false);
            }
        })
        .catch((err) => reject(err));
    });
};

now we can import { isSignedIn } from '../actions/auth'; from any of our components and use it like this:

isSignedIn()
    .then((res) => {
        if (res) { 
            // user is properly logged in and the login keys are valid and less than 14 days 
        }
    })

////////////////////////////////////////////////////////////////////////////

If you want to use redux

Handling login in redux

In your types.js

//types.js
export const LOGGED_IN = 'LOGGED_IN';

In your redux actions

//loginActions.js
import {
    LOGGED_IN,
} from './types';

export function login() {
    let dateOfLastLogin = null;
    let isLoggedIn = 'false';
    AsyncStorage.multiGet(['auth_key', 'dateOfLastLogin'])
    .then((res) => {
        isLoggedIn = res[0][1];
        dateOfLastLogin = parseInt(res[1][1]);
    }); //note this works asynchronously so, this may not be a good approach
    return {
        type: LOGGED_IN,
        isLoggedIn, 
        dateOfLastLogin
    };
}

In your loginReducer

//LoginReducer.js
import {
    LOGGED_IN
} from '../actions/types';


const initialState = {
    userIsLoggedIn: false
};

export function loginReducer(state=initialState, action) {
    switch (action.type) {

        case LOGGED_IN:

            const userKey = action.isLoggedIn;
            const lastLoginDate = action.dateOfLastLogin;
            const today = new Date().getTime();
            const daysElapsed = Math.round(
                (today - lastLoginDate) / 86400000
                );
            let trulyLoggedIn = false;
            if (userKey !== null && (daysElapsed < 14)) {
                trulyLoggedIn = true;
            } else { trulyLoggedIn = false }
            return {
                userIsLoggedIn: trulyLoggedIn
            };

        default:
            return state;
    }
}

In your ./reducers/index.js

//reducers index.js
import { combineReducers } from 'redux';

import { loginReducer } from './LoginReducers';

const rootReducer = combineReducers({
    loggedIn: loginReducer
});

export default rootReducer;

In your store where you used redux-thunk, applyMiddleWare. Lets call it configureStore.js

//configureStore.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';

export default function configureStore(initialState) {
    return createStore(
        rootReducer,
        initialState,
        applyMiddleware(thunk)
    );
}

In your App.js

//App.js
import { Provider } from 'react-redux';
import configureStore from './src/store/configureStore'; //where you configured your store
import { YourMainNavigator } from '../src/config/router'; //where your root navigator is

const store = configureStore();
export default class App extends Component<{}> {
    render() {
        return (
            <Provider store={store}>
                <YourMainNavigator />
            </Provider>
        );
    }
}

You should know you no longer need the isSignedIn method in your auth.js Your login method remains the same as outlined above in LoginScreen.

Now you can use redux to check the state of login like this:

import React, {Component} from 'react';
import {connect} from 'react-redux';

class MyComponent extends Component {
    someFunction() {
        if (this.props.loggedIn) {
            //do something
        }
    }
}
const mapStateToProps = (state) => {
    return {
        loggedIn: state.loggedIn.userIsLoggedIn
    };
}


export default connect(mapStateToProps)(MyComponent);

There should be a better way of using redux to manage login - better than what I outlined here. I think you can also use redux to manage your login state without using AsyncStorage. All you need to do is in your loginScreen, if the login functions returns a response.status == 'ok', you can dispatch an action to redux that logs the user in. In the example above, using asyncstorage you might only need to use redux to check if a user is logged in.

It is recommended that you use an abstraction on top of AsyncStorage instead of AsyncStorage directly for anything more than light usage since it operates globally. Redux-persist is that abstraction that goes on top of AsyncStorage. It provides a better way to store and retrieve more complex data (eg redux-persist has persistReducer(), persistStore()).

React native typescript implementation

storage.ts

import AsyncStorage from "@react-native-community/async-storage";
import { createStore, combineReducers } from "redux";
import { persistStore, persistReducer } from "redux-persist";

import exampleReducer from "./example.reducer";

const rootReducer = combineReducers({
  example: exampleReducer,
});

const persistConfig = {
  key: "root",
  storage: AsyncStorage,
  whitelist: ["example"],
};


// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = createStore(persistedReducer);

// Middleware: Redux Persist Persister
let persistor = persistStore(store);

export { store, persistor };

App.tsx

import React from "react";
import { PersistGate } from "redux-persist/es/integration/react";
import { Provider } from "react-redux";

import RootNavigator from "./navigation/RootNavigator";
import { store, persistor } from "./store";

function App() {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <RootNavigator />
      </PersistGate>
    </Provider>
  );
}

export default App;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM