Redux persist not syncing - react-native

Im using react native with expo, I have redux persist and im able to use it fine to get the state from the store however when i reload the app, the state does not persist.
Im wondering if there is some other step to have your state sync with the async storage state.
I am using a dispatch method to change the state, Then i print the updated state - all good.
But then i print what is stored in the asyc storage and only the old state is stored.
I have also done this on web browser and seen the same thing, The state updates fine in the application, Then when i check the storage nothing has changed and it still has the 'initialState' variable as state and the updates are not being added to the storage.
heres my outer component:
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ThemeProvider theme={theme}>
<AppGateway />
</ThemeProvider>
<StatusBar style="auto" />
</PersistGate>
</Provider>
The dispatch method to update the state:
async function handleSubmit() {
dispatch(createUser(fakeuser)).....
The reducer/duck
const CREATEUSER = 'createuser'
export const createUser = ({name, id, email}) => ({
type: CREATEUSER,
payload: {
id,
name,
email,
created_account: Math.floor(Date.now() / 1000),
last_login: Math.floor(Date.now() / 1000),
}
})
function userReducer (state = initialState, action) {
switch (action.type) {
case CREATEUSER:
return {...state, account:{...state.account, ...action.payload} }
break;
default:
return state
}
}
export default userReducer;
and my config
import {createStore , combineReducers} from 'redux'
import userReducer from './ducks/user'
import { persistStore, persistReducer } from 'redux-persist'
// import AsyncStorage from 'redux-persist/lib/storage'
import AsyncStorage from '#react-native-async-storage/async-storage';
const reducer = combineReducers({
user: userReducer
})
const persistConfig = {
key: 'root',
storage: AsyncStorage,
}
const persistedReducer = persistReducer(persistConfig, reducer)
export default () => {
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
}

Related

React Native/Redux State not updating after making API Call

I can return values from the Redux State, and when I make an API call, I see those being logged out but the state is not updating. I setup a basic App with a Redux Store to prove this out. And I've also include the Thunk library, which I read is needed for asynchronous State actions (like API). My guess is I am not returning the state properly at the end of the reducer, because again, if I log the values, there is detail being returned.
To consolidate the code as best as possible, I am only including the necessary pieces:
export default function App() {
const OtherVal = useSelector((state) => state.reducer2.WorkoutDetails);
const dispatch = useDispatch();
React.useEffect (() =>{
dispatch({ type: 'Test_API' })
})
return (
<View style={styles.container}>
<Text>Open up App.js to start rking on your app!</Text>
<Text>Value is: {OtherVal}</Text>
<StatusBar style="auto" />
</View>
);
}
My Store:
import {createStore, applyMiddleware } from 'redux';
import rootReducer from './index';
import thunk from 'redux-thunk'
const store = createStore(rootReducer, applyMiddleware(thunk) );
export default store;
My Root Reducer (could be in the Store):
import { combineReducers } from 'redux'
import reducer2 from './FirstReducer'
export default combineReducers({
reducer2,
})
My Reducer:
import axios from 'axios';
const initialState = {
WorkoutDetails:null,
}
const reducer2 = (state = initialState, action) => {
if(action.type == 'Test_API'){
axios.post('https://xxxx',{},
{
headers:{
'X-Requested-With':'XMLHttpRequest'
, 'Content-Type': 'application/json',
}
}
).then(function (response) {
// handle success
console.log('API Call 2')
const val = JSON.parse(response.data.recordset[0].EndPoint)
console.log('JSON Val', val)
return {...state,WorkoutDetails: val}
})
}
console.log('default Red',state)
return state
}
export default reducer2;
Instead of calling your API in reducer , you can call it in Action creator and then dispatch your action with payload as response. Based on the dispatch action type you can return your updated state to store through reducer.
Component dispatch an action -> apply your thunk (api call)->dispatch an action with API response data ->reducer update the store with correct state based on the action type.
Please refer the below URL:
https://www.freecodecamp.org/news/redux-thunk-explained-with-examples/

Open the application with the last State before closed

I am new to react-native and I am building an app which has an authentication module. The login works with the jwt token and sets the state of the user. I want to save the state of the user such that the next time the user launches the application, it retrieves the last state of the application and skips the login module. Note that I am not talking about the app going to background. I am storing the jwt in the async storage once the login is true in the api function.
Can anyone advise me to correct pointer to look for the same.
Below is my login auth code -
Reducer -
import { combineReducers } from 'redux';
const initialAuthState = { isLoggedIn: false };
const Login = 'Login';
const Logout = 'Logout';
export const login = data => ({
type: Login,
data
});
export const logout = () => ({
type: Logout,
});
function auth(state = initialAuthState, action) {
switch (action.type) {
case Login:
console.log("reducer called for Login");
console.log(action.data.user)
return { ...state, isLoggedIn: true, user: action.data.user};
case Logout:
console.log("reducer called for logout");
return { ...state, isLoggedIn: false, user: {} };
default:
return state;
}
}
const AppReducer = combineReducers({
auth,
});
export default AppReducer;
login.js
import React from 'react';
import { StyleSheet, Text, TextInput, View } from 'react-native';
import Button from 'react-native-button';
import PropTypes from 'prop-types';
import AppStyles from '../AppStyles';
import Api from '../Api';
import { connect } from 'react-redux';
import { login } from '../reducers';
class LoginScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
// loading: true,
email: 'username',
password: 'password'
};
}
onPressLogin = () => {
Api.login(this.state.email, this.state.password, (success, data) => {
if (success) {
this.props.login({ user: data.username});
} else {
alert(data);
}
});
};
render() {
return (
<View style={styles.container}>
<Text style={[styles.title, styles.leftTitle]}>Sign In</Text>
<View style={styles.InputContainer}>
<TextInput
style={styles.body}
placeholder="E-mail or phone number"
onChangeText={text => this.setState({ email: text })}
value={this.state.email}
underlineColorAndroid="transparent"
/>
</View>
<View style={styles.InputContainer}>
<TextInput
style={styles.body}
secureTextEntry
placeholder="Password"
onChangeText={text => this.setState({ password: text })}
value={this.state.password}
underlineColorAndroid="transparent"
/>
</View>
<Button
containerStyle={styles.loginContainer}
style={styles.loginText}
onPress={() => this.onPressLogin()}
>
Log in
</Button>
</View>
);
}
}
Thanks
You could you redux-persist. I see you're already managing your state with Redux, it is pretty simple to setup and will persist your reducers through sessions. You could also hold on a Splash Screen while it is loading, so the user interaction is seamless. You'd then check for auth info in the reducer before sending to the Login Screen or Main Screen.
You could also use some kind of local database, such as Realmjs, and then store whatever info you need in there.
An example how to use redux-persist:
store.js
// Imports: Dependencies
import AsyncStorage from '#react-native-community/async-storage';
import { createStore, applyMiddleware, compose } from 'redux';
import { createLogger } from 'redux-logger';
import { persistStore, persistReducer } from 'redux-persist';
import rootReducer from '../reducers/index';
import thunk from 'redux-thunk';
// Middleware: Redux Persist Config
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: [
'authReducer',
],
};
// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
let composeEnhancers = compose;
if(__DEV__) {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}
// Redux: Store
const store = createStore(
persistedReducer,
composeEnhancers(
applyMiddleware(
thunk,
createLogger()
)
)
);
// Middleware: Redux Persist Persister
let persistor = persistStore(store);
export {
store,
persistor,
};

