How to refresh a screen when returning from another screen of a different navigator (React Native)? - react-native

I have been implementing most of my application with a StackNavigator. Now, I added a DrawerNavigator, from which one of its screens calls another screen of the original StackNavigator. For example, consider the following navigation sequence that a user could make:
ScreenA -> ScreenB -> ScreenC
where ScreenA belongs to the StackNavigator, ScreenB belongs to the DrawerNavigator, and ScreenC belongs to the StackNavigator again. To achieve that, actually ScreenA does not call ScreenB directly, but another screen whose sole purpose is to serve as a root of all the screens that belong to the DrawerNavigator. Also, that root receives the StackNavigator in the ScreenProps in order that its screens can later use the Stack again.
Now, if I am in ScreenC and I go back using "this.props.navigation.goBack()", I return to the DrawerNavigator in the ScreenB, because that is which called ScreenC. The ScreenB should refresh its state, that is, it should reload information from the database, because that information could have changed in ScreenC, so the previous state is no longer valid.
When only using StackNavigator, I always managed to do it using "NavigationEvents". For example:
import {Component} from 'react'
...
import { NavigationEvents } from 'react-navigation'
class ScreenB extends Component{
// This is the function that loads information from the database (PouchDB)
loadInformation = async() =>{
...
}
render(){
return(
<View>
<NavigationEvents onWillFocus = {payload => this.loadInformation()}/>
<NavigationEvents onDidFocus = {payload => this.loadInformation()}/>
...
</View>
)
}
}
With this implementation, the function "loadInformation" activated both when I entered the screen for first time, and also when I returned to it from a child screen. But this time that I am mixing both navigators, neither "onWillFocus" nor "onDidFocus" are activating when returning from ScreenC to ScreenB, so I cannot enter to the "loadInformation" function again. How could I do it?
Edit:
I also tried keeping a boolean variable in Redux store that determines if the function "loadInformation" of ScreenB must be activated. That variable starts with the true value. Then, once I enter to Screen B and I execute the function, it is changed to false. When I navigate to ScreenC, in that screen the variable is changed to true again, so when I go back to ScreenB it indicates again that the function must be executed.
That required to use in ScreenB the "componentDidUpdate" function, that constantly checks if that variable is true or false in order to call "loadInformation". That solved the problem, but brought a new one. When I try to navigate from ScreenB to another screen of the DrawerNavigator, it takes too much time, because in the transition "componentDidUpdate" is called repeatedly. So this solution does not seem viable.

Unfortunately the approach you used <NavigationEvents> has been updated. so, what should you do is:
class screenA/ screenB/ screenC extends React.Component {
componentDidMount() {
this._unsubscribe = navigation.addListener('focus', () => {
// do something
});
}
componentWillUnmount() {
this._unsubscribe();
}
render() {
// Content of the component
}
}
Use these updated navigation events in all of your screens. Hope it will solve your issue. For more information See This

I am answering my own question.
The solution was to use a boolean variable from Redux's store that indicates if the function "loadInformation" must be activated or not. Let's say the variable is named "loadView", which has the value "false" by default, but the ScreenC sets it in "true" when it is going to be closed and therefore we are going to return to ScreenB.
In other words, the file of ScreenC includes this code:
import {Component} from 'react'
import { connect } from 'react-redux'
// Here we import the action that allows to change the value of "loadView"
import { changeLoadView } from '../../redux/actions/popUpActions'
...
class ScreenC extends Component{
...
// Function that is activated automatically when we leave the screen
componentWillUnmount(){
// This is the function that assigns the value "true" to "loadView"
this.props.dispatchChangeLoadView(true)
}
...
}
const mapStateToProps = (state) => {
return {
...
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchChangeLoadView: (bool) => dispatch(changeLoadView(bool)),
....
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenC);
In the file of ScreenB I use a "fake view", which is a React View that is not placed directly in the "render" function but it is called by another function (named "activateLoadInformation" in this case). That function returns an empty view, but the reason to use it is because before its "return" we can activate any other function of ScreenB that we want, that in this case is "loadInformation". I don't know another way to activate functions that don't render anything arbitrarily when we want to.
import {Component} from 'react'
import { connect } from 'react-redux'
...
class ScreenB extends Component{
...
// This is the function that loads information from the database (PouchDB)
loadInformation = async() =>{
this.props.dispatchChangeLoadView(false);
...
}
// Fake view that calls the function "loadInformation"
activateLoadInformation(){
this.loadInformation();
return(<View/>)
}
render(){
return(
<View>
{!this.props.loadView &&
<NavigationEvents onWillFocus = {payload => this.loadInformation()}/>
}
{this.props.loadView &&
this.activateLoadInformation()
}
...
</View>
)
}
}
const mapStateToProps = (state) => {
return {
loadView: state.popUpReducer.loadView,
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchChangeLoadView: (bool) => dispatch(changeLoadView(bool)),
....
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenB);

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>)
}

The action POP with payload was not handled by any navigator

I have no idea what is causing this bug in my react native app. I'm using version 5 of the React Navigation library.
It randomly crashes the app sometimes. Google searching hasn't helped me understand what this is. It's very selective though which is a good(or a bad) thing.
So what does this mean and what could be causing it?
if(navigation.canGoBack()) {
navigation.dispatch(StackActions.pop(1));
}
see https://github.com/react-navigation/react-navigation/issues/7814#issuecomment-599921016
Did you try this?
this.props.navigation.goBack()
It means you tried to pop a view where there was nothing to pop. It might mean there's a bug in your app because, generally, you shouldn't be popping a view when there isn't any to pop.
But it can also be part of intentional design where you have insufficient knowledge of the current navigation state, but need to make sure at least one pop is done (similar to clearing a flag variable even if it might not be set in the first place, in which case it would be a no-op). If that's the case, then you can disable this development-level warning:
const temp = console.error;
console.error = () => {};
navigation.pop();
console.error = temp;
Error Cause: goBack() or pop() is getting called multiple times. Sometimes onPress event gets called many times. You can check by adding console.log().
How to Solve: you need to throttle the onPress function.
Example:
import React, { PureComponent } from 'react'
import { Text, View } from 'react-native'
import { throttle } from 'lodash'
export default class Test extends PureComponent {
constructor(props) {
super(props)
this.state = {
}
this.onPress = throttle(this.onPress, 500, {trailing: false})
}
onPress = () => {
console.log("going back")
this.props.navigation.pop();
//this.props.navigation.goBack();
}
render() {
return (
<View>
<Text>Hello World!</Text>
</View>
)
}
}
you need to check there can go back or not by canGoBack method like this
import { StackActions } from '#react-navigation/native';
if(this.refs.navigation.canGoBack())
{
this.refs.navigation.dispatch(StackActions.pop(1));
// this.refs.navigation.dispatch(StackActions.popToTop());
}

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.