How to update a stack navigator if its properties change? - react-native

The situation is the following, suppose I have this component.
function HomeScreen({ navigation: { navigate } }) {
const [data, setData] = useState(somedata)
return (
<View>
<Text>This is the home screen of the app</Text>
<Button
onPress={() =>
navigate('Profile', { name: data.name})
}
title="Go to Brent's profile"
/>
</View>
);
}
This component has a function that automatically updates the state when it changes in the database, (which I did not place, since it is only an example) and it works correctly, because when printing from the console, I realize that the component " HomeScreen" is rendered again, the problem is that when I navigate to the "Profile" component and update in the database, the "Profile" component is not rendered again or update then, however if the "HomeScreen" component is updated. How can I do to that somehow send a signal to the "Profile" component and it can be updated with the data from the database?

You need updated data and for that
function HomeScreen({ navigation: { navigate } }) {
const [data, setData] = useState(somedata);
useEffect(()=>{
//Console.log(data);
// Here you'll get updated data and solve your issue.
},[data]);
return (
<View>
<Text>This is the home screen of the app</Text>
<Button
onPress={() =>
navigate('Profile', { name: data.name})
}
title="Go to Brent's profile"
/>
</View>
);
}

Related

how to finish modal animation in react-native-modal componentwillunmount?

I am looking for a solution to delay RN component unmount until react-native-modal animationOut is completed. This animation is triggered by setting isVisible=false.
With my current implementation the animation performs as expected on componentWillMount hook, however the problem is in componentWillUnmount the alert unmounts without an animation. I suspect this is because the component unmounts before the animation even has a change to start.
Here's the alert component:
//modal component
import Modal from "react-native-modal";
export default CustomAlert = props => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
setIsVisible(true)
return () => setIsVisible(false);
}, [])
return (
<Modal
isVisible={isVisible}
animationIn={'slideInUp'}
animationOut={'slideOutDown'}
>
<View
...
</View>
</Modal>
)
};
Here's how the custom alert needs to be used:
export default ApplicationScreen = ({ navigation }) => {
const [alert, setAlert] = useState(null);
...
const doSomething = () => {
...
//show alert
setAlert({ title: 'title', message: 'message'});
...
//hide alert
setAlert(null);
...
}
...
return (
<SafeAreaView style={styles.container}>
{alert &&
<CustomAlert
title={alert?.title}
message={alert?.message}
...
/>
}
...
</SafeAreaView>
)
}
The problem is that you are containing the CustomAlert in a conditional rendering. Once the alert is set to false, the Modal would dismount directly.
You could pass the alert state into CustomAlert as property and then into Modal. The 's property isVisible would hanlde the render of the modal for you with animation.
ApplicationScreen:
<SafeAreaView style={styles.container}>
<CustomAlert
title={alert?.title}
message={alert?.message}
isAlertVisible={alert? true : false}
/>
</SafeAreaView>
in CustomAlert:
return (
<Modal
isVisible={props.isAlertVisible}
animationIn={'slideInUp'}
animationOut={'slideOutDown'}
>
<View
...
</View>
</Modal>
)

How can I pass a const to an other screen in react native?

I have an filtered Array "newAsanasAnfaenger" which I want to pass to another screen to use it for a slide show. How can I pass it to for exampe another class called "UebungenAnzeige"? How can I pass a whole array? Or is there a way to export two things from one file (e.g. class and const)?
render() {
const {category} = this.props.route.params;
const { navigation } = this.props;
const newAsanasAnfaenger = asanas.filter(asana => asana.kategorie == category);
return (
<View>
<View style={stylesUebungen.container2}>
<FlatList
data={newAsanasAnfanger}
renderItem={this.renderEntry}
keyExtractor={item => `${item.id}`}
/>
<Button title="Go back" onPress={() => this.props.navigation.navigate('Auswahl')} />
</View>
</View>
);
this.props.navigation.navigate('Auswahl', { myProp: newAsanasAnfaenger } )}
Then in your other component:
const { myProp } = this.props.route.params;
You can find more information about passing parameters to routes in the React Navigation documentation: https://reactnavigation.org/docs/params/
Your second question seems unrelated to the first, but the answer is yes, you can export multiple things from one file. For example:
export const myVar = 1;
export function myFunc() { return true; }

