React: Update Child Component Without Rerendering Parent

Viewed 14136

What follows is a simple example:

const { Component } = React
const { render } = ReactDOM

const Label = ({ text }) => (
  <p>{text}</p>
)

const Clock = ({ date }) => (
  <div>{date.toLocaleTimeString()}</div>
)

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      date: new Date()
    }
  }
  
  componentDidMount() {
    this.interval = setInterval(
      () => this.setState({ date: new Date() }),
      1000
    )
  }
  
  componentWillUnmount() {
    clearInterval(this.interval)
  }
  
  updateTime() {
    
  }
  
  render() {
    return (
      <div>
        <Label text="The current time is:" />
        <Clock date={this.state.date} />
      </div>
    )
  }
  
}

render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="app"></div>

this.setState({ date: new Date() }) is being called every second updating the Clock with the current time. To my knowledge, setState calls the render method on the App which causes the whole component to be rerendered including the Label.

Is there a way pass date to Clock (causing it to be rerendered) without rerendering the entirety of the App component? How big of a role does this play in regards to performance?

3 Answers

When you want to update a child without updating the parent, the state must be in the child. You can pass the state getter / setter from the child to the parent to be able to read and update it:

function Child({onMount}) {
  const [value, setValue] = useState(0);

  useEffect(() => {
    onMount([value, setValue]);
  }, [onMount, value]);


  return (
    <div>
      {value}
    </div>    
  );
};


function Parent() {

  let value = null;
  let setValue = null;
  
  const onChildMount = (dataFromChild) => {
    value = dataFromChild[0];
    setValue = dataFromChild[1];
  };

  // Call setValue to update child without updating parent

  return (
    <div>
      <Child onMount={onChildMount}/>
    </div>    
  );
};

Since const [value, setValue] = useState(0); is in the Child, only the child component will re render when updating the value. Additionally, since the Parent receives value and setValue at onChildMount, the parent can use them to update the child without re rendering the parent.

It is not possible what you want. To pass a prop to a child component, the parent component's state or props should change somehow. As you know, this triggers a re-render obviously, so all the children re-render. To be updated, your Clock component should be re-rendered and unmounted/remounted in this situation to reflect the DOM change.

If your app is not so big and doesn't have so many children do not struggle with this problem since rendering is not so expensive. The expensive one is DOM manipulation of a component. Here, React diffs the real and virtual DOM and do not unmount/remount Label component even it re-renders. But, if you write your Label component as a PureComponent it doesn't re-render. But for Clock component to be updated like this, there is no way.

class Label extends React.PureComponent {
  render() {
    console.log("rendered");
    return (<p>{this.props.text}</p>)
  }
}

const Clock = ({ date }) => (
  <div>{date.toLocaleTimeString()}</div>
)

class App extends React.Component {

  constructor() {
    super()
    this.state = {
      date: new Date()
    }
  }


  componentWillMount() {
    this.interval = setInterval(
      () => this.setState({ date: new Date() }),
      1000
    )
  }

  componentWillUnmount() {
    clearInterval(this.interval)
  }

  updateTime() {

  }

  render() {
    return (
      <div>
        <Label text="The current time is:" />
        <Clock date={this.state.date} />
      </div>
    )
  }

}

Using reference of child component it can achievable:

export const parentA = () => {
  let ref = useRef<View>(null);
  const updateChild () {
    ref.current.props.setNativeProps({style:{backgroundColor: 'gray',}})
  }
  return (
    <childA>
      <Text>Test</Text>
    </childA>
  );
};

export const childA = () => {
  return <View style={{backgroundColor: 'green',}}></View>;
};
Related