I am using Redux to manage the state of my app and AsyncStorage to persist some data of the store.
I'm using the following way to initialize the store data because I want to access the store without any initialization function but directly from store variable. I know that store's data is read-only but it works fine for me.
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import store from './store';
export default App = () => {
const [ready, setReady] = React.useState(false);
React.useEffect(() => {
AsyncStorage.getItem('reduxData').then((data) => {
store.getState().ReducerA.sampleItem1 = data.sampleItem1;
store.getState().ReducerB.sampleItem2 = data.sampleItem2;
store.getState().ReducerC.sampleItem3 = data.sampleItem3;
setReady(true);
});
}, [setReady]);
if (!ready) return null;
return (
<Provider store={store}>
...
</Provider>
)
}'
What is the best way to initialize store correctly? ( Without external library such us Redux Persist and without wrapping the store in a function )
Related
I am trying to retrieve the API_URL from AsyncStorage and make it accessible in all app, the storing and retrieving (in settings screen) is working fine but when I try to load the data in the App.js using useEffect hook, it returns null. Reloading the app is not working but as soon as I save the App.js (using CTRL-S) it works fine.
Please let me know the correct way to do this.
import React, { useEffect, useState } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
export default function App() {
const [hostState, setHostState] = useState(null);
const getAHostInfoAsync = async () => {
const hostInfo = AsyncStorage.getItem('host').then(
setHostState(hostInfo)
).then(
console.log(hostState)
);
};
useEffect(() => {
getAHostInfoAsync();
}, []);
module.exports = {
host: hostState
};
}
and using in another file:
import App from "../../../App";
const API_URL = App.host;
I think your issue is in the way you use async/then. instead of async await.
I am not 100% sure that this is your issue. But if I change my async/await function to use async/then the way you are having it, my IDE says that the variable (hostInfo) might not have been initialised. In any case, I think this is a better syntax than with then.
const getAHostInfoAsync = async () => {
const hostInfo = await AsyncStorage.getItem('host')
setHostState(hostInfo)
console.log(hostState)
};
I'm trying to keep a live state of either the user is signed-in or not, so I can either show him or not, specific elements in the components. Using this code it's supposed to console.log(0) and then console.log(1), but it actually throws an error Cannot read properties of undefined.
./addons/Signed.js:
import { useState, createContext } from "react";
export const SignedContext = createContext();
export default function SignedProvider(props) {
const [SignedIn, setSignedIn] = useState(0);
return (
<SignedContext.Provider value={{ SignedIn, setSignedIn }}>
{props.children}
</SignedContext.Provider>
);
}
./screens/Profile.js:
import { useContext } from "react";
import SignedContext from "../addons/Signed";
...
const ProfileScreen = () => {
const { SignedIn, setSignedIn } = useContext(SignedContext);
console.log(SignedIn);
setSignedIn(1);
console.log(SignedIn);
...
}
...
You are using a named export for SignedContext but using a default import in Profile. Thus, you must use curly braces for your import. The following should change your issue.
import { SignedContext } from ".../addons/Signed"
Edit: If ProfileScreen is not a child of SignedContext.Provider, then this will not work. The general workflow is documented here. Hence, if ProfileScreen is not a child of the Provider, the context won't be available to it.
The ususal way to do this, is to define the context provider as a top level element in your app, if you want the context to be available at a global level in your application.
function App = () => {
const [signedIn, setSignedIn] = useState(0)
const contextValue = React.useMemo(() => ({signedIn, setSignedIn}), [singedIn])
// your application structure must be wrapped inside here.
// as an example I have only used ProfileScreen.
// Usually this is your root stack.
return (
<SignedContext.Provider value ={contextValue}>
<ProfileScreen />
</SignedContext.Provider>
)
}
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
};
In my application,the home page fetches the Json response from my rest API.Then I add the products into the cart array.Initially,my store values are..
const DEFAULT_STATE_TRENDING = {
data:{},
specialoffdata:[],
banner:[],
offerstitle:[],
cart:[],
cartcount:0,
isFetching:false,
dataFetched:false,
error:false,
}
After i added the products the cart array and cart count becomes..
cart:[{...},{...}],
cartcount:cart.length
Now, i close my app.After reloading the app,the cart array becomes empty.How to persist the store values here?
The best way to persist the store values is by using this awesome library. Redux Persist
It contains various methods and levels of persistance, with the ease of use.
Installation and basic usage is well documented on their github docs.
The simplest approach is to use AsyncStorage
let response = await AsyncStorage.getItem('count'); //Read
AsyncStorage.setItem('count', this.state.count + '');
This way, you can access the 'count' in every component.
The most modern way is to use react's new context api, you define the context provider and consume it everywhere:
const ThemeContext = React.createContext('light')
class ThemeProvider extends React.Component {
state = {theme: 'light'}
render() {
return (
<ThemeContext.Provider value={this.state.theme}> //<--- 'light' is exposed here
{this.props.children}
</ThemeContext.Provider>
)
}
}
class App extends React.Component {
render() {
return (
<ThemeProvider>
<ThemeContext.Consumer>
{item => <div>{item}</div>} //<-- you consume the value ('light') here
</ThemeContext.Consumer>
</ThemeProvider>
)
}
}
Either way, it is much lighter and easier than Redux or MobX.
I used the AsyncStorage.In this way i persisted my store state values even after reloading,closing and opening app.
configureStore.js:
import {createStore, applyMiddleware,compose} from 'redux';
import thunk from 'redux-thunk';
import { autoRehydrate } from 'redux-persist';
import allReducers from './reducers';
import { AsyncStorage } from 'react-native';
const middleware = [ thunk,
];
export default function configureStore() {
let store= createStore(
allReducers,
{},
compose(applyMiddleware(...middleware),autoRehydrate())
);
console.log("Created store is"+store);debugger
return store;
}
App.js:
const store = configureStore();
export default class App extends Component {
componentDidMount() {
console.log("App.js started and its store value is"+store);debugger
SplashScreen.hide()
persistStore(
store,
{
storage : AsyncStorage,
whitelist : ['trending']
}
);
}
render() {
return (
<Provider store={store}>
<MyStack/>
</Provider>
);
}
}
I'm having trouble accessing saved data after app starts with redux-persist.
The reducer initializes with data defined in my initial state. By the time the previous session's state loads with AsyncStorage, the component is already displayed with the default data.
I know that the data is saved and fetched successfully. If I leave the screen to a different one and come back to it, I can see that the data is displayed correctly. Debugging the state and props also shows that this is a async timing issue.
How can I delay the loading of the redux containers to after the store rehydrates. Alternatively, can I force update the conatainer (and its children) to update when the store finishes loading?
Here is what I've tried.
to configure my store:
export default function configureStore(initialState) {
const middlewares = [thunk];
if (process.env.NODE_ENV === `development`) {
const createLogger = require(`redux-logger`);
const logger = createLogger();
middlewares.push(logger);
}
let store = compose(
applyMiddleware(...middlewares),
autoRehydrate() )(createStore)(reducers);
persistStore(store, {blacklist: ['routing'], storage: AsyncStorage}, () => {
store.dispatch(storeRehydrateComplete())
})
return store
}
Hoping that this will update all other components (since all reducers in redux are called in an action), I've tried creating an action that triggers when the store finishes loading:
import { REHYDRATE_STORE } from './types'
export function storeRehydrateComplete(){
return {
type: REHYDRATE_STORE,
}
}
And it's reducer:
import { REHYDRATE_STORE } from '../actions/types'
const initialState = {
storeIsReady: false
}
export default function reducer(state = initialState, action){
switch (action.type) {
case REHYDRATE_STORE:
return {...state, storeIsReady:true}
break;
default:
return state
break;
}
}
But although the storeIsReady state changes to true when rehydration is complete, I'm still couldn't figure out how to force update the container component.
I solved it by following this recipe:
delay-render-until-rehydration-complete
Basically the solution is as follows:
In the component that wraps <Provider>, create a local state flag that tracks whether the store has rehydrated yet or not. Using the callback of persistStore() to update this flag.
In the render function of this wrapper, if the flag is true, render <Provider>, otherwise, render some temporary view.
You can wrap your store with PeristGate as such:
import { PersistGate } from 'redux-persist/integration/react'
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RootComponent />
</PersistGate>
</Provider>
This will show a custom loading screen (or no loading screen) until your store rehydrates with the persist data.