1

I'm using the TabView from the React-Native and as the part of this i'm using the two states for two different objects as shown below.

const [routes, setRoutes] = React.useState([]);
const [sceneMap, setSceneMap] = React.useState({});

I'm calling a function inside the useEffect hook whenever the TabView gets the focus. Also inside this hook itself i'm changes/reassigning the both of the states with the new values as shown below

React.useEffect(() => {
        // Return the function to unsubscribe from the event so it gets removed on unmount
        return navigation.addListener('focus', async () => {
            const dynamicTabsResponse = await getTabsDynamically();
            let response;
            if (dynamicTabsResponse != null) {
                response = await getRoutes(dynamicTabsResponse);
                **setRoutes(response);**

                const scenes = await getScenes(dynamicTabsResponse)
                newSceneMap = await getSceneMap(response, scenes)

                **setSceneMap({...newSceneMap});**
            }
        });
    }, []);

This is my return function:

if (routes.length > 0 && Object.keys(newSceneMap).length > 0) {
        return (
            <TabView
                navigationState={{index, routes}}
                renderScene={SceneMap(sceneMap)}
                onIndexChange={setIndex}
                initialLayout={initialLayout}
            />
        );
    } else {
      console.log( Object.keys(newSceneMap))
        console.log("routes length", routes.length)
        return null
    }

The problem is as soon as the first state gets update the re-render of the UI is happening and its causing the error, since my UI needs the updated state of the second one as well. How can i inform React-Native not to re-render until both the states get update? Is there a way to do like this or i'm implementing in a wrong way? Please suggest

Just to explain more consider the output below:

When i restart the app the values of Routes and Array is as below:

1. routes length inside if 2

2. inside if Array [
  "NEWS",
  "CRIME",
]

When go other screen and check the check box and come to Tabscreen, the output is as below:

1. routes length inside if 3

2. inside if Array [
  "NEWS",
  "CRIME",
]

As i can see from the 2nd output, the state of the Routes has been updated to 3(Which is causing the re-render), but not the state of the Array. Due to which TabView is displaying/throwing the error

Error:

Check the render method of `SceneComponent`., 
    in SceneComponent (created by SceneView)
    in RCTView (created by SceneView)
    in SceneView (created by Pager)
    in RCTView (at createAnimatedComponent.js:233)
    in AnimatedComponent(Component) (created by PanGestureHandler)
    in PanGestureHandler (created by Pager)
    in Pager (created by TabView)
    in RCTView (created by TabView)
    in TabView (at NewsScreen.js:73)
    in NewsScreen (at SceneView.tsx:98)
    in StaticContainer
    in StaticContainer (at SceneView.tsx:89)
    in EnsureSingleNavigator (at SceneView.tsx:88)
    in SceneView (at useDescriptors.tsx:125)
    in RCTView (at BottomTabView.tsx:38)
    in SceneContent (at BottomTabView.tsx:122)
    in RCTView (at ResourceSavingScene.tsx:44)
    in RCTView (at ResourceSavingScene.tsx:27)
    in ResourceSavingScene (at BottomTabView.tsx:117)
    in RCTView (at screens.native.js:132)
    in ScreenContainer (at BottomTabView.tsx:101)
    in RCTView (at BottomTabView.tsx:100)
    in SafeAreaProviderCompat (at BottomTabView.tsx:99)
    in BottomTabView (at createBottomTabNavigator.tsx:41)
    in BottomTabNavigator (at BottomTabNavigator.js:22)
    in BottomTabNavigator (at SceneView.tsx:98)
    in StaticContainer
    in StaticContainer (at SceneView.tsx:89)
    in EnsureSingleNavigator (at SceneView.tsx:88)
    in SceneView (at useDescriptors.tsx:125)
    in RCTView (at CardContainer.tsx:190)
    in RCTView (at CardContainer.tsx:189)
    in RCTView (at Card.tsx:526)
    in RCTView (at createAnimatedComponent.js:151)
    in AnimatedComponent (at Card.tsx:508)
    in PanGestureHandler (at Card.tsx:501)
    in RCTView (at createAnimatedComponent.js:151)
    in AnimatedComponent (at Card.tsx:497)
coders
  • 719
  • 1
  • 11
  • 35
  • You likely want to attach the listener *in* the effect, and return a cleanup function to remove it. What error are you referencing? Use appropriate guard clauses in the rendered JSX to ensure all pieces of state exist to correctly render UI. – Drew Reese May 04 '20 at 19:00

2 Answers2

1
const Hello = () => {
 let response = [{one:"two"}]
 let newSceneMap = {
    name:"sdfvsdf"
 }
 const [routesAndsceneMap, setIt] = React.useState({
 routes:[{one:"three"}],
 sceneMap:{
   name:"sdfvds"
 }
});
const changeit = () => {
        setIt({...routesAndsceneMap,routes:[...response],sceneMap:  {...newSceneMap}})
 }
return (
<div>
  <h1>Hello {routesAndsceneMap.routes[0].one} {routesAndsceneMap.sceneMap.name}!</h1>
  <button onClick={changeit}>Click</button>
</div>
)};

You can check here how it works For working see this link https://stackblitz.com/edit/react-p7jrjv?file=Hello.js

You have added two different useState instead try to combine it into one as i have done and change it together in just one set function on button click it changes two properties together

  • yes it will becoz its an array i modified it bit it is just and example to give an idea that instead of trying change state using two different methods u can combine it into one – Ashwin Rao K May 04 '20 at 19:34
  • This was perfect. Thank you Ashwin – coders May 04 '20 at 19:36
0

"How can i inform React-Native not to re-render until both the states get update?"

You can't stop a re-render after state is changed. But what you can do is not update the state until you are ready to render.

If you move the two state updates together, so they are updated at the same time and don't have any async fetches between them, it should work like you expect.

        if (dynamicTabsResponse != null) {
            response = await getRoutes(dynamicTabsResponse);

            const scenes = await getScenes(dynamicTabsResponse)
            newSceneMap = await getSceneMap(response, scenes)

            // Update both states together, after all awaits are done.
            setRoutes(response);
            setSceneMap({...newSceneMap});
        }
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337