Home > Software engineering >  How to stop sibling components from Re-Rendering?
How to stop sibling components from Re-Rendering?

Time:11-01

I'm trying to toggle the visibility of a component using another component like this:

const CompA = React.memo(({Vis})=>{
  console.log('Rendered CompA');
  return(<Pressable onPress={()=>{Vis[1](!Vis[0])}} style={...}></Pressable>);
})

const CompB = React.memo(({Vis})=>{
  console.log('Rendered CompB');
  return(<>{Vis[0]&&(<View style={...}></View>)}</>);
})

export default function App() {

  const Vis=useState(()=>true);

  return (<View style={styles.body}>
    <CompA Vis={Vis}/>
    <CompB Vis={Vis} />
  </View>);
}

but every time I toggle the visibility even CompA gets re rendered
How do I only re render CompB everytime I change the visibility?

CodePudding user response:

The array you receive from useState will never be === a previous array you received from useState; React creates a new array when you call useState each time. Just like [] === [] is always false, the check being done by memo on the props will always be false and it will re-render.

There are at least two ways to solve the problem:

  1. Pass the component parts of that array instead, since the setter function is guaranteed to be stable, and only pass CompA the setter function since it doesn't need the flag (it can use the callback form of the setter).

  2. If you really, really want to pass the array around instead, implement a custom memo callback for CompA that only looks at the second element in the array, and don't use the first element of the array in CompA. But I'd strongly recommend not doing that, the readability/semantics of it are very misleading/surprising.

Here's #1:

const CompA = React.memo(({setVisible}) => {
    console.log("Rendered CompA");
    return (<Pressable onPress={() => {setVisible(visible => !visible)}} style={...}></Pressable>);
});

const CompB = React.memo(({visible}) => {
    console.log("Rendered CompB");
    return (<>{visible && (<View style={...}></View>)}</>);
});

export default function App() {
    const [visible, setVisible] = useState(true); // No need for the callback form here
  
    return (<View style={styles.body}>
        <CompA setVisible={setVisible} />
        <CompB visible={visible} />
    </View>);
}

Live Example:

Show code snippet

const {useState} = React;

const CompA = React.memo(({setVisible}) => {
    console.log("Rendered CompA");
    return (<button onClick={() => {setVisible(visible => !visible)}}>pressable</button>);
});

const CompB = React.memo(({visible}) => {
    console.log("Rendered CompB");
    // Unfortunately, the version of Babel used by Stack
    // Snippets is so old it doesn't understand shorthand
    // fragment syntax
    return (<React.Fragment>{visible && (<div>this is the View</div>)}</React.Fragment>);
});

function App() {
    const [visible, setVisible] = useState(true); // No need for the callback form here
  
    return (<div>
        <CompA setVisible={setVisible} />
        <CompB visible={visible} />
    </div>);
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

You need to pass particular props in components. isVisible and the function which change the value of isVisible. When passing function as p props to components and using React.memo you need to use useCallback in function hooks to prevent re-render stuff.

Read more about memo and usecallback

import React from "react";
import ReactDOM from "react-dom";
const { useState, useCallback, memo } = React;

const CompA = memo(({ handleChange }) => {
  console.log("Rendered CompA");
  return <Pressable onPress={handleChange}>Comp A</Pressable>;
});

const CompB = memo(({ isVisible }) => {
  console.log("Rendered CompB", isVisible);
  return isVisible && <View> CompB</View>;
});

function App() {
  const [isVisible, setVisible] = useState(true);

    const handleChange = useCallback(() => setVisible((isVisible) => !isVisible),[]);

  return (
    <View style={styles.body}>
      <CompA handleChange={handleChange} />
      <CompB isVisible={isVisible} />
    </View>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

Live Example

const { useState, useCallback, memo } = React;

const CompA = memo(({ handleChange }) => {
  console.log("Rendered CompA");
  return <button onClick={handleChange}>Comp A</button>;
});

const CompB = memo(({ isVisible }) => {
  console.log("Rendered CompB");
  return isVisible && <div> CompB</div>;
});

function App() {
  const [isVisible, setVisible] = useState(true);

  const handleChange = useCallback(
    () => setVisible((isVisible) => !isVisible),
    []
  );

  return (
    <div>
      <CompA handleChange={handleChange} />
      <CompB isVisible={isVisible} />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<div id="root"></div>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

I have a solution but it doesn't look pretty:

let UniVis;

const CompA = React.memo(({Vis})=>{
  console.log('Rendered CompA');
  return(<Pressable onPress={()=>{UniVis[1](!UniVis[0])}} style={...}></Pressable>);
});

const CompB = React.memo(({Vis})=>{
  console.log('Rendered CompB');
  return(<>{Vis[0]&&(<View style={...}></View>)}</>);
})

export default function App() {

  const Vis=useState(()=>true);
  UniVis=Vis;

  return (<View style={styles.body}>
    <CompA />
    <CompB Vis={Vis} />
  </View>);
}

but it does work

  • Related