default values from initial state are loaded before store rehydrates with redux-persist - react-native

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.

Related

Re-render component everytime screen is opened react native

I'm kinda new to React Native. I'm using the getFamily() on my screen MyFamily but when I go to another screen there change the value of the Family and come back to my MyFamily screen then I don't see the changes.
I tried doing it with the useEffect but still nothing happens, also the log doesn't happen. How can I solve this?
export default function MyFamily({ navigation, props, person, inheritors }) {
console.log(getFamily());
let [family, setFamily] = useState(getFamily());
useEffect(() => {
console.log(getFamily());
setFamily(getFamily());
}, [getFamily]);
In the screen where I set the Family again I do this:
And I know that's correct because the Json that is shown shows the updated value.
import { setFamily } from '../../utilities/family';
setFamily(responseJson.family);
This is the way family is formulated:
let family = '';
export default family;
export function getFamily() {
return family;
}
export function setFamily(f) {
family = f;
}
React doesn't actually know that the value returned from the getFamily function changes each render. In the useState function, it's only used in the initial state, and the useEffect function never gets re-run because the getFamily function itself doesn't ever change and re-trigger the useEffect. You have to change the getFamily() function to use a state that's stored in a parent component and pass it into the MyFamily component as a prop.
e.g.
// the parent component that renders the MyFamily screen
function Router() {
const [family, setFamily] = useState('')
return (
<Navigator>
<Screen component={<MyFamily family={family} setFamily={setFamily} />
<Screen component={<OtherComponent family={family} setFamily={setFamily} />
</Navigator>
}
)
}
And then from MyFamily:
function MyFamily({ family }) {
console.log(family); // this should be updated
}
and from OtherComponent:
function OtherComponent({ setFamily }) {
return (<Button onClick={() => setFamily('newFamily')>Change family</Button>)
}

Changing state in React native App.js from another component

I'm making authentication in an app, and I'm kind of stuck. I have 2 different navigations. One shows if the user is logged in and another one if not. Basically, a Sign in screen. It's working fine if I change the value manually upon the start. But I can't find a way to change a state when a user signs in, for example. Even though the value in auth module changes, it doesn't update in App.js So how can I update the App.js's state from Sign in screen, for example?
import React, { Component } from 'react';
import { AppRegistry, Platform, StyleSheet, Text, View } from 'react-native';
import DrawerNavigator from './components/DrawerNavigator'
import SignedOutNavigator from './components/SignedOutNavigator'
import auth from './auth'
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props)
this.state = {
isLoggedIn: auth.isLoggedIn
}
}
render() {
return (
(this.state.isLoggedIn) ? <DrawerNavigator /> : <SignedOutNavigator />
);
}
}
AppRegistry.registerComponent('App', () => App)
and my auth module, which is very simple
import { AsyncStorage } from 'react-native';
// try to read from a local file
let api_key
let isLoggedIn = false
function save_user_settings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
isLoggedIn = true
});
}
module.exports.save_user_settings = save_user_settings
module.exports.api_key = api_key
module.exports.isLoggedIn = isLoggedIn
First off, there are loads of ways to approach this problem. Because of this I'm going to try explain to you why what you have now isn't working.
The reason this is happening is because when you assign auth.isLoggedIn to your isLoggedIn state, you are assigning the value once, kind of as a copy. It's not a reference that is stored.
In addition to this, remember, React state is generally only updated with setState(), and that is never being called here, so your state will not update.
The way I would approach this problem without bringing in elements like Redux, which is overkill for this problem by itself, is to look into building an authentication higher order component which handles all the authentication logic and wraps your entire application. From there you can control if you should render the children, or do a redirect.
Auth Component
componentDidMount() {
this._saveUserSettings(settings);
}
_saveUserSettings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
this.setState({isLoggedIn: true});
});
}
render() {
const { isLoggedIn } = this.state;
return isLoggedIn ? this.props.children : null;
}
App.js
render() {
<AuthComponent>
//the rest of authenticated app goes here
</AuthComponent>
}
Here's a really quick, incomplete example. But it should showcase to you how you may want to lay your authentication out. You'll also want to consider error handling and such, however.

How to set config show/hide refresh button on AppBar

Click to see image
Button refresh on AppBar is not refresh on page Dashboard because I just use Component Card but work on page using component List or Datagrid, so I want to config show/hide refresh button on AppBar or how to fix it work for page not use component List or Datagrid.
Sorry I'm not strong in English.
You'll have to fetch some data from the react-admin state for it to work. Indeed, the refresh button just trigger the refreshView action which update the state.admin.ui.viewVersion key of the the react-admin redux state. This key is a simple counter. Internally, we use this counter to check whether we must update some components data. Here is a simple example of a connected Dashboard which can do things when refreshed:
import React, { Component } from "react";
import { connect } from "react-redux";
class Dashboard extends Component {
componentDidMount() {
this.doOnMountAndWhenRefreshed();
}
componentDidUpdate(prevProps) {
if (prevProps.views !== this.props.views) {
this.doOnMountAndWhenRefreshed();
}
}
doOnMountAndWhenRefreshed = () => {
// This is where you do update your component:
// - Make API requests
// - Fetch data from the react-admin store, etc.
};
render() {
const { views } = this.props;
return <div>Refreshed {views} times.</div>;
}
}
const mapStateToProps = state => ({ views: state.admin.ui.viewVersion });
export default connect(
mapStateToProps,
{}
)(Dashboard);
You can see it working in this codesandbox
Edit for newer version of react-admin
import { useVersion } from 'react-admin';
const Dashboard = () => {
const version = useVersion();
return <div>Refreshed {version} times.</div>;
}
In react-admin 4.x I managed to get the desired behaviour like this:
import React from 'react'
import { useQuery } from 'react-query'
const noop = async () => new Date().valueOf()
export const MyDashboard = () => {
const { data } = useQuery('myDashboard', noop)
return (
<div>Last refreshed at {data}</div>
)
}
export default MyDashboard
Note how data represents the value returned by noop().
That way, whenever the user presses the refresh icon in the AppBar, the component is re-rendered.

