I am new to Redux and React Native and would like know if I can implement and store token sessions in Redux for keeping the user logged in after they close and reopen the app. I have found out some people recommend AsyncStorage but my app state is handled with Redux.
This is my Redux store which uses AsyncStorage too.
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import AsyncStorage from '#react-native-community/async-storage';
import reducer from './reducers/index'
const persistConfig = {
key: 'root',
version: 0,
storage: AsyncStorage
}
const persistedReducer = persistReducer(persistConfig, reducer)
const store = createStore(persistedReducer)
const persistor = persistStore(store)
export { store, persistor }
Would that be enough to keep a token session as I store other data in the same way?
redux store cannot restore data after closing and reopening the app.
import { AsyncStorage } from 'react-native'
you can store by
AsyncStorage.setItem(userSessionKey, userData)
and restore by
async function restoreSession() {
try {
const data = await AsyncStorage.getItem(userSessionKey)
const userData = JSON.parse(data)
if (userData !== null) {
return userData
} else {
throw new Error('User Data is empty')
}
} catch (error) {
//console.log(error)
return null
}
}
so, when the app starts, before navigating to main app,
restore the data, and add to redux store
Redux can't persist/keep the data of the store/reducer when you kill the application.
But with the help of redux-persist library, redux can persist the data of the reducer/store. Also if you user reudx-persist you don't have to manually create AsyncStorage calls for retrieving initially when app starts redux-persist will handle that for you. You can use different storage engines not just AsyncStorage more info here
In your case you can totally store user session token in redux with the help of redux-persist. Use Whitelist/blacklist in persist config to let redux-persist know which reducer to persist.
e.g.
// BLACKLIST
const persistConfig = {
key: 'root',
storage: storage,
blacklist: ['authReducer'] // navigation will not be persisted
};
// WHITELIST
const persistConfig = {
key: 'root',
storage: storage,
whitelist: ['authReducer'] // only navigation will be persisted
};
Related
I have a React Native app and using Redux Toolkit to store application state. It should load state from remote server during init process.
import { configureStore } from '#reduxjs/toolkit'
import counterReducer, {setAmount} from '../reducers/counterSlice';
import tankReducer, {DEFAULT_TANK} from '../reducers/tankSlice';
import logger from 'redux-logger'
import thunk from 'redux-thunk';
const preloadedState = {
counter:
{
value: 500
},
tank:
{
currentTank: DEFAULT_TANK,
tankList: [DEFAULT_TANK]
}
}
export const store = configureStore({
reducer: {
counter: counterReducer,
tank: tankReducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger, thunk),
preloadedState
})
It works fine with static, but i need get that preloadedState from the server with async request. How to do it? I mean can I somehow force configureStore logic to wait until I get async data from the server? Or where I can put async code which update state immediately after I do configureStore?
i am trying to persist and encrypt the redux store my react-native app.
i am trying to use redux-persist-transform-encrypt as per the documentation:
import { persistReducer } from 'redux-persist'
import createEncryptor from 'redux-persist-transform-encrypt'
const encryptor = createEncryptor({
secretKey: 'my-super-secret-key'
})
const reducer = persistReducer(
{
transforms: [encryptor]
},
baseReducer
)
but the big deal is to find a secure way to store 'my-super-secret-key'.
I've successfully got it from user input and saved it with react-native-keychain.
Now the problem is that the function to get the key from the keychain is asynchronous and i would need to get the key before the store initialisation.
The result would look like this
import { persistReducer } from 'redux-persist'
import createEncryptor from 'redux-persist-transform-encrypt'
const encryptionKey = // get the key here from the keychain before initiating the store
const encryptor = createEncryptor({
secretKey: encryptionKey
})
const reducer = persistReducer(
{
transforms: [encryptor]
},
baseReducer
)
Do someone have any workaround for me?
I also spent some time how to resolve this. At first I found nice library to save generated secret key into keychain called react-native-sensitive-info. After that I was facing the problem that getting and setting generated secret key is asynchronous. Then I figured out that AsyncStorage is used as a storage in the config for persistReducer and there was the way - let's create a storage which uses react-native-sensitive-info. After a while, I stumbled upon a library that already exists and is implementing exactly the same logic. This is it -> redux-persist-sensitive-storage.
Just use it like this:
...
import createSensitiveStorage from 'redux-persist-sensitive-storage';
const appReducer = combineReducers(myReducers);
const persistConfig = {
key: 'root',
storage: createSensitiveStorage({
keychainService: 'myKeychain',
sharedPreferencesName: 'mySharedPrefs'
})
}
const persistedReducer = persistReducer(persistConfig, appReducer);
const store = createStore(persistedReducer, applyMiddleware(thunkMiddleware));
const persistor = persistStore(store);
...
That's it!
I'm trying to access a redux store with redux persist in a service for my react native app.
I need a specific token to set a websocket connection.
My code so far:
./redux/Store.js:
const persistedReducer = combineReducers({
tokens: persistReducer(secureConfig, TokensReducer),
});
const store = createStore(persistedReducer);
const configureStore = () => {
const persistor = persistStore(store);
return { persistor, store };
};
export default configureStore;
./redux/reducers/TokenReducer
const initialState = {
accessToken: null,
refreshToken: null
}
const TokensReducer = (state = initialState, action) {
// reducer
};
export default TokensReducer;
./service/websocket.js
import configureStore from '../redux/Store';
const { store } = configureStore();
console.log(store.getState().tokens);
The problem is, I'm not getting the persisted content, but I'm getting the initial state (accessToken = null, refreshToken = null).
When I access the store from inside my app (inside components inside and ), I get the correct values.
Edit:
when I wrap the console.log in a setTimeout() of let's say 1 second, it works! So it asynchronous, but how can I create my code to wait for it and not using setTimeout?
Currently I have the following configuration in my vuex, what I do is through the userAuth action verify the user's role and assign it to a property of the state 'rol_user', using the UPDATEROL mutation. I execute this action when I log in so that the user's role is recorded in vuex.
here the vuex configuration:
import Vue from "vue";
import Vuex from "vuex";
import firebase from 'firebase';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
rol_user: ''
},
mutations: {
UPDATEROL: function (state, payload) {
state.rol_user = payload.rol_user;
}
},
actions: {
userAuth: async (context) =>{
let user = firebase.auth().currentUser;
if(user){
let user_info = await firebase.database().ref('users').child(user.uid).once('value');
let val_user = user_info.val();
console.log("USER AUTH - VUEX");
console.log(val_user.rol);
context.commit('UPDATEROL',{
rol_user: val_user.rol
});
}
}
}
});
It effectively assigns the user's role to the state property, rol_user.
The problem I have is when reloading the page. When I reload the page rol_user returns to its initial state, that is empty.
How can I do so that the value of the role is not lost even when reloading the page, but rather that it is changed to empty only until I log out
You need to use some sort of storage mechanism and check that storage everytime the app is first mounted. For example, you could store a session key in localStorage or just store the user state that you want to persist.
Note, I do not know what kind of data you are storing, I am assuming rol_user is an object, if it is a string, you do not need JSON serialization/deserialization as I've done in the example below:
import Vue from "vue";
import Vuex from "vuex";
import firebase from 'firebase';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
rol_user: ''
},
mutations: {
UPDATEROL: function (state, payload) {
state.rol_user = payload.rol_user;
localStorage && (localStorage.rol_user = JSON.stringify(state.rol_user));
//^ try to store the user role in localStorage
},
},
actions: {
userAuth: async (context) =>{
if(localStorage && localStorage.rol_user) {
//^ if it's in localStorage, log the user back in/update state to match what it is in localStorage
context.commit('UPDATEROL', { rol_user: JSON.parse(localStorage.rol_user)});
return;
}
let user = firebase.auth().currentUser;
if(user){
let user_info = await firebase.database().ref('users').child(user.uid).once('value');
let val_user = user_info.val();
console.log("USER AUTH - VUEX");
console.log(val_user.rol);
context.commit('UPDATEROL',{
rol_user: val_user.rol
});
}
}
}
});
You could also use Cookies (cookies are generally more common for this purpose, but localStorage also works just fine)
I am trying to integrate redux-persist to my react-native project. The aim is to persist the redux store's data between application re-launch so that user does not need to log in each time they launch the app. Also, I want to store the previously viewed data on the local storage to avoid re-querying all the data each time app is re-launched.
I added the redux persist support as per the code below:
import { AsyncStorage } from 'react-native';
import { createStore, applyMiddleware } from 'redux';
//import { createLogger } from 'redux-logger';
import { persistStore, persistReducer } from 'redux-persist';
import rootReducer from '../reducers/index';
import ReduxThunk from 'redux-thunk';
import { createTransform } from 'redux-persist';
import JSOG from 'jsog';
export const JSOGTransform = createTransform(
(inboundState, key) => JSOG.encode(inboundState),
(outboundState, key) => JSOG.decode(outboundState),
)
const persistConfig = {
// Root
key: 'root',
// Storage Method (React Native)
storage: AsyncStorage,
//transforms: [JSOGTransform]
// Whitelist (Save Specific Reducers)
// whitelist: [
// 'authReducer',
// ],
// // Blacklist (Don't Save Specific Reducers)
// blacklist: [
// 'counterReducer',
// ],
};
// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
// Redux: Store
const store = createStore(
persistedReducer,
applyMiddleware(ReduxThunk),
);
// Middleware: Redux Persist Persister
let persistor = persistStore(store);
// Exports
export {
store,
persistor,
};
If I don't add jsog and use the transforms: [JSOGTransform] line in the persistConfig I get this error:
redux-persist/createPersistoid: error serializing state TypeError:
Converting circular structure to JSON
If I uncomment the 'transforms' line in the persistConfig (as per the suggestion here: https://github.com/rt2zz/redux-persist/issues/735), then I get this error:
Exception in HostObject::set: < unknown >
I am just persisting the returned "user" object from the firestore database in my redux-store. Without the redux-persist, there has been no problem, but with the persist added I am having this issue.
What type of circular problem would exist in the user object returned after successful login to firestore (using password/email auth.)?
Why would JSOG not work as suggested in the link above? Any alternatives to how I can solve this problem?
P.S. Not only the user returned from firestore is causing these errors, but any data returned from firestore seems to fail to persist.
I appreciate any helps!
Cheers...
I think I solved the problem. Instead of JSOG I installed flatted and used in for my redux-persist transform.
Working transform and persistConfig looks like this:
export const transformCircular = createTransform(
(inboundState, key) => Flatted.stringify(inboundState),
(outboundState, key) => Flatted.parse(outboundState),
)
const persistConfig = {
key: 'root',
storage: AsyncStorage,
stateReconciler: autoMergeLevel2,
transforms: [transformCircular]
};
Some developer have this problem because of the socket set in Redax, Otherwise, you can use this way as well
// SetTransform.js
import { createTransform } from 'redux-persist';
const SetTransform = createTransform(
// transform state on its way to being serialized and persisted.
(inboundState, key) => {
// convert mySet to an Array.
return { ...inboundState, mySet: [...inboundState.mySet] };
},
// transform state being rehydrated
(outboundState, key) => {
// convert mySet back to a Set.
return { ...outboundState, mySet: new Set(outboundState.mySet) };
},
// define which reducers this transform gets called for.
{ whitelist: ['someReducer'] }
);
export default SetTransform;
and
// PersistReducers
import storage from 'redux-persist/lib/storage';
import { SetTransform } from './transforms';
const persistConfig = {
key: 'root',
storage: storage,
transforms: [SetTransform]
};