reactjs - working - react rerender when props change




Can you force a React component to rerender without calling setState? (12)

I have an external (to the component), observable object that I want to listen for changes on. When the object is updated it emits change events, and then I want to rerender the component when any change is detected.

With a top-level React.render this has been possible, but within a component it doesn't work (which makes some sense since the render method just returns an object).

Here's a code example:

export default class MyComponent extends React.Component {

  handleButtonClick() {
    this.render();
  }

  render() {
    return (
      <div>
        {Math.random()}
        <button onClick={this.handleButtonClick.bind(this)}>
          Click me
        </button>
      </div>
    )
  }
}

Clicking the button internally calls this.render() , but that's not what actually causes the rendering to happen (you can see this in action because the text created by {Math.random()} doesn't change). However, if I simply call this.setState() instead of this.render() , it works fine.

So I guess my question is: do React components need to have state in order to rerender? Is there a way to force the component to update on demand without changing the state?


So I guess my question is: do React components need to have state in order to rerender? Is there a way to force the component to update on demand without changing the state?

The other answers have tried to illustrate how you could, but the point is that you shouldn't . Even the hacky solution of changing the key misses the point. The power of React is giving up control of manually managing when something should render, and instead just concerning yourself with how something should map on inputs. Then supply stream of inputs.

If you need to manually force re-render, you're almost certainly not doing something right.


Bind store changes using HOC

Using the HOC (higher order component) pattern , you can wrap your React components and have automatic updates when your stores change. This is a very light-weight approach without a framework.

withStores HOC handle store updates

import React, { Component } from 'react'

export default function(/* store1, store2, store3... */) {
  const storeArgs = Array.prototype.slice.call(arguments)
  return function(WrappedComponent) {
    return class WithStore extends Component {
      constructor(props) {
        super(props)
        this.state = {
          lastUpdated: Date.now()
        }
        this.stores = storeArgs
      }

      _onChange = () => {
        this.setState({ lastUpdated: Date.now() })
      }

      componentWillMount = () => {
        this.stores && this.stores.forEach(store => {
          // each store has a common change event to subscribe to
          store.on('change', this._onChange)
        })
      }

      componentWillUnmount = () => {
        this.stores && this.stores.forEach(store => {
          store.off('change', this._onChange)
        })
      }

      render() {
        return <WrappedComponent 
                 lastUpdated={this.state.lastUpdated}
                 {...this.props}  />
      }
    }
  }
}

How to use

import React, { Component } from 'react'
import { View, Text, Button } from 'react-native'
import withStores from './withStores'
import userStore from './stores/users'

class MyView {
  _increaseUserCount = () => {
    userStore.setState({ userCount: userStore.state.count + 1 })
  }

  render() {
    return (
      <View>
        <Text>User count: {userStore.state.count}</Text>
        <Button
          title="Increase User Count"
          onPress={this._increaseUserCount}
        />
      </View>
    )
  }
}

return withStores(userStore)(MyView)

Simple Store class example

var ee = require('event-emitter')
export default class Store {
  constructor() {
    this._state = {}
    this._eventEmitter = ee({})
  }
  get state() {
    return this._state
  }
  setState(newState) {
    this._state = {...this._state, ...newState}
    this._eventEmitter.emit('change')
  }
  on(ev, fn) {
    this._eventEmitter.on(ev, fn)
  }
  off(ev, fn) {
    this._eventEmitter.off(ev, fn)
  }
}

Store instance as singleton

import Store from './Store'

const myStore = new Store()
export default myStore

This is really meant for smaller projects with low-depth of component trees. The reason being is if you use this for a larger tree of components, this isn't very selective on what parts to update for a store, just the store itself triggers the update.

You will be ok if you split up your stores in a more granular fashion but then again, this is why redux is good as it can be both granular as needed and only requires a single store but also has disproportionate boilerplate on a smaller project.


Actually, forceUpdate() is the only correct solution as setState() might not trigger a re-render if additional logic is implemented in shouldComponentUpdate() or when it simply returns false .

forceUpdate()

Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate() . forceUpdate()

setState()

setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate() . more...

forceUpdate() can be called from within your component by this.forceUpdate()


Another way is calling setState , AND preserve state:

this.setState(prevState=>({...prevState}));


I have found it best to avoid forceUpdate(). One way to force re-render is to add dependency of render() on a temporary external variable and change the value of that variable as and when needed.

Here's a code example:

class Example extends Component{
   constructor(props){
      this.state = {temp:0};

      this.forceChange = this.forceChange.bind(this);
   }

   forceChange(){
      this.setState(prevState => ({
          temp: prevState.temp++
      })); 
   }

   render(){
      return(
         <div>{this.state.temp &&
             <div>
                  ... add code here ...
             </div>}
         </div>
      )
   }
}

Call this.forceChange() when you need to force re-render.


In order to accomplish what you are describing please try this.forceUpdate().


Just another reply to back-up the accepted answer :-)

React discourages the use of forceUpdate() because they generally have a very "this is the only way of doing it" approach toward functional programming. This is fine in many cases, but many React developers come with an OO-background, and with that approach, it's perfectly OK to listen to an observable object.

And if you do, you probably know you MUST re-render when the observable "fires", and as so, you SHOULD use forceUpdate() and it's actually a plus that shouldComponentUpdate() is NOT involved here.

Tools like MobX, that takes an OO-approach, is actually doing this underneath the surface (actually MobX calls render() directly)


There are a few ways to rerender your component:

The simplest solution is to use forceUpdate() method:

this.forceUpdate()

One more solution is to create not used key in the state(nonUsedKey) and call setState function with update of this nonUsedKey:

this.setState({ nonUsedKey: Date.now() } );

Or rewrite all current state:

this.setState(this.state);

Props changing also provides component rerender.


When you want two React components to communicate, which are not bound by a relationship (parent-child), it is advisable to use Flux or similar architectures.

What you want to do is to listen for changes of the observable component store, which holds the model and its interface, and saving the data that causes the render to change as state in MyComponent . When the store pushes the new data, you change the state of your component, which automatically triggers the render.

Normally you should try to avoid using forceUpdate() . From the documentation:

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your application much simpler and more efficient



forceUpdate(), but every time I've ever heard someone talk about it, it's been followed up with you should never use this.


forceUpdate(); method will work but it is advisable to use setState();







react-jsx