Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expo/React Native TabView re-render component/tab on state change

I am writing an app using Expo (React Native framework). Whenever the state of a component is changed with setState(...) method, the component should re-render to show the latest state data. This works with basic React Native components such as View, Text, Button (I have tried these three), but it does not re-render here, where I use custom component TabView. This is stated in the documentation about component:

"All the scenes rendered with SceneMap are optimized using React.PureComponent and don't re-render when parent's props or states change. If you need more control over how your scenes update (e.g. - triggering a re-render even if the navigationState didn't change), use renderScene directly instead of using SceneMap."

I don't quite understand how to manage this. I would like the component to re-render whenever the state is changed, in this case, after clicking the button and calling the function writeData().

Documentation of the TabView component is here: https://github.com/react-native-community/react-native-tab-view .

import React from 'react';
import { TabView, SceneMap } from 'react-native-tab-view';

import { StyleSheet, Text, View, Dimensions, Button, ToastAndroid } from 'react-native';

export default class MojTabView extends React.Component {
state = {
    index: 0,
    routes: [
      { key: 'allEvents', title: 'Všetko' },
      { key: 'sortedEvents', title: 'Podľa druhu' },
      { key: 'myEvents', title: 'Moje akcie'}
    ],
    name: "Robert"
  };

MyEvents = () => (
    <View style={[styles.scene, { backgroundColor: 'white' }]}>
        <Text>Some content</Text>
        <Button
          style={{ margin: 5 }}
          onPress={this.writeData}
          title="Write data"
          color="#841584"
        />
        <Text>Name in state: {this.state.name}</Text>
    </View>
  );

SortedEvents = () => (
      <Text>Some content</Text>
);

AllEvents = () => (
     <Text>Some other content</Text>
);

writeData = () => {
  ToastAndroid.show("button click works!", ToastAndroid.LONG);
  this.setState({ name: "Richard"});

}
render() {
    return (
        <TabView
          navigationState={this.state}
          renderScene={SceneMap({
              allEvents: this.AllEvents,
              myEvents: this.MyEvents,
              sortedEvents: this.SortedEvents
          })}
          onIndexChange={index => this.setState({ index })}
          initialLayout={{ width: Dimensions.get('window').width }}
        />
    );
}

}

I spent a few hours trying to achieve that, without solution. This is my first StackOverflow question, thanks.

like image 553
druskacik Avatar asked Oct 17 '25 01:10

druskacik


2 Answers

Okay, I was finally able to come up with a nice solution. The point is to define content of individual tabs/routes in a separate file as a typical React Native component with its own state, not inside this MyTabView component, as it is made even in the example in the documentation about TabView. Then it works as it should.

Simple example: This is content of one of the tabs:

import React from 'react';
import { Text, View, Button } from 'react-native';

export default class MyExampleTab extends React.Component {
state = {
    property: "Default value",
}

writeData = () => {
    this.setState({
        property: "Updated value"
   })
}

render() {
    return (
        <View>
            <Button
            style={{ margin: 5 }}
            onPress={this.writeData}
            title="Change the state and re-render!"
            color="#841584"
            />
            <Text>Value of state property: {this.state.property}</Text>
        </View>
    )
}
}

This is how I reference it in the MyTabView component:

 import MyExampleTab from './MyExampleTab'
 ...
 export default class MyTabView extends React.Component {
 ...
       render() {
           return (
              <TabView
                  navigationState={this.state}
                  renderScene={SceneMap({
                       ...
                       someKey: MyExampleTab,
                       ...
                  })}
                  onIndexChange={index => this.setState({ index })}
                  initialLayout={{ width: Dimensions.get('window').width }}
              />
         )

So I don't use <MyExampleTab />, as I would normally using custom component, but write it as it is, MyExampleTab. Which quite makes sense. And on the button click the state changes and tab re-renders.

like image 194
druskacik Avatar answered Oct 20 '25 16:10

druskacik


I changed to self implemented renderScene and inner component now get re-rendered

<TabView
        navigationState={{ index, routes }}
        renderScene={({ route }) => { 
          switch (route.key) {
          case 'first':
              return <FirstRoute data={secondPartyObj} />;
          case 'second':
              return SecondRoute;
          case 'third':
              return ThirdRoute;
          case 'forth':
              return ForthRoute;
          default:
              return null;
          }
      }}
        onIndexChange={setIndex}
        initialLayout={{ width: layout.width }}
        renderTabBar={renderTabBar}

      />
like image 20
Sir'Damilare Avatar answered Oct 20 '25 14:10

Sir'Damilare