React Native: Load value from AsyncStorage - reactjs

How can i load data from AsyncStorage to redux store at the time of application's first page loading?
I tried to call a check function in componentWillMount.
componentWillMount(){
debugger;
this.check();
}
and in check i tried to take the value from AsyncStorage and store it to redux store object.
async check(){
await AsyncStorage.getItem('location').then((location) => {
this.props.location[0].city = location;
}
});
this.props.selectLocation(this.props.location);
}
As it is a Asynchronous function , I am unable to get the values and store it view selectLocation action of redux.
How to properly get the data and store it before component gets mounted ?
Update:
In my index.android.js i changed my store like this,
import { persistStore, autoRehydrate } from 'redux-persist'
import combineReducers from './src/Redux/reducers/combineReducers';
// const store = createStore(combineReducers);
const store = compose(autoRehydrate())(createStore)(combineReducers)
persistStore(store, {storage: AsyncStorage}, () => {
console.log('restored');
console.log(store);
})
export default class ReactNavigation extends Component {
render() {
return (
<Provider store = {store}>
<App />
</Provider>
);
}
}

Your component should listen to store through event listeners for changes and should show the data when the data is loaded into the store.
For the time being when the data is not there, you should show something like a spinner to let the user know that it's loading.
Ideally, you don't want to make componentWillMount wait for the data to be loaded.

Related

How to get stored data from store.getState()

I have using react with redux for the first time.
In my app's render, if I console log store.getState() I can see the stored data {username: 'foo', password: 'bar'}
but when I want to get the data like store.getState().password
I get undefined error. I am trying to pass this data to
my components for private route as:
inside PrivateRoute I then try to check if the user is logged in or not to send to dashboard
so, how do I get data to pass to my props?
even const { isLoggedIn, username } = store.getState() doesn't work, it shows
Property isLoggedIn does not exist on type {}
btw I know this might be bad but it's my first react app so I am trying to learn redux
if you are calling the store from the react application you have to use provider and pass the store to the react app, and then bind state, actions and/or methods of the store to the react props as shown in this link
connect https://react-redux.js.org/5.x/api/connect
but if you are using redux in normal javascript then it will work fine.
example in react
first
import { Provider } from 'react-redux'
import { store } from "store";
ReactDOM.render(
<Provider store={store}>
<YourReactApp/> // e.g <Container />
</Provider>, document.getElementById('root')
);
then in you can bind anything from your store to react component like this
import { connect } from "react-redux";
const mapStateToProps = (state) => {
const { isLoggedIn, username }= state
return {
isLoggedIn,
username
};
};
const mapDispatchToProps = (dispatch :any) => {
return {
login: ()=> {
return dispatch(your action creator)
}
}
}
const Containter = connect(mapStateToProps,mapDispatchToProps)(AnyReactComponentYouWantToPassThisStore);
export default Containter;
the you can use it in your page like this
function AnyReactComponentYouWantToPassThisStore (props){
return(
<div> {props.username} </div>
)
}
then instead of calling <AnyReactComponentYouWantToPassThisStore />
now use <Container />

How to architect handling onSuccess of a redux dispatched request that becomes a React Navigation change of screen