React Native - Rerunning the render method

I have a file here that defines an icon for the title.
static navigationOptions = ({ navigation }) => {
return {
headerRight: () => (<HomeHeaderIcon/>)
}
};
HomeHeaderIcon.js
export default class HomeHeaderIcon extends Component {
async componentDidMount(){
const token = await AsyncStorage.getItem('token');
this.setState({token});
}
state={
token:null
};
render() {
return (
<View>
{
this.state.token ===null
?
(
<TouchableOpacity
onPress={() => (NavigationService.navigate("LogStack"))}>
<Icon name="ios-power" size={30} style={{color: "white",marginRight:wp("5%")}}/>
</TouchableOpacity>
)
:
(
<TouchableOpacity
onPress={() => (NavigationService.navigate("Profile"))}>
<Icon name="ios-home" size={30} style={{color: "white",marginRight:wp("5%")}}/>
</TouchableOpacity>)
}
</View>
);
}
}
The system works exactly as I want. If there is a token, I say icon1 or icon2 show. The problem is I do this in componentDidMount, the icon does not change without refreshing the page. How do I render it again?
componentDidMount is called, as the name suggests, just once, when the component is mounted. Use componentDidUpdate to decide how your component behaves based on what piece of props or state has changed.
Read the documentation for more information regarding lifecycle methods.

React Navigation Back Button Doesnt do anything

Basically what the title says. I am trying to create a back button by passing the current page routeName into the navigation state and then on the detail page I retrieve the params and pass the routeName into the goBack button. But when I click the button it doesnt do anything. I checked to see if the routeName of the previous page was in the state but it still doesnt do anything.
List Screen
Here is my Button, I am passing an id from a reducer and the routeName of the currentscreen into the navigation params
renderList = () => {
const { lists } = this.props;
if(lists) {
return (lists.data.map(el => {
return (
<TouchableOpacity
key={el.id}
onPress={ () => this.props.navigation.navigate('PostItem', { nid: el.id, prevScreen: this.props.navigation.state.routeName})}>
<Text>Next Screen</Text>
</TouchableOpacity>
)}
))
}
}
ListDetail Screen
class listDetail extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.loadPost('article', this.props.navigation.state.params.nid + '?include=field_image')
}
render() {
const { goBack } = this.props.navigation;
return (
<View>
<Button title="Back" onPress={() => goBack(this.props.navigation.state.params.prevScreen)} />
{ this.renderPost() }
</View>
)
}
}
any suggestions?
The parameter in goBack refers to the key not the routeName.
From the reactnavigation goBack docs
Note -- a key is not the name of the route but the unique identifier you provided when navigating to the route. See navigation key.
So in your example I'd try to call navigate with a key parameter
For renderList onPress could be
onPress={ () => this.props.navigation.navigate({ routeName: 'PostItem', key: `PostItem_${el.id}`, params: { nid: el.id } })}>
Then in ListDetail
<Button title="Back" onPress={() => goBack(`PostItem_${this.props.navigation.state.params.nid}`)} />

React Native, render function not rendering, although the trace indicates that passes through it

