How to get navigation parameter before re-render the page - react-native

I'm using react navigation to navigate between pages. I have 2 pages. Let's call them A and B. I have some cards on page A and has touchable opacities on cards to navigate to page B with an id (using this id for getting data from the server).
On first navigation from A to B everything works well. But when i go back to page A from sidemenu and select another card (i mean send another id as parameter to page B) its show me same page with first navigate. I figured out navigation parameter changes after the render.
i tried that lifecycle;
export class TanksContent extends React.Component {
constructor(props){
super(props);
this.state = {
isLoading:true,
tanks:[],
}
}
componentDidMount(){
this.getData();
}
componentWillUnmount(){
this.setState({
isLoading: true
});
}
componentWillUpdate(){
this.getData();
}
getData(){
//fetching data on here from server and set state for loading and data.
}
render(){
if(this.state.isLoading){
return(
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
return(
// screen
)
Its refreshing the page again and again... So its not working well. How could i stop rendering until parameter has changed? (its possible to user choose the same parameter again)

I solved that problem.
componentDidUpdate(prevProps) {
if(this.props.id!==this.state.id){
this.setState({
id: this.props.id,
isLoading: true
});
this.getData();
}
}
with that function. if someone need help about that just comment and i will do my best for you.

Related

Component is not showing changes after render in React Native

Newbie to React Native here. I am trying to use Redux for the mobile app for React Native. There are couple of components (app screens). I am choosing the item from one screen (has list of items) and provide it to the next one as param to show its details. When I do it first time, everything is correct. But if I choose another item in the list screen, the second screen does not show the correct item details. Instead it shows the details for the previously selected item.
First screen provides the item and navigates:
this.props.navigation.navigate('DetailsScreen', { item: item.value } );
Second screen:
class DetailsScreen extends Component{
constructor(props) {
super(props);
this._item = this.props.navigation.getParam(‘item’);
}
componentDidMount() {
this.props.dispatchData(this._item);
}
render(){
return (
<View>
<SectionList
sections={this.props.layout}
stickySectionHeadersEnabled={false}
renderItem={({item}) => (
<ListItem
title={item.title}
/>
)}
renderSectionHeader=
{
({section}) => (
<ListItem
bottomDivider={true}
/>)
}
keyExtractor={(item, index) => index}
/>
</View>
);
}
}
const mapStateToProps = state => {
return {
layout: state.detailsReducer.layout,
};
};
const mapDispatchToProps = {
dispatchData: (item) => getLayout(item)
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(DetailsScreen);
The odd thing is that I can see from the logs that new layout (from the newly selected item) is rendered, meaning the render() function in DetailsScreen is called with the correct up-to-date data. But I can not see those changes on the screen. What am I missing? Is this because of navigation saves the previous instance of the screen? Or something else?
Is this because of navigation saves the previous instance of the screen? Or something else?
answer is yes, react navigation, what it does is if you do navigation.navigate('home') to a page , then for the first time it renders it with every lifecycle method, but the second. time it doesnt call componentDidMount since it has been taken from navigation stack. What you can do is try using
this.props.navigation.push('DetailsScreen', { item: item.value } );
It will push the screen to top of stack every time its called.
Hope it helps feel free for doubts
componentDidMount(){
navigation.addListener("willFocus", () => {
// write what you want to be rendered in here
});
}

Set navigation parameter on goBack in react native

I have two screens A and B.
When i navigate from Screen A to Screen B I have passed Parameter "onGoBack":false.
On B Screen I have setParameter "onGoBack" as true Which is not working. On go back from Screen B I always receiving "onGoBack" parameter as false only.
Please help to solved this problem.
How can I received updated parameter value on go back.
Below the code I have used to set parameter on Go back from screen B
componentDidMount() {
this.props.navigation.setParams({onGoBack: true});
console.log('Updated Go Back Value: '+this.props.navigation.state.params.onGoBack);
this.backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
this.props.navigation.goBack();
return true;
});
}
You can get the navigation state params whenever a screen is mounted so try this:
import { NavigationEvents } from 'react-navigation';
render() {
return (
<View style={{ flex: 1 }}>
<NavigationEvents
onWillFocus={() => {
// Do your things here
}}
/>
</View>
);
}
Or else,
You can send the params when you're navigating from screen A to screen B:
this.props.navigation.navigate('screenB', { onGoBack: false });
So screen A is already mounted on Stack, so you can use the navigate function to send params. Here, the navigate function will act as goBack(); it won't push a new screen in the stack
this.props.navigation.navigate('screenA', { onGoBack: true });

React Native Redux store dispatches reducers correctly, but doesn't update UI component

Working on a cancer treatment app in react native:
Current functionality: when I move the sliders and change the date on my app, it dispatches changes to the redux store successfully. Unfortunately, my UI doesn't update, even though I am calling the same store from the presentational components that I called for dispatch.
That results in this:
GIF of redux store changing while UI is static
Printing via
store.getState();
store.subscribe(() =>
console.log(store.getState())
);
I tried using subscription, but it seems like this isn't the right way to go about this. Thoughts?
snippets from my code (all in one small file, linked below)
Action
//action
function set_num_treatments(num) {
return {
type: SET_NUM_TREATMENTS,
num: num
}
}
setting the title
SET_NUM_TREATMENTS = "SET_NUM_TREATMENTS"
main reducer
function main_reducer(state = initialState, action) {
switch (action.type) {
case SET_PAGE_VIEW:
return Object.assign({}, state, {
current_page: action.page_of_interest
})
case SET_NUM_TREATMENTS:
return Object.assign({}, state, {
num_treatments: action.num
})
case SET_INTER_TREATMENT_INTERVAL:
return Object.assign({}, state, {
inter_treatment_interval: action.weeks_between_treatments
})
case SET_TREATMENT_START_DATE:
return Object.assign({}, state, {
treatment_start_date: action.date
})
default:
return state
}
return state
}
Here's where I start the store & produce the printing functionality
let store = createStore(main_reducer);
store.getState();
store.subscribe(() =>
console.log(store.getState())
);
here's the presentational components
class TreatmentSettings extends React.Component {
constructor(props){
super(props)
}
render() {
const props = this.props
const {store} = props
const state = store.getState()
return(
<View style={styles.treatment_option_slider_card}>
<Text style={styles.my_font, styles.tx_settings_header}>{state.num_treatments} Treatments</Text>
<Slider step={1} minimumValue={1} maximumValue={20} value={12}
onValueChange={(num_treatments) => {store.dispatch(set_num_treatments(num_treatments))}} />
<Text style={styles.my_font, styles.tx_settings_header}>X Weeks Between Treatments</Text>
<Slider step={1} minimumValue={1} maximumValue={4} value={2} style={{marginBottom:60}}
onValueChange={(value) => {store.dispatch(set_inter_treatment_interval(value))}}
/>
</View>
)
}
}
These final two components hold the main containers for the app
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={createStore(main_reducer)}>
<AppContainer />
</Provider>
);
}
}
class AppContainer extends React.Component {
constructor(props){
super(props)
}
render(){
return(
<View style={styles.container}>
<TreatmentSettings store={store} />
<Text>footertext</Text>
</View>
)
}
}
the one gist file is here if you want to see it all: https://github.com/briancohn/learning-redux/blob/navigation_addn/App.js
I really appreciate the help—
Thanks in advance!
-Brian
I think the way you are updating the store is fine but there’s something wrong with how your components are listening to the changes.
It seems you meant to use connect from react-redux for the containers to connect to the store. Then you can use mapStateToProps to get the data from the store to pass into the components as props. Check https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options for example.