I have a Registration screen.
The result of a successful registration will update the account store with the state:
{error: null, token: "acme-auth" ...}
On the Registration screen I render an error if there is one from the store.
What I want to do is navigate to the Dashboard with this.props.navigation.navigate when the store state changes.
I can do this hackily:
render() {
const {account} = this.props
const {token} = account
if (token) {
this.props.navigation.navigate('Dashboard')
}
}
I can also use callbacks:
sendRegistration = () => {
const {email, password} = this.getFormFields()
this.props.registerStart({email, password, success: this.onRegisterSuccess, failure: this.onRegisterFailure}) //using mapDispatchToProps
}
Passing the callback through the redux path seems redundant since I already have the changed state thanks to linking the account store to my Registration component props.
I am toying with the idea of a top-level renderer that detects a change in a userScreen store then swaps out the appropriate component to render.
Is there a simpler, or better way?
Yes there is a better way. If you want to navigate in an async fashion the best place to do it is directly in the thunk, sagas, etc after the async action is successful. You can do this by creating a navigation Service that uses the ref from your top level navigator to navigate.
In app.js:
import { createStackNavigator, createAppContainer } from 'react-navigation';
import NavigationService from './NavigationService';
const TopLevelNavigator = createStackNavigator({
/* ... */
});
const AppContainer = createAppContainer(TopLevelNavigator);
export default class App extends React.Component {
// ...
render() {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
This sets the ref of the navigator. Then in your NavigationService file:
// NavigationService.js
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
// add other navigation functions that you need and export them
export default {
navigate,
setTopLevelNavigator,
};
Now you have access to the navigator and can navigate from redux directly. You can use it like this:
// any js module
import NavigationService from 'path-to-NavigationService.js';
// ...
NavigationService.navigate(''Dashboard' });
Here is the documentation explaining more:
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html

Grab redux snapshot of redux state without connecting

I have a background saga that is watching the location and submits an action with the new location every time it changes and updates the location state. However, I have a component that I just want to grab the current state.user.location on a user interaction, but I don't want to use mapStateToProps because the component keeps re-rendering and I only want to grab the state when the user requests it and avoid constantly re-rendering.
I need the state in the background for other parts of the app.
reducer:
export const updateLocation = (state, { location }) => state.merge({ location })
selector:
getLocation : state => state.user.location && state.user.location.coords || null
Component:
class SearchScreen extends PureComponent {
render(){
const {location} = this.props
return(
<Button onPress={()=>searchWithCurrentLocation(location)} />
)}
}
const mapStateToProps = (state) => {
return {
location: UserSelectors.getLocation(state),
}
}
this is my current setup, but I'd like to not pass in the location and keep re-rendering as it's not used to display the component.
You can make the store accessible from anywhere like this:
file: storeProvider.js
var store = undefined;
export default {
init(configureStore){
store = configureStore();
},
getStore(){
return store;
}
};
file: App.js
import { createStore } from 'redux';
import rootReducer from './rootReducer';
import storeProvider from './storeProvider';
const configureStore = () => createStore(rootReducer);
storeProvider.init(configureStore);
const store = storeProvider.getStore();
const App = () =>
<Provider store={store} >
<Stuff/>
</Provider>
file: Component.js
import storeProvider from './storeProvider';
class SearchScreen extends PureComponent {
render(){
return(
<Button onPress={()=> searchWithCurrentLocation(UserSelectors.getLocation(storeProvider.getStore().getState())} />
)}
}
I don't think you need to be troubled about re-rendering if the data that has changed isn't directly affecting the components inside your render method. Let us remember that ReactDOM watches only those changed state and only update the DOM based on what's different. The render method maybe called but if nothing has really changed, it won't affect render performance at all.
After all that is what react is selling: reactive elements that updates changes if data changes in a smart way that it is optimized by updating only updated elements by using a virtual DOM which is ReactDOM.
Changes are first compared between the virtual DOM and the real DOM before updates are committed to the real DOM. You can read how ReactDOM does this here.

How to connect component (file) to redux store

I need to create component/file/class whatever and connect it to redux store.
I don't need this component to be rendered.
It's just a helper component which can contain methods that return value from store.
I tried to create a file with:
export function getKeyFromReduxStore(key:string) {...
but this is just a file that export function and I can't (or don't know) how to connect it to redux store.
All my components are connected with store throught:
<RouterWithRedux>
<Scene key="root">...
but as I said this is no scene it's just helper component that I want to reuse through whole app.
How can I make such a component and connect it to redux?
Redux store has a method called getState which gets the state of the store. You can import the store you have created in the file where redux store is required.
// in the file where you created your store
import { createStore } from 'redux';
export const myStore = createStore(
// ... some middleware or reducer etc
)
// in your component/file/class
import { myStore } from '../path/to/store'
export function getKeyFromReduxStore(key:string) {
return (myStore.getState())[key];
}
Alternatively, you can pass in the store to getKeyFromReduxStore function and call it in react component where store would be available. For instance in the mapStateToProps function:
// component/file/class
export function getKeyFromReduxStore(store, key) {
return store[key];
}
// react component with access to store
import { getKeyFromReduxStore } from '../path/to/file';
class MyKlass extends Component {
// ... your logic
render() {
const val = this.props.getKey(someVal);
}
}
const mapStateToProps = (state) => ({
getKey: (key) => getKeyFromReduxStore(store, key),
})
export default connect(mapStateToProps)(MyKlass);

React-Router v4 and Redux authentication

I'm using react-router v4 and Redux for a new project.
I have the following code:
export class App extends Component {
componentDidMount() {
const { dispatch } = this.props;
dispatch(initAuth());
}
render() {
const { user } = this.props;
return (
<BrowserRouter>
<div>
<NavContainer />
<Match pattern="/login" component={LogInForm} />
<MatchWhenAuthorized pattern='/users' component={Users} user={user} />
</div>
</BrowserRouter>
);
}
}
Where initAuth dispatches an action that checks if there's an existing token in localStorage and if there is, a logIn action is dispatched as well.
The problem is that if I go directly to myapp.com/users the action hasn't returned yet, so there's no user logged in and in that case MatchWhenAuthorized redirects me to my LogInForm, which I don't want if my initAuth logs a user in.
Is there an elegant way to solve this?
I think I could solve it by rendering the MatchWhenAuthorized component only if there's a user logged in, but that doesn't seem right.
The initial login state should be set when the page is loaded and before you mount your app. I'm not sure why the initAuth is a redux action creator when you could just check the localStorage without involving redux.
index.js
import { createStore } from 'redux'
import reducer from './reducer'
import { getUser } from './storage'
// load the user data from localStorage and
// use the value in the store's initial data
const store = createStore(reducer, {
user: getUser()
})
Then you can connect your <MatchWhenAuthorized> component to the store to access the user value and redirect if there is no user in the store.

Resources