Component is not showing changes after render in React Native - 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
});
}

Related

Warning when switching to another screen if the user has authorization

When I open the application and if the isAuth variable is true, it means the user has authorization, then I want to go to the provider screen.
My authorization component.
export const LoginScreen: NavigationStackScreenComponent<NavigationParams> = observer(({ navigation }: NavigationParams) => {
useEffect(() => {
AuthState.checkAuthentication();
}, []);
if (AuthState.isAuth) {
navigation.navigate('Provider');
}
return <View style={styles.body}>{LoaderState.loading ? <LoaderComponent /> : <AuthComponent />}</View>;
});
My provider component.
export const ProvidersScreen: NavigationStackScreenComponent<NavigationParams> = observer(({ navigation }: NavigationParams) => {
useEffect(() => {
ProvidersState.setProviders();
}, []);
return (
<View style={styles.body}>
{LoaderState.loading ? (
<LoaderComponent />
) : (
<ItemListComponent itemsList={ProvidersState.providersList} />
)}
</View>
);
});
But I get a warning. I understand that it is associated with the react-navigation library.
How to switch to another screen using a conditional statement?
Functional components are basically the equivalent to the render() function in class components. You are calling navigate before the component gets render and that's a problem.
You can either call the navigate inside your useEffect or put the conditional logic in the parent view.
In the first case, navigate will be called AFTER the component renders
useEffect(() => {
AuthState.checkAuthentication();
if (AuthState.isAuth) {
navigation.navigate('Provider');
}
}, []);
so it will be visible for half a second and then change. If you'd like to avoid this, then go for the second option.
You can put the condition in the parent. Something like
<View>{AuthState.isAuth ? <ProvidersScreen /> : <LoginScreen />}</View>

How to get navigation parameter before re-render the page

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.

react-navigation squash navigation stack but preserve rendering?

I'm running in to an issue with react-navigation and a basic StackNavigator where I'd like to navigate back to HomeScreen while preserving the initial render of that component and not "reset" it. It would be compared to multiple goBack() calls until HomeScreen is hit, but in one navigation.
App structure:
- StackNavigator
- HomeScreen
- ChannelScreen
- VideoScreen
There is, however cyclical navigation on ChannelScreen, where you can click another related section and get to another ChannelScreen.
Quickly, the StackNavigator becomes: HomeScreen => ChannelScreen=> ChannelScreen=> ChannelScreen => etc...
We wanted an easy way to get back to HomeScreen, from any ChannelScreen so I employed a basic reset action:
export const resetStackNavigate = (navigation, routeName, params = {}) => {
const resetAction = NavigationActions.reset({
index: 0,
params,
actions: [
NavigationActions.navigate({ routeName })
]
})
navigation.dispatch(resetAction);
}
which works amazingly well with the exception that the reset causes the stack to "reset" so the initial HomeScreen is unmounted and remounted, and all the components/images/etc have to be reloaded.
I've also tried to pass the HomeScreen's state.key (looks like: Init-id-1513113862825-1) down to the ChannelScreen and utilize:
this.props.navigation.goBack(this.props.navigation.state.params.homeScreenKey);
and
this.props.navigation.dispatch(NavigationActions.back({ key: this.props.navigation.state.params.homeScreenKey }));
Both which did nothing.
Is there a way to get back to HomeScreen but preserve the initial screen?
I solved this by caching the first ChannelScreen's this.props.navigation.state.key on navigation to the sub ChannelsScreens:
class SomeComponent extends Component {
_navigateToHome = () => {
const { backKey } = this.props.navigation.state.params;
this.props.navigation.goBack(backKey);
}
_navigateToChannel = () => {
let { backKey } = this.props.navigation.state.params;
if(!backKey) {
backKey = this.props.navigation.state.key
}
navigate('Channel', { backKey });
}
render() {
return (
<View>
{/* the link to another Channel */}
<TouchableOpacity onPress={this._navigateToChannel}>
{/* ... */}
</TouchableOpacity>
{/* Return to HomeScreen */}
<TouchableOpacity onPress={this._navigateToHome}>
{/* ... */}
</TouchableOpacity>
</View>
)
}
}
If you are on the first ChannelScreen, backKey will be null and therefore just .goBack() is called. On each subsequent ChannelScreen the first ChannelScreen's key is passed along allowing .goBack(key) to navigate us back to the HomeScreen.

React Native Handle Headerback with Redux

I have integrated Redux to my RN Project. I can navigate between screens with buttons, but when I want to go back with the HeaderBackButton it says: "undefined is not a function"
Github-Repo: https://github.com/bayraktarhasan/React-Navigation-Redux-Globalization-Example.git
My Feed Component:
class Feed extends Component {
static navigationOptions = {
title: 'Hello Ahmet',
headerLeft: <HeaderBackButton onPress={this.goBack()} />,
}
constructor(props) {
super(props);
this.goBack = this.goBack.bind(this);
}
goBack = () => {
this.props.navBack();
}
render() {
return (
<View style={styles.container}>
<Text>{I18n.t('feedComponent')}</Text>
<Button
title={I18n.t('back')}
onPress={this.goBack}
/>
</View>
);
}
}
export default connect(null, { navBack, navToProfile })(Feed);
Reducer:
import { NAVIGATE_BACK, NAVIGATE_PROFILE, NAVIGATE_FEED } from '../Actions/types';
const firstAction = AppNavigator.router.getActionForPathAndParams('Main');
const initialNavState = AppNavigator.router.getStateForAction(
firstAction
);
function nav(state = initialNavState, action) {
console.log(action.type);
let nextState;
switch (action.type) {
case NAVIGATE_BACK:
nextState = AppNavigator.router.getStateForAction(
NavigationActions.back(),
state
);
break;
This is just a shot in the dark, but goBack isn't really defined. Maybe try:
const goBack = () => {
this.props.navBack();
}
This is very ironic, because I was having the same problem yesterday. Here's the post How to properly assign a url as a prop to action creator
It might not be very clear from the above link what might need to be done in your case. But I'll try to explain what I've discovered (I'm not an expert in any way). Your code would work on a text input or a date picker or something of that sort. Like this:
<TextInput
label="Model"
placeholder="4020"
value={this.props.model}
onChangeText={value => this.props.entryUpdate({ prop: 'model', value })}
/>
Where the text input has a couple of props already. Here, you assign value to
this.props.<whatever you want the specific object/prop to be called>
But with a button, you have to assign an actual prop name/variable to pass to your action creator. Here you don't have an actual prop. So, it comes back as undefined. So, you have to declare a variable name for that prop and assign it to this.props just before:
this.props.navBack();
So maybe it would be:
const goBack = () => {
const { back } = this.props
this.props.navBack({ back });
}
And in your case, you might need to specify a specific value for the prop { back }. Otherwise, it'll tell you that your second argument is undefined as well.
Here's a cool debugger that helps you see if your props are actually being passed to the action creator:
https://github.com/jhen0409/react-native-debugger
Hope that helps more than it confuses. Good luck!

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