Refresh overview scene after changing state in another scene with react / redux / react-native-router-flex

Most simplified working example provided in github !!!
I have a simple app to learn building apps with react native and redux. From my understanding if you display data from the redux state in your render method and then values of this state is changed, then the value will be changed as well and react rerenders all components which needs to be rerendered due to the state change.
I have the application available on github: https://github.com/schingeldi/checklist
Its really simple. I have an overview, if you click on the status of an entry, you get to a detailed page. If you click on "Mark xxx" the status in changed in the redux state (according to logs) but its not refreshed in the overview scene.
Basically I have an Overview.js:
class Overview extends Component {
constructor(props) {
super(props);
this.state = {fetching:false};
}
entries() {
// console.log("Overview");
// console.log(this.props);
// console.log(this.props.entries);
return Object.keys(this.props.entries).map(key => this.props.entries[key]);
}
componentDidMount() {
this.setState({fetching:true});
this.props.actions.getEntries()
.then( (res) => {
this.setState({fetching: false});
})
}
handleChange(entryId) {
Actions.detail({id: entryId});
}
render() {
return (
<View>
<ScrollView>
{ !this.state.fetching && this.entries().map((entry) => {
return (
<TouchableHighlight key={entry.id}>
<View >
<Text>{entry.name}</Text>
<TouchableHighlight onPress={(entryId ) => this.handleChange(entry.id)}><Text>{entry.status}</Text></TouchableHighlight>
<Text>---------------------------</Text>
</View>
</TouchableHighlight>
)
}
)
}
{this.state.fetching ? <Text>Searching </Text> : null }
</ScrollView>
</View>
)}
}
function mapStateToProps(state) {
return {entries: state.default.entries };
}
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(actions,dispatch)};
}
export default connect(mapStateToProps, mapDispatchToProps)(Overview);
When clicking on the Status ( {entry.status} ) I open another Scene Details.js:
class Detail extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentWillMount() {
this.setState({
entry: this.props.entries[this.props.id]
})
}
patchEntry(newStatus) {
console.log("Details: patchEntry with " + this.props.id +" and " + newStatus );
this.props.actions.patchEntry(this.props.id, newStatus);
}
render() {
return (
<View>
<Text>{this.state.entry.name}</Text>
<TouchableHighlight onPress={() => this.patchEntry('done')}><Text>Mark done</Text></TouchableHighlight>
<TouchableHighlight onPress={() => this.patchEntry('cancelled')}><Text>Mark cancelled</Text></TouchableHighlight>
</View>
)
}
}
function mapStateToProps(state) {
console.log(state);
return {entries: state.default.entries };
}
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(actions,dispatch)};
}
export default connect( mapStateToProps, mapDispatchToProps)(Detail);
And I have an action and a reducer which are called perfectly fine when one of the TouchableHighlights are pressed. I even see in the logs that the state is changed when outputting the whole state.
But my question is, how do I get the status refreshed on the Overview scene, once I got back (pop) from the Detail scene?
If you need anymore information let me know, but it should be simple to reproduce as I wrote a whole working app. Just clone, npm install and run it.
Thanks a lot for your help.
I did a quick look into your code and here are some suggestions/information.
In you Detail.js file you're setting your state once the component is mounted.
When you update your redux store and get the refreshed props, it won't update your UI because it's reflecting your state, and your state won't get the new value because you're only setting it on componentWillMount method. Check more information here in the docs.
Also it seems it's not very clear for you when to use the React component's state.
In this example, from Detail.js file you don't need the component's state at all. You can compute that value directly from the properties.
Ex:
render() {
const entry = this.props.entries[this.props.id];
return (
<View>
<Text>{entry.name}</Text>
<TouchableHighlight onPress={() => this.patchEntry('done')}><Text>Mark done</Text></TouchableHighlight>
<TouchableHighlight onPress={() => this.patchEntry('cancelled')}><Text>Mark cancelled</Text></TouchableHighlight>
</View>
)
}
You could even do that inside your mapStateToProps function. More info here.
Ex:
function mapStateToProps(state, ownProps) {
return {
entries: state.default.entries,
entry: state.default.entries[ownProps.id],
};
}
It seems your Overview.js file is OK regarding the UI being updated, because it's render method is reflecting the props and not it's state.
UPDATE 06/27
I've just checked your reducers and you may have some fixes to do there as well.
case ENTRY_PATCHING:
let patchedEntries = state.entries;
patchedEntries[action.data.entryId].status = action.data.newStatus;
return {...state,
entries: patchedEntries
}
In this reducer you're mutation your state, and you must not do that. The redux store can't be mutated. You can check more details about it here http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html
So, fix example:
case ENTRY_PATCHING:
const patchedEntry = {
...state.entries[action.data.entryId],
status: action.data.newStatus
}
return {
...state,
entries: {
...state.entries,
[action.data.entryId]: patchedEntry,
}
}