Solution added at the end of this question, inspired by https://github.com/facebook/react-native/issues/6268 from #grabbou
The curtain rises and the first scene 'Scene1' appears. Scene1 is a presentational component wrapped with 'connect' from react-redux framework to bind the status and actions to their props.
Actions work perfectly well and renders the state, the counter, on the screen.
Cliking forward to the second scene 'Scene2', exactly the same as the first component, but the props (the same as Scene1) are passed through passProps in renderScene within the Naviagator.
Every thing is OK, the actions are dispatched correctly, you can see on the trace, render function is invoked for painting the counter, again you can see in the trace, but DOES NOT WORK!. The inner component logs that is in the Scene1! What's wrong?
This is the trace, after going directly to Scene2 and click twice on <+> to increment the state.
It's a bug-Native React?
I am using
"react-native": "0.19.0",
"react-redux": "4.1.2",
"redux": "3.1.7",
This is all the code, if you can help me.
There are no concession to stylize the presentation, so the result on the screen is very simple.
1. The simple code of index.ios.js
'use strict';
import React, {
AppRegistry,
} from 'react-native';
import App from './src/App'
AppRegistry.registerComponent('App', () => App);
And 2. this is the code of the App.js:
'use strict';
import React, {
Navigator,
Component,
View, ListView, ScrollView,
Text, TouchableOpacity
} from 'react-native';
import { Provider, connect } from "react-redux";
import { createStore, applyMiddleware, combineReducers, bindActionCreators } from "redux";
import thunkMiddleware from "redux-thunk";
import createLogger from "redux-logger";
2.1 The redux part
// REDUX BEGIN
//Actions
const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'
//Actions creators
const increment = () => ({ type: INCREMENT })
const decrement = () => ({ type: DECREMENT })
//Redux Initial State
const initialState = {
counter: 0
}
//Reducer
const reducer = (state = initialState, action = {}) => {
let delta = 1
switch (action.type) {
case DECREMENT: delta = -1;
case INCREMENT:
return Object.assign({}, state, { counter: state.counter+delta })
default:
return state
}
}
//Redux Middelware
const loggerMiddleware = createLogger();
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
)(createStore);
//Wrapper to bind state and actions to props on Presentational Component
const connectComponent = (component) => connect(
(state) => ({
counter: state.counter
}),
(dispatch) => ({
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement())
})
)(component)
// REDUX END
2.2 The App root, with the Provider and the Navigator
// APP
export default class App extends Component {
render () {
return (
<Provider store={createStoreWithMiddleware(reducer, initialState)}>
<Navigator style={{flex: 1}}
initialRoute={{
name: 'Scene1',
component: connectComponent(Scene1),
}}
renderScene={ (route, navigator) => {
const Component = route.component;
return (
<View style={{flex: 1, marginTop:40}}>
<Component navigator={navigator} route={route} {...route.passProps} />
</View>
);
}}
/>
</Provider>
)
}
}
2.3.
The inner Component in both scenes to render the counter.
Has some traces, to show that the shouldComponentUpdate is triggered and return True (you has to Update!) with the time traced to show that is invoqued just some milliseconds after an action is dispatched.
And other to show that the render function is reached, but doesn't not render with in the Scene2.
The trace show that this component always he thought that was the Scene1!!
class Counter extends Component{
constructor (props, context){
super(props, context);
}
shouldComponentUpdate(nextProps, nextState){
//Begin log
const repeat = (str, times) => (new Array(times + 1)).join(str);
const pad = (num, maxLength) => repeat(`0`, maxLength - num.toString().length) + num;
const formatTime = (time) => `# ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`;
console.log('shouldComponentUpdate '+this.props.route.name+ ': '+ (nextProps.counter !== this.props.counter) +' '+formatTime(new Date()));
//End log
return nextProps.counter !== this.props.counter;
}
render() {
console.log('onRender: '+this.props.counter);
return (
<View>
<Text style={{fontSize: 100}}>{this.props.counter}</Text>
<TouchableOpacity onPress={()=>{this.props.increment()}} ><Text style={{fontSize: 40}}>{'<'}+{'>'}</Text></TouchableOpacity>
<TouchableOpacity onPress={()=>{this.props.decrement()}} ><Text style={{fontSize: 40}}>{'<'}-{'>'}</Text></TouchableOpacity>
<Text>----</Text>
</View>
)
}
}
2.4.
The two scenes, are equals, just the button to forward or backward
class Scene1 extends Component {
render() {
return (
<View>
<Text style={{fontSize: 40}}>Scene1</Text>
<Counter {...this.props}/>
<TouchableOpacity onPress={()=>{
this.props.navigator.push({
name: 'Scene2',
component: Scene2,
passProps: {...this.props}
})
}}>
<Text style={{fontSize: 20}}>{'<'}Forward{'>'} to Scene2</Text>
</TouchableOpacity>
</View>
)
}
}
class Scene2 extends Component {
render() {
return (
<View>
<Text style={{fontSize: 40}}>Scene2</Text>
<Counter {...this.props}/>
<TouchableOpacity onPress={()=>{
this.props.navigator.pop()
}} >
<Text style={{fontSize: 20}}>{'<'}Back{'>'} to Scene1</Text>
</TouchableOpacity>
</View>
)
}
}
At the end some 'hard copy' to show the 'show'
The Scene2 showing the counter, and the two buttons to dispatch actions.
Clicking theses actions doesn't render the counter, but the actions are dispatched correctly.
After just going to Scene2 and two clicks on <+> to increment the counter.
The Counter component is his trace show the route.name, but it it show is on Scene1! What is wrong here?
Well, the play is over, the curtain has fallen.
It is a very dramatic scene. (Just the Scene2)
I wonder why it does not work.
Native React issue?
Thanks to all
The Solution
from https://github.com/facebook/react-native/issues/6268
#grabbou inspired the changes, he proposes wrap the all App as a Container and then pass Store and Actions as simple props to all Scenes.
To make these changes create a new component the RootComponent and render the App connected to the Redux Store and Actions like this.
export default class RootComponent extends Component {
render () {
const AppContainer = connectComponent(App); //<< App has to be container
return (
<Provider store={createStoreWithMiddleware(reducer, initialState)}>
<AppContainer/>
</Provider>
)
}
}
Then App change removing the Provider and just passing the Scene1 as dumb component, and renderScene pass {...this.props} insted of {...route.passProps}
class App extends Component {
render () {
return (
<Navigator style={{flex: 1}}
initialRoute={{
name: 'Scene1',
component: Scene1,
}}
renderScene={ (route, navigator) => {
const Component = route.component;
return (
<View style={{flex: 1, marginTop:40}}>
<Component navigator={navigator} route={route} {...this.props} />
</View>
);
}}
/>
)
}
}
The remove passProps from navigator.push in Scene1, because already are passed as default in renderScene
<TouchableOpacity onPress={()=>{
this.props.navigator.push({
name: 'Scene2',
component: Scene2,
//passProps: {...this.props}
})
}}>
And this is all folks!
Thanks
NOTE: This is merely a copy/paste of the author provided solution above.
from https://github.com/facebook/react-native/issues/6268
#grabbou inspired the changes, he proposes wrap the all App as a Container and then pass Store and Actions as simple props to all Scenes.
To make these changes create a new component the RootComponent and render the App connected to the Redux Store and Actions like this.
export default class RootComponent extends Component {
render () {
const AppContainer = connectComponent(App); //<< App has to be container
return (
<Provider store={createStoreWithMiddleware(reducer, initialState)}>
<AppContainer/>
</Provider>
)
}
}
Then App change removing the Provider and just passing the Scene1 as dumb component, and renderScene pass {...this.props} insted of {...route.passProps}
class App extends Component {
render () {
return (
<Navigator style={{flex: 1}}
initialRoute={{
name: 'Scene1',
component: Scene1,
}}
renderScene={ (route, navigator) => {
const Component = route.component;
return (
<View style={{flex: 1, marginTop:40}}>
<Component navigator={navigator} route={route} {...this.props} />
</View>
);
}}
/>
)
}
}
The remove passProps from navigator.push in Scene1, because already are passed as default in renderScene
<TouchableOpacity onPress={()=>{
this.props.navigator.push({
name: 'Scene2',
component: Scene2,
//passProps: {...this.props}
})
}}>
And this is all folks!
Thanks