How do I populate the initialState in Redux from react native's AsyncStorage?

I have a React Native app. I am storing username and uid in AsyncStorage so they don't have to log in every time. How do I populate the initialState with these values. There are some packages that do it for you but it seems like this should be doable without the overhead of another package. Right now initial state is just empty values.
const initialState = {
uid: "",
username: "",
};
Here is the solution I came up with. Just create an action that gets the AsyncStorage properties and dispatch the array of properties to the reducer where they are assigned to the state. And you call the action directly on the store. Much lighter than adding a whole other library. For simplicity I'll assume all the Redux code is in one file called myRedux.js:
// Imports:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { AsyncStorage, } from "react-native";
// Set initial state to empty values:
const initialState = {
uid: "",
username: "",
};
// Reducer:
const reducer = (state = initialState, action) => {
switch(action.type) {
case "setInit":
return {
...state,
uid: action.uid,
username: action.username,
}
default:
return state;
}
};
// Store
const store = createStore(reducer, applyMiddleware(thunk));
export { store };
// Action
const setInit = (result) => {
return {
type: "setInit",
uid: result[0][1],
username: result[1][1],
};
}
const getAsyncStorage = () => {
return (dispatch) => {
AsyncStorage.multiGet(['uid', 'username'])
.then((result) => {dispatch(setInit(result))});
};
};
// Dispatch the getAsyncStorage() action directly on the store.
store.dispatch(getAsyncStorage());
Then in the Screen files you can access them with mapStateToProps:
const mapStateToProps = (state) => {
return {
uid: state.uid,
username: state.username,
};
}
// Access the prop values in the render:
render() {
return (
<View>
<Text>Uid: {this.props.uid}</Text>
<Text>Username: {this.props.username}</Text>
</View>
);
}
// Connect mapStateToProps to the component class
export default connect(mapStateToProps)(MyScreen);
Aman Mittal provides an excellent guide for persisting state to AsyncStorage and populating the initial state using the redux-persist package.
https://blog.jscrambler.com/how-to-use-redux-persist-in-react-native-with-asyncstorage/
Just make sure when you get to the config part, that you use AsyncStorage as the storage value:
import { persistReducer } from 'redux-persist';
import rootReducer from './reducers';
...
export const config = {
key: 'my-root-key',
storage: AsyncStorage,
blacklist: [],
};
const store = createStore(
persistReducer(
config,
rootReducer
),
compose(...activeEnhancers),
);