React Native Change Page after 5 seconds

I'm newbie in react native and I don't know how to change page after 5 seconds.
I create an android.index.js file that will navigate to LandingPage.js. What I want to do is, when the LandingPage being loaded, it will wait for 5 seconds and then redirect / navigate to another page.
index.android.js
export default class DefaultProject extends Component {
render() {
return (
<Navigator
renderScene={(route, navigator) =>
<LandingPage/>
}
/>
)
LandingPage.js
export default class LandingPage extends Component {
render() {
return (
<Image source={require('./images/event3.jpeg')}
style={styles.container} />
//How to redirect to another page from here after 5 secs?
);
}
}
You can use a simple setTimeout, as you would in a standard JS setup:
export default class LandingPage extends Component {
componentDidMount(){
// Start counting when the page is loaded
this.timeoutHandle = setTimeout(()=>{
// Add your logic for the transition
}, 5000);
}
componentWillUnmount(){
clearTimeout(this.timeoutHandle); // This is just necessary in the case that the screen is closed before the timeout fires, otherwise it would cause a memory leak that would trigger the transition regardless, breaking the user experience.
}
render() {
return (
<Image source={require('./images/event3.jpeg')}
style={styles.container} />
//How to redirect to another page from here after 5 secs?
);
}
}
I'm using lodash for this:
export default class Splash extends React.Component {
constructor(props) {
super(props);
}
async componentWillMount() {
_.delay(() => this.props.navigator.replace({ component: 'login' }), 1000);
}
render() {
return (
...
);
}
}
This will only work if you have a Navigator set up. Check this article:
React Native Navigator — Navigating Like A Pro in React Native