Re-render component everytime screen is opened react native - 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>)
}

Related

change props value of a child component causing too many renders

child component takes a prop. Now from button click of the parent, i want to continue updating the prop so that the child component can update itself with new calculated result
const childComponent = ({value}) =>{
const [total, updateTotal] = useState('enter 0 or higher value and hit calculate button');
const calculateTotal = () =>{
updateTotal(value * 0.25);
}
useEffect(()=>{
if (value>0){
calculateTotal();
}
}, [value]);
return (<Text>{total}</Text>)
}
ParentComponent:
const [value, updateValue]= useState(0);
const generateValue = ()=>{
//logic that generates a number
const numberGenerated = generateValue();
updateValue(numberGenerated);
}
return (<View><ChildComponent value={value}/> <Button onPress={generateValue}>Calculate</Button></View>)
But when I click the button, it says :
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Maybe it's because you missed the arrow function:
<Button onPress={()=>generateValue()}>Calculate</Button>

How to refresh a screen when returning from another screen of a different navigator (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);

How to change function from firing on press to firing when app loads

I'm trying to make a weather app that gets the user's location when they open the app. I was given starter code that uses a button that gets the user's location when it's pressed. How can do I change the code so that it fires the contents of the _onPress function when the app is opened?
My starter code:
import React, { Component } from "react";
import Button from "./../Button";
import styles from "./style.js";
const style = { backgroundColor: "#DDDDDD" };
class LocationButton extends Component {
_onPress() {
navigator.geolocation.getCurrentPosition(
initialPosition => {
this.props.onGetCoords(
initialPosition.coords.latitude,
initialPosition.coords.longitude
);
},
error => {
alert(error.message);
},
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
}
render() {
return (
<Button
label="Use Current Location"
style={style}
onPress={this._onPress.bind(this)}
/>
);
}
}
export default LocationButton;
I had read that componentWillMount() will fire before render, so I tried inserting the following code after the _onPress() function, but I got an error stating 'Can't find variable: _onPress'
componentWillMount() {
_onPress();
}
I'm sure the solution is simple - I'm very new to ReactNative. I will gladly give any additional information if need be. Thank you!
_onPress() doesn't exist because it's not bound to anything until the render method. Just_onPress() actually doesn't exist anywhere. To fix this, change_onPress() to an arrow function by changing the function declaration from _onPress(){} to _onPress = () => {}, then call it in componentDidMount via this._onPress(). You'll also have to change your onPress to onPress={this._onPress}. The second way of fixing this still requires you to change your onPress to onPress={this._onPress}, but also to create a constructor and bind your function there by doing this.onPress = this.onPress.bind(this).

While coming back from react navigation componentWillMount doesn't get called

I have used react-navigation and on clicking hardware back button in android, I come back to previous component but componentWillMount doesn't get called. How do I ensure that componentWillMount is called?
componentWillMount will not trigger when you entering new screen / back to the screen.
my solution is using event navigator handler
https://wix.github.io/react-native-navigation/#/screen-api?id=listen-visibility-events-in-onnavigatorevent-handler
you can implement your 'componentWillMount' codes while 'willAppear' event id triggered, see this implementation:
export default class ExampleScreen extends Component {
constructor(props) {
super(props);
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
onNavigatorEvent(event) {
switch(event.id) {
case '`willAppear`':
// { implement your code on componentWillMount }
break;
case 'didAppear':
break;
case 'willDisappear':
break;
case 'didDisappear':
break;
case 'willCommitPreview':
break;
}
}
}
Does this answer from #bumbur help you? It defines a global variable that tracks if nav state has changed. You could insert a piece of code to see if you're in the specific tab that you are interested in. With that you could trigger a call to componentWillMount() ?
If you don't want to use redux, this is how you can store globally
information about current route, so you can both detect a tab change
and also tell which tab is now active.
https://stackoverflow.com/a/44027538/7388644
export default () => <MyTabNav
ref={(ref) => { this.nav = ref; }}
onNavigationStateChange={(prevState, currentState) => {
const getCurrentRouteName = (navigationState) => {
if (!navigationState) return null;
const route = navigationState.routes[navigationState.index];
if (route.routes) return getCurrentRouteName(route);
return route.routeName;
};
global.currentRoute = getCurrentRouteName(currentState);
}}
/>;

React navigation with Redux

I use Stack Navigator to pile up from A-B-C & I initialise my components in componentDidMount() function. When I press back on C it goes to B but nothing loads on screen. How can I restore my previous state from Redux store in my component as componentDidMount() or constructor is not called on going back.
This is how my home component loads a list
componentDidMount() {
this.getHomeItems();
}
getHomeItems = () => {
this.props.getHomeItems();
}
homeItems = () => {
return Object.keys(this.props.homeItems).map( key => this.props.homeItems[key]);
}
and in the render function I render a list like this
<List
dataArray={this.homeItems()}
renderRow={this.renderRow.bind(this)} >
</List>
To go back from C to B on hw press event i use dispatch(NavigationActions.back());
Here is my home connect function
function mapStateToProps(state){
return {
homeItems: state.home,
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(HomeActionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);