React native and redux persist not working

I am using redux-persist to store the data in my react-native app.
This is the code:
store.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import {
persistStore,
persistCombineReducers,
} from 'redux-persist';
import { AsyncStorage } from 'react-native';
import { composeWithDevTools } from 'redux-devtools-extension';
import user from './reducers/user';
import auth from './reducers/auth';
const config = {
key: 'root',
storage: AsyncStorage,
};
const reducers = persistCombineReducers(config, {
user,
auth
});
export const configureStore = () => {
const store = createStore(
reducers,
compose(
applyMiddleware(thunk),
)
);
const persistor = persistStore(store);
return { persistor, store };
};
Then in the App.js I have this :
const { persistor, store } = configureStore();
const onBeforeLift = () => {
// take some action before the gate lifts
store.dispatch(startingApp());
}
return (
<Provider store={store}>
<PersistGate
loading={<HomeLoader />}
onBeforeLift={onBeforeLift}
persistor={persistor}>
<RootNav />
</PersistGate>
</Provider>
Everything works fine when I dispatch and action from the App.js componentDidMount.
The problem is that when I fire the action from component, for example, the state is not stored, so when I restart the app the state is gone.
What I do in is just calling the action and passing the data:
this.props.onSetAuthData(data.credentials);
The state is updated as I can see in the console, but if I restart the app, only the state created by the action in App.js is saved, not the one in
Maybe this has to do with the RootNav component ?
maybe I am exporting wrong the reducers?
I have
const user = (state = initialState, action = {}) => {}
export default user.
Same for the other reducer:
const auth = (state = initialState, action = {}) => {}
export default auth.
Then I export with
combineReducers({auth, user})
Is this wrong?
Use tool like Reacttotron to see if your store is persisted or not.
https://github.com/infinitered/reactotron
If it's already persisted your component should wait until the store rehydrated on app launch. Sometimes I can't use the redux persist using persistgate to wait for the persisted store to be rehydrated. So I set the store and persistor into state on async componentWillMount then in your render, check if the store is not empty (null) and already rehydrated then load your app.
constructor(){
super();
this.state = {store: null, persistor: null}
}
async componentWillMount () {
const store = configureStore();
this.setState({ store: store.store })
this.setState({ persistor: store.persistor })
}
render(){
return (
if (this.state.store === null) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
<Provider store={this.state.store} persistor={this.state.persistor}>
<RootNav />
</Provider>
Also try to change your storage from AsyncStorage to storage.
const config = {
key: 'root',
storage,
};
First import the storage import storage from 'redux-persist/es/storage';
sometimes it calls an error with the key in persistConfig. try key: 'primary'
const primary = {
key: 'root',
storage: AsyncStorage,
blacklist: [],
whitelist: ['user'],
};

Actions not being passed to redux in react-native-router-flux

I followed the instructions set out in https://github.com/aksonov/react-native-router-flux/blob/master/docs/v3/REDUX_FLUX.md#step-1 to a tee in version beta.24 and when I navigate via Action.push, pop, replace, etc there is no corresponding action that is passed through to my reducer.
i print at the top of my reducer and can capture events I pass through dispatch manually. Are there common issues that I could run into?
Code
Reducer
import { ActionConst } from 'react-native-router-flux';
const initialState = {
scene: {},
};
export default function SceneReducer(state = initialState, action) {
console.log(action);
switch (action.type) {
case ActionConst.FOCUS:
return { ...state, scene: action.scene };
default:
return state;
}
}
Combined Reducers
import { combineReducers } from 'redux';
import SceneReducer from './SceneReducer';
const rootReducer = combineReducers({
routing: SceneReducer,
// other reducer here...
});
export default rootReducer;
App
import RootReducer from './RootReducer';
import loginRouter from './LoginRouter';
const ReduxRouter = connect()(Router);
const store = compose(applyMiddleware(thunkMiddleware))(createStore)(RootReducer);
const navigator = Actions.create(
<Modal hideNavBar>
<Scene key="root" hideNavBar>
<Scene key='login1' component{Log1} />
<Scene key='login2' component{Log2} />
</Scene>
<Scene key="modalRoot"><Scene key="modal" component={Comp} /></Scene>
</Modal>,
);
export default class AppRouter extends Component {
render() {
return (
<Provider store={store}>
<ReduxRouter scenes={navigator} />
</Provider>
);
}
}
Thanks for the help!
Try replace your ReduxRouter with this:
import { Router, Reducer } from 'react-native-router-flux';
import { connect } from 'react-redux';
const ReduxRouter = connect()(({ dispatch, children, ...props }) => (
<Router
{...props}
createReducer={params => (state, action) => {
dispatch(action);
return Reducer(params)(state, action);
}}
>
{children}
</Router>
));
Also, for the reducer, the action's route key is routeName rather than scene (maybe your version differs so look out for both):
I'm using "react-native-router-flux": "4.0.0-beta.27".
There are some of the modifications that you need to do in your code.
You need to implement and use reducer object from react-native-router-flux, which defines and handles the actions appropriately.
Then bind it to your SceneReducers.js as
import {Reducer, ActionConst, Actions} from 'react-native-router-flux'
const defaultReducer = Reducer();
export default (state, action) => {
console.log(action);
switch (action.type) {
case ActionConst.FOCUS:
return defaultReducer(state, action);
default:
return defaultReducer(state, action);
}
}
It is important to load reducers AFTER actions.create, so don't use import here.
This is because the initial state of the reducer' must be available at compile time and Router is created runtime.
Therefore in your App use
// create actions
// connect your router to the state here
const ReduxRouter = connect((state) => ({ state: state.routing }))(Router);
const RootReducer = require('./RootReducer').default;
// define the store
const store = compose(applyMiddleware(thunkMiddleware))(createStore)(RootReducer);
Have a look at this thread to follow-up.You might run into issues due to non-deep linking of the external states, therefore you can check it on version until 4.0.0-beta.23 as mentioned in this comment.
Hope it helps