React Native with Redux: mapStateToProps()-function is not updating

I am working at a React Native and Redux project with several reducers, that are combined through combineReducers().
I have one component, that doesn't dispatches any actions but only shows information from the redux state. It is connected to this state with the connect function:
class MyComponent extends React.Component {
componentWillMount() {
// some code
}
render() {
return(
<Text>
{ this.props.value }
</Text>
)
}
}
function mapStateToProps(state) {
return {
value: state.updaterReducer.value,
}
}
export default connect(
mapStateToProps,
) (MyComponent);
There's another part/component in the project, that changes the redux state and value:
import { makeUpdate } from './updater.actions.js';
import configureStore from '#redux/configureStore.js';
const store = configureStore();
// This function is can be called by some buttons in the app:
export default function update() {
console.log('Update function called');
store.dispatch(makeUpdate());
}
And here's my problem: when update is called, the updater-action and the updater-reducer are both called, so that the redux state changes, but 'MyComponent' never updates.
I could solve the problem on my own: The solution was very easy in the end, but couldn't be found on the basis of the code in the original question: Every time I needed the redux-store, I used a configureStore-function from a web-tutorial to create it on basis of the reducers. So I created multiple times the 'same' store. Unfortunately these stores were not connected to each other...
Sometimes that worked in the project, because a mapStateToProps-function and a mapDispatchToProps-function both were in the the same component and used one store, but sometimes (like in the example in the question) those functions used different stores and couldn't influence each other.

REDUX: understanding a bit the concept + react native

So, I am working on a pretty straight forward mobile app that has these scenes:
a list of people
person profile
add form
now, what I do, when I first load the LIST scene, I make an API call (I have a list component that I populate once I get results from the API... state.people).
All good here... when I tap on a person he's profile opens, no extra API calls, just passing the person object from state.people array.
All good here as well.
When I open ADD NEW person and send the form I make another API call (I post the information and get the new Object back)...
now the bit that is confusing to me.
What I would like is to update the LIST scene state.people by making another API call (get all again) after I get the OK confirmation from the POST.
and then navigate to Person's profile.
but, I am outside the scope of the LIST scene (I am in ADD NEW form). So, what would be the correct redux logic for this one?
The LIST component is already mounted... how do I communicate to LIST if I am on different scene
all these binding actions to components properties is confusing too... why can't redux act like a global hub that would always be accessible and would always retain it's state (at least on mobile app)
There is really a lack of real app examples... so far I see only very simplified examples that are not very useful on the grand scale to understand the whole flow
the store I have
/**
* STORE
*/
'use strict';
import { createStore, applyMiddleware } from 'redux';
import reducer from './_reducer';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';
const store = createStore(reducer, {}, applyMiddleware(
thunkMiddleware,
promiseMiddleware()
));
export default store;
and the actions I have:
import * as constants from '../../constants/constants';
import request from '../../utils/request';
export const getAll = () => ({
type: constants.PEOPLE_FETCH,
payload: request(constants.API_PATH + 'person', {method: 'GET'})
});
export const search = (data, searchTerm) => ({
type: constants.PEOPLE_SEARCH,
payload: _filter(data, searchTerm)
});
export const save = (data) => ({
type: constants.PERSON_SAVE,
payload: request(constants.API_PATH + 'person', {method: 'POST', body: JSON.stringify(data)})
});
This can be an example architecture for your app:
Make a Redux store with list of people.
On initial API call, update the store to contain the list fetched by API call.
Wrap your app inside Provider and pass the store to the Provider.
Use connect and mapStateToProps and mapDispatchToProps to connect the Redux store to React state.
Whenever you update or insert new person, and get the new object, you need to dispatch an action which then goes to the reducer function which finally returns the updated Redux store, and dont worry with the re-rendering as React does the re-rendering itself whenever there is a change in a state.
I'll give a small example of store/actions/reducer, with a react + redux app.
store.js
import { applyMiddleware, compose, createStore } from 'redux'
import reducer from './reducer'
import logger from 'redux-logger'
// TOOD: add middleware
let finalCreateStore = compose(
applyMiddleware(logger())
)(createStore)
export default function configureStore (initialState = { todos: [] }) {
return finalCreateStore(reducer, initialState)
}
actions.js
let actions = {
helloWorld: function(data){
return {
type: 'HELLO_WORLD',
data: data
}
}
};
export default actions
reducer.js // Please read from Redux docs that reducers need to be pure functions
export default function myReducer(state = [], action) {
switch (action.type) {
case 'HELLO_WORLD':
return 'welcome' + data;
default:
return state;
}
}
Component.js (the React App) //In component whenever you receive new object, dispatch an action which will modify the store.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actions from '../redux/actions'
class App extends Component {
handleClick() {
store.dispath(action.helloWorld("jimmy")); //this dispatches an action, which goes to the reducer to change the state and adds 'welcome' before 'jimmy'
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>
{store.getState()} //getState function to access store values
</div>
)
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch) //binds all the actions with dispatcher and returns them
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
This works like whenever you click the 'div' in the React Component, it calls the function, handleClick(), in which there is an action dispatch. This action then calls the reducer itself to update the store. I know you might get confused that how is store getting updated. Its a bit confusing but for that you need to follow a basic tutorial to explain React+Redux.
Please note this is not a runnable example, just a pseudocode. I recommend you to watch this youtube series to completely understand the redux stores+ react+webpack