Can I subscribe to this.props.navigation.state.params? - reactjs

I am wonder if in screenA I have an object data = {} that will be changed dynamically, can I receive changes in screenB by just sending this props from screenA through this.props.navigation.navigate('screenB', {data})?
And in screenB to have a componentWillReceiveProps(nextProps) to get this changes through something like nextProps.navigation.state.param.data
Or there is a way to achieve this?

You can use onWillFocus of NavigationEvents, which fires whenever the screen is navigated to.
_willFocus = () => {
const { navigation } = this.props
const data = navigation.getParam('data', null)
if (data !== null) {
/* do something */
}
}
/* ... */
render () {
return (
<View>
<NavigationEvents onWillFocus={_willFocus()}
</View>
)
}

It is easy, just as you said: send some data navigation.navigate('screenB', { data }) and receive it in the screenB as navigation.state.params.data.
I agree with #FurkanO you probably show use Redux instead to control all the state of your app, but for simple stuff I think isn't necessary!
I made a simple snack demo to show you: snack.expo.io/#abranhe/stackoverflow-56671202
Here some code to follow up:
Home Screen
class HomeScreen extends Component {
state = {
colors: ['red', 'blue', 'green'],
};
render() {
return (
<View>
{this.state.colors.map(color => {
return <Text>{color}</Text>;
})}
<View>
<Text>Details Screen</Text>
<Button
title="Go to Details"
onPress={() => this.props.navigation.navigate('Details', { colors: this.state.colors })}
/>
</View>
</View>
);
}
}
Details Screen
class DetailsScreen extends Component {
state = {
colors: [],
};
componentWillMount() {
this.setState({ colors: this.props.navigation.state.params.colors });
}
render() {
return (
<View>
{this.state.colors.map(color => {
return <Text>{color}</Text>;
})}
<Text>Details Screen</Text>
</View>
);
}
}
Update
The question's author requested an update to add a setTimeout() to see the exact moment when the data is on the other screen, so it will look like this:
componentWillMount() {
setTimeout(() => {
this.setState({ colors: this.props.navigation.state.params.colors });
}, 3000);
}

Related

Refresh screen or component when navigate to it

I have two screens, one for displaying the records consuming an API and the other for registering.
the problem is that when I do a register and navigate to the display screen it does not update.
This is a construction of the screen:
constructor(props) {
super(props);
this.state = {isLoading: true, pendIsLoading: true, dataSource: [], contentStorageS:""}
};
fetchDados = async () => {
let usuario = await AsyncStorage.getItem("ASCOFAR_app_usuario");
try {
const response = await api.get("api/listaRegistros.php?usuario="+usuario);
const responseData = await response.data
if(responseData.status == "ERRO"){
this.setState({
isLoading: false,
dataSource: "",
})
}else{
this.setState({
isLoading: false,
dataSource: responseData,
})
}
console.log(response)
} catch (error) {
Alert.alert(error)
}
}
async componentDidMount () {
this.fetchDados();
this.atualizaState();
}
tirarLoad() {
if(this.state.isLoading == true){
return (
<ActivityIndicator size="large" color="#be152c"/>
)
}else if(this.state.dataSource == ""){
return (
<ScrollView >
<View style={{justifyContent:"center", alignItems:"center",}}>
<Image
style ={{width:150, height:150, marginTop:35}}
source={require('../assets/images/aguardando.png')}
/>
</View>
</ScrollView>
)
}else{
return (
<ScrollView>
<Text style={styles.tituloGrid}>Formularios Enviados</Text>
{this.state.dataSource.map(dados => (
<View style={styles.list} key={dados.id}>
<Text style={styles.listChild}>{dados.id}</Text>
<Text style={styles.listChild}>{dados.nome}</Text>
<Text>|</Text>
<Text style={styles.listChild}>{dados.endereco}</Text>
</View>
))}
</ScrollView>
)
}
}
<View style={styles.grid}>
{this.tirarLoad()}
</View>
I need to know how to do when navigating to this screen to update API consumption
Assuming you are using React-Navigation, did you try to addListener
focus react-navigation documentation
You could also do it by componentDidUpdate. I could not find the official documentation for doing it on 5.x. I believe it still works with 5.x. (Doc on 3.x)
import { withNavigationFocus } from "react-navigation";
componentDidUpdate(prevProps) {
if (prevProps.isFocused !== this.props.isFocused) {
this.fetchDados()
//or other similar onFocus function
}
}
export default withNavigationFocus(TabScreen);
Try re-rendering your Home screen after navigation
this.props.navigation.navigate('Home', {
onBack: () => this.refresh() //function to refresh screen,
});
import { withNavigationFocus } from "react-navigation";
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
() => {
this.refreshFetch();
this.refreshLocal();
}
);
componentWillUnmount() {
this.willFocusSubscription.remove();
}

