Search code examples
reactjsreact-nativeflux

React setState / Flux - Does it always trigger a re-render?


I've been playing around with React Native lately and I reached a point where I became interested in managing my state more properly, as a start achieving a shared state between all the components.

The answer of course is Flux. Before moving forward with some more advanced solutions (e.g. Redux, Alt, MobX) I thought I should start with understanding the raw structure itself, with the help of one small tool, that is the Flux dispatcher.

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

import EventEmitter from 'EventEmitter';
import { Dispatcher } from 'flux';

class Store extends EventEmitter {
  list = [];

  actions = {
    add: title => dispatcher.dispatch({ type: 'add', payload: { title } })
  };

  handle = ({ type, payload }) => {
    switch(type) {
    case 'add': this.add(payload.title); break;
    }
  };

  add(title) {
    this.list.push(title);
    this.emit('change');
  }
}

const store = new Store(), dispatcher = new Dispatcher();

dispatcher.register(store.handle);

class App extends Component {
  state = { list: store.list };

  componentWillMount() {
    this.listener = store.addListener('change', () => this.setState({ list: store.list }));
  }

  componentDidMount() {
    setInterval(() => store.actions.add(new Date().getTime()), 1000);
  }

  componentWillUnmount() { this.listener.remove(); }

  render() {
    return (
      <View style={{ marginTop: 20 }}>
        <Text>{JSON.stringify(this.state.list)}</Text>
      </View>
    );
  }
}

AppRegistry.registerComponent('straightforwardFlux', () => App);

Notice in the view layer, we have {JSON.stringify(this.state.data)}, naturally when the store is updated the view will be re-rendered since it is linked to the state.

When changing to {JSON.stringify(store.data)} the view is also re-rendered! this shouldn't happen because the view should only update when there is a change in the state that affect the view directly, in this case there is no state rendered in the view whatsoever. Am I missing something here? why would we encounter this behaviour?

This leads to another question, does render() get called every time there is a state change? even if it doesn't affect the way the view layer looks? I've looked into this and I got two different answers, one says yes and that componentShouldUpdate() returns true by default, meaning that some changes need to be made here (if so, how?), and the other one was simply no, it doesn't update with each setState().

Overall, is this implementation correct?


Solution

  • Per the documentation...

    setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate(). If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.

    tl;dr; React isn't analyzing your view to see explicitly what state it is depending on, that is up to you to optimize with shouldComponentUpdate().

    shouldComponentUpdate(nextProps, nextState) {
      // check if your view's attributes changes
      let check1 = nextState.foo != this.state.foo
      let check2 = nextState.bar != this.state.bar
    
      // if return is true, the component will rerender
      return check1 || check2
    }