React Component Props are receiving late. (Meteor JS)

I am working on a react-native and meteor js project.
My problem is that the props received from withTracker() function are only received in componentDidUpdate(prevProps) I don't get them in constructor or componentWillMount.
Another issue is when i pass props directly from parent to child. it receives them late due to which my component does not update
iconGroups prop comes from withTracker() method
and openSection props which i am using in this showGroupIcons()
is passed directly from parent to this component.
I want to open Accordian section that is passed to it via parent. but problem is in componentDidUpdate(prevProps) I am changing state due to which component re-renders.
openSection variable by default value is Zero. when props arrvies it value changes which i required But Accordian does not update.
Below is my code
import React, { Component } from 'react';
import Meteor, { withTracker } from 'react-native-meteor';
import {
View, Image, ScrollView, TouchableOpacity,
} from 'react-native';
import PopupDialog from 'react-native-popup-dialog';
import {Text, Icon, Input, Item, List,} from 'native-base';
import Accordion from 'react-native-collapsible/Accordion';
import { Col, Row, Grid } from 'react-native-easy-grid';
import styles from './styles';
import CONFIG from '../../config/constant';
import {MO} from "../../index";
const staticUrl = '../../assets/img/icons/';
class IconPickerComponent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
itemName: 'apple1',
activeSections: 0,
showAccordian: true,
accordianData: []
};
}
componentDidUpdate(prevProps) {
if(prevProps.iconGroups !== this.props.iconGroups) {
let images = this.props.iconGroups.map(icon => icon.images);
let flatten = [].concat.apply([], images).map(img => { return {name: img, icon: CONFIG.ICON_URL+img+'.png'} })
this.setState({ filteredItems: flatten, dataSource: flatten, accordianData: this.props.iconGroups });
}
}
componentDidMount() {
this.props.onRef(this);
}
componentWillUnmount() {
this.props.onRef(null);
}
method() {
// this.setState(...this.state,{
// searchText: ''
// })
this.iconPicker.show(); // show icon picker
}
onSearchChange(text) {
this.setState({
showAccordian: !(text.length > 0)
});
const searchText = text.toLowerCase();
const filteredItems = this.state.dataSource.filter((item) => {
const itemText = item.name.toLowerCase();
return itemText.indexOf(searchText) !== -1;
});
this.setState({ filteredItems });
}
onIconSelect(item) {
this.setState({
itemName: item,
});
this.iconPicker.dismiss();
if (this.props.onIconChanged) {
this.props.onIconChanged(item);
}
}
_renderSectionTitle = section => {
return (
<View style={styles.content}>
<Text></Text>
</View>
);
};
_renderHeader = section => {
return (
<View style={styles.accordHeader}>
<Text style={{color: 'white'}}>{this.state.showAccordian} - {section.group}</Text>
<Text>
<Icon style={styles.downArrow} name="ios-arrow-down" />
</Text>
</View>
);
};
_renderContent = section => {
return (
<View style={styles.accordContent}>
{
section.images.map((img, key) => (
<TouchableOpacity onPress={() => this.onIconSelect(img)} key={key}>
<View style={styles.iconsGrid}>
<Image style={styles.image} source={{uri: CONFIG.ICON_URL+ img + '.png'}}/>
</View>
</TouchableOpacity>
))
}
</View>
);
};
_updateSections = activeSections => {
this.setState({ activeSections });
};
hasGroupIcons() {
return this.props.iconGroups.length > 0;
};
showGroupIcons() {
if(this.state.showAccordian){
let openSection;
if(!!this.props.openSection) {
let groupIndex = this.state.accordianData.findIndex(icon => icon.group === this.props.openSection);
if(groupIndex !== -1) {
openSection = groupIndex;
} else {
openSection = 0;
}
} else {
openSection = 0;
}
return(<Accordion
sections={this.state.accordianData}
activeSections={this.state.activeSections}
renderSectionTitle={this._renderSectionTitle}
renderHeader={this._renderHeader}
renderContent={this._renderContent}
onChange={this._updateSections}
initiallyActiveSection={openSection} />);
} else {
return(<View style={{flexWrap: 'wrap', flexDirection: 'row'}}>
{
this.state.filteredItems.map((item, key) => (
<TouchableOpacity onPress={() => this.onIconSelect(item.name)} key={key}>
<View style={styles.iconsGrid}>
<Image style={styles.image} source={{uri: item.icon}}/>
</View>
</TouchableOpacity>
))
}
</View>)
}
};
render() {
return (
<PopupDialog
overlayOpacity={0.8}
overlayBackgroundColor="#414141"
dialogStyle={styles.dialogBox}
containerStyle={styles.dialogContainer}
ref={(popupDialog) => { this.iconPicker = popupDialog; }}
>
<ScrollView>
<View style={styles.dialogInner}>
<Item searchBar rounded style={styles.searchbar}>
<Icon style={styles.searchIcon} name="search" />
<Input onChangeText={this.onSearchChange.bind(this)} style={styles.inputSearch} placeholder="Search" />
</Item>
{
this.hasGroupIcons() && this.showGroupIcons()
}
</View>
</ScrollView>
</PopupDialog>
);
}
}
export default withTracker(params => {
MO.subscribe('ipSubsId3', 'IconGroups');
return {
iconGroups: MO.collection('IconGroups', 'ipSubsId3').find({}),
};
})(IconPickerComponent);
I am new to react. I am assuming when props change component re-renders.
Use this life cycle method
static getDerivedStateFromProps(prevProps, prevState) {
if(prevProps.iconGroups !== this.props.iconGroups) {
let images = this.props.iconGroups.map(icon => icon.images);
let flatten = [].concat.apply([], images).map(img => { return {name: img, icon: CONFIG.ICON_URL+img+'.png'} })
this.setState({ filteredItems: flatten, dataSource: flatten, accordianData: this.props.iconGroups });
}
}
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
Read more about this lifecycle method here
I have fixed this issue. Actually my concepts were not right. I thought props are first received in constructor and componentWillMount. But I get all props in render() and everything works fine i dont have to use any lifecycle method to use props now

React-Redux FlatList Updation

I have an AuditionsList component which displays a FlatList. In a different reducer I'm changing Settings called location and roleType. Depending on these new settings I want to refresh the AuditionList.
Here is my code:
import React from 'react';
import { Text, View, FlatList, ActivityIndicator } from 'react-native';
import { connect } from 'react-redux';
import AuditionItem from './AuditionItem';
import Auditions from './../data/Auditions';
class AuditionsList extends React.Component {
constructor(props) {
super(props);
this.state = { isLoading: true, data: [], refresh: false }
}
componentDidMount() {
this._refreshData();
}
_onRefresh() {
this.setState({ isLoading: true }, this._refreshData() );
}
_refreshData = () => {
Auditions.fetchAuditions(this.props.productionType, this.props.location, this.props.roleType).then(auditions => {
this.setState({ isLoading: false, data: this._addKeysToAuditions(auditions) });
});
}
_addKeysToAuditions = auditions => {
return auditions.map(audition => {
return Object.assign(audition, { key: audition.Role});
});
}
_renderItem = ({ item }) => {
return (
<AuditionItem
auditionId={item.objectId}
role={item.Role}
project={item.Project.Name}
productionType={item.Project.ProductionType.Type}
auditionDate={JSON.stringify(item.Date.iso)}
productionHouse={item.Project.ProductionHouse.Name}
/>
);
}
render() {
console.log("Here...");
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList onRefresh={() => this._onRefresh()} refreshing={this.state.isLoading} data={this.state.data} renderItem={this._renderItem} />
</View>
);
}
}
const mapStateToProps = state => {
return {
location: state.settings.location,
roleType: state.settings.roleType,
};
}
export default connect(mapStateToProps)(AuditionsList);
I need to call the _onRefresh() or _refreshData() function AFTER the callback to mapStateToProps (which runs successfully) but BEFORE the FlatList re-renders (which also happens successfully, but with old data). So where do I call the _onRefresh() or _refreshData() functions? Putting them in render() causes an infinite loop.
Have you tried using the ComponentDidUpdate react lifecycle method? This will fire after you receive new props, and you could make a call to this._refreshData there.
https://reactjs.org/docs/react-component.html#componentdidupdate

Performance drop when updating state

I'm building react-native app, but my problem is linked with React itself.
It's an app that connects to external JSON, fetches data, and creates react component for each of item in that JSON data, so it's like 70 child components inside 1 wrapper. App is also using Navigator and phone storage but that's a part of the question.
To visualize:
Parent component (People) has methods to operate on a DB, it fetches data, creates component for each of item in array and exposes methods to child components (Person). Each person has a "add to favourites" button, this is a method updating empty star to full one (conditional rendering) if clicked, updates state of component and whenever local state has been changed it fires parents component to update it's state - and from there parent's component saves all data to DB. So I've made a link to synchronize Child's State -> Parent's State -> Local DB (phone's memory).
Problem is, it's quite slow when updating parent's state. It freezes for 1-1.5 sec. but it's all cool if I remove method to update parent's state (I've marked that in example attached).
Question 1: How to refactor this code to fix performance issue when updating parent's (People's state)?
Question 2: I'm open to any other suggestions and lessons how to improve quality of my code.
Here's a link to visualize my code, I've just removed few non-relevant methods and styles.
https://jsfiddle.net/xvgfx90q/
class People extends React.Component {
constructor() {
super();
this.state = {
peopleData: [],
database: {}
}
}
componentDidMount() {
this.fetchApi();
this.syncDatabase();
}
// function that connects to external JSON file and parses it
fetchApi() {... it sets peopleData state after promise has been resolved}
// function called from PersonSection to pass it's state and update main state of People
syncStates(data) {
const newState = this.state;
newState.database[data.id] = data;
this.setState(newState); // <-- !! PERFORMANCE DROP HERE !!
this.saveDatabase();
}
// connects to phone's DB and updates state with result of promise
async syncDatabase() {
AsyncStorage.getItem(this.state.DBKey).then((data) => {
let newState = {};
newState.database = JSON.parse(data);
this.setState(newState);
}).catch((error) => {
return error;
})
}
// saves current state to DB
async saveDatabase() {
AsyncStorage.setItem(this.state.DBKey, JSON.stringify(this.state.database));
}
renderTeams() {
return Object.keys(this.state.peopleData).map((team) => {
return (
<TeamSection key={team} teamName={team} membersList={this.state.peopleData[team]}>
{this.renderPeople(team)}
</TeamSection>
)
})
}
renderPeople(team) {
return this.state.peopleData[team].map((people) => {
return (
<PersonSection
key={people.id}
data={people}
database={_.has(this.state.database, people.id) ? this.state.database[people.id] : false}
navigator={this.props.navigator}
syncStates={this.syncStates.bind(this)}
/>
)
})
}
render() {
return (
<ScrollView style={styles.wrapper}>
<Options filterPeople={this.filterPeople.bind(this)} />
{this.renderTeams()}
</ScrollView>
)
}
}
class PersonSection extends Component {
constructor(props) {
super(props);
this.state = {
database: {
id: this.props.data.id,
name: this.props.data.name,
favourites: this.props.database.favourites
}
}
}
// updates components state and sends it to parent component
toggleFavourites() {
const newState = this.state.database;
newState.favourites = !newState.favourites;
this.setState(newState);
this.props.syncStates(this.state.database);
}
render () {
return (
<View>
<View>
<View>
<Text>{this.props.data.name}</Text>
<Text>{this.props.data.position}</Text>
<Text>{this.props.data.ext}</Text>
</View>
<View>
<TouchableOpacity onPress={() => this.toggleFavourites()}>
{ this.state.database.favourites
? <Icon name="ios-star" size={36} color="#DAA520" />
: <Icon name="ios-star-outline" size={36} color="#DAA520" />}
</TouchableOpacity>
</View>
</View>
</View>
)
}
};
export default PersonSection;
React.render(<People />, document.getElementById('app'));`
This is not a recommended way to do it, but basically you can just update the child state instead of the parent and passing it back down.
class People extends React.Component {
constructor() {
super();
this.state = {
peopleData: [],
database: {}
}
}
componentDidMount() {
this.fetchApi();
this.syncDatabase();
}
// function that connects to external JSON file and parses it
fetchApi() {... it sets peopleData state after promise has been resolved}
// function called from PersonSection to pass it's state and update main state of People
syncStates(data) {
this.state.database[data.id] = data;
this.saveDatabase();
}
// connects to phone's DB and updates state with result of promise
async syncDatabase() {
AsyncStorage.getItem(this.state.DBKey).then((data) => {
let newState = {};
newState.database = JSON.parse(data);
this.setState(newState);
}).catch((error) => {
return error;
})
}
// saves current state to DB
async saveDatabase() {
AsyncStorage.setItem(this.state.DBKey, JSON.stringify(this.state.database));
}
renderTeams() {
return Object.keys(this.state.peopleData).map((team) => {
return (
<TeamSection key={team} teamName={team} membersList={this.state.peopleData[team]}>
{this.renderPeople(team)}
</TeamSection>
)
})
}
renderPeople(team) {
return this.state.peopleData[team].map((people) => {
return (
<PersonSection
key={people.id}
data={people}
database={_.has(this.state.database, people.id) ? this.state.database[people.id] : false}
navigator={this.props.navigator}
syncStates={this.syncStates.bind(this)}
/>
)
})
}
render() {
return (
<ScrollView style={styles.wrapper}>
<Options filterPeople={this.filterPeople.bind(this)} />
{this.renderTeams()}
</ScrollView>
)
}
}
class PersonSection extends Component {
constructor(props) {
super(props);
this.state = {
database: {
id: this.props.data.id,
name: this.props.data.name,
favourites: this.props.database.favourites
}
}
}
// updates components state and sends it to parent component
toggleFavourites() {
const newState = this.state.database;
newState.favourites = !newState.favourites;
this.setState(newState);
this.props.syncStates(this.state.database);
}
render () {
return (
<View>
<View>
<View>
<Text>{this.props.data.name}</Text>
<Text>{this.props.data.position}</Text>
<Text>{this.props.data.ext}</Text>
</View>
<View>
<TouchableOpacity onPress={() => this.toggleFavourites()}>
{ this.state.database.favourites
? <Icon name="ios-star" size={36} color="#DAA520" />
: <Icon name="ios-star-outline" size={36} color="#DAA520" />}
</TouchableOpacity>
</View>
</View>
</View>
)
}
};
export default PersonSection;
React.render(<People />, document.getElementById('app'));

Comments won't reload with new props?

I'm having some trouble with updating some props. My app has a feed screen (listView) of posts (listItems). I’m also using redux.
If I click on a post, it'll take me to the comments screen. As it does so, it passes through the props of the listItem and fully displays the post text, image, author etc.
When in the commentScreen, If I click on the content (image/text), it'll take me through to the editPost screen. As it does this, it takes the props (image/text/uid) and populates the textInput and image field so the user can edit it. When done, I can click the saveEdit button. This will take the text, image and uid and update the post within firebase via an action. It will also pop() the screen and take me back to the commentsScreen.
The problem is, the edited post can't be seen. The original text/image is still there. If I look in my Firebase console, I can see that the edit HAS been saved.
If I pop the commentsScreen and go back to the feed, it's up to date. I can see that the post has been successfully edited. If I then click on the post again, it'll take me back to the commentsScreen, showing the updated post.
I guess this is because the listView in the feed itself is watching the firebase reference for the posts? and as such the post in the commentScreen won't change until I revisit the feed so it can update?
How can I work around this? Do I need to be doing something with componentWillUpdate or componentWillReceiveProps or something?
I have tried firing different get requests to firebase at different points but I've had no success so far.
This is my Feed:
class FeedComponent extends Component {
componentWillMount() {
this.props.postsFetch();
this.createDataSource(this.props);
}
componentWillReceiveProps(nextProps) {
// nextProps are the next set of props that this component
// will be rendered with
// this.props is still the old set of props
this.createDataSource(nextProps);
}
createDataSource({ posts }) {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(posts);
}
renderRow(posts) {
return <PostListItem posts={posts} />;
}
render() {
return (
<ListView
{...this.props}
enableEmptySections
removeClippedSubviews={false}
dataSource={this.dataSource}
renderRow={this.renderRow}
style={{ backgroundColor: '#D8D8D8' }}
/>
);
}
}
const mapStateToProps = state => {
const posts = _.map(state.feed, (val, uid) => {
return { ...val, uid };
});
return { posts };
};
export default connect(mapStateToProps, { postsFetch })(FeedComponent);
This then performs this action:
export const postsFetch = () => {
return (dispatch) => {
firebase.database().ref('/social/posts')
.on('value', snapshot => {
dispatch({ type: POSTS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
and dispatches it to the reducer.
Here is the listItem:
#withNavigation
class PostItem extends Component {
state = {
result: ''
};
goToComments() {
this.props.navigation
.getNavigator('root')
.push(Router.getRoute('comments', { posts: this.props.posts }));
}
render() {
const {
text,
author,
vote_count,
comment_count,
image,
created_at
} = this.props.posts;
const fromNow = moment(created_at).fromNow();
return (
<Card style={styles.cardStyle}>
<TouchableOpacity onPress={this.goToComments.bind(this)}>
//Text/Image component using props
</TouchableOpacity>
</Card>
);
}
}
There, you can see that there's an goToComments function, it passes the post props
This is the comment screen:
class Comments extends Component {
render() {
return (
<View style={{ flex: 1 }}>
<PostSection
post={this.props.route.params.posts}
/>
</View>
);
}
}
This feeds the props into the postSection component which displays the post in the comments screen. It is this that doesn't update after I go to the edit screen, make the edit and come back:
#withNavigation
class PostSection extends Component {
editPost() {
this.props.startEdit(this.props.post);
this.props.navigation
.getNavigator('root')
.push(Router.getRoute('editPost'));
}
renderImage(image) {
if (image) {
return (
<View style={styles.imageContainer}>
<Image
style={styles.imageSize}
source={{ uri: image }}
/>
</View>
);
}
}
renderText(text) {
if (text) {
return <PostText text={text} />;
}
}
renderPost(text, image) {
// if (POST BELONGS TO CURRENT USER) {
return (
<TouchableOpacity onPress={this.editPost.bind(this)}>
{this.renderText(text)}
{this.renderImage(image)}
</TouchableOpacity>
);
}
render() {
const {
text,
author,
vote_count,
comment_count,
image,
created_at
} = this.props.post;
const fromNow = moment(created_at).fromNow();
return (
<View>
<TopBar
author={author}
fromNow={fromNow}
/>
{this.renderPost(text, image)}
<BottomBar
voteCount={vote_count}
commentCount={comment_count}
/>
</View>
);
}
}
export default connect(null, { startEdit })(PostSection);

Resources