Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If using forceUpdate() is discouraged, how should components react to change events in the model?

I use the following pattern in several components throughout my app. I notice that many people discourage using forceUpdate(), and some even call it a hack. So how should this be done without using it?

// user.js

export default class User extends EventEmitter {
    constructor(args) {
        super();
        this.id = args.id;
        this.isActivated = args.isActivated || false;
    }
    activate() {
        this.isActivated = true;
        this.emit('change');
    }
}

// UserView.jsx

export default class UserView extends React.Component {
    componentDidMount() {
        this.props.user.on('change', this.onUserUpdate);
    }
    onUserUpdate() {
        this.forceUpdate();
    }
    render (
        var user = this.props.user;
        return (
            <div>
                <span>User {user.isActivated ? 'is' : 'is not'} activated.</span>
                <button onClick={user.activate()}>Activate</button>
            </div>
        );
    );
}

// app.js

var user = new User({ id: 123 });
ReactDOM.render(<UserView user={user} />, container);
like image 560
1in7billion Avatar asked Dec 17 '25 14:12

1in7billion


2 Answers

React will automatically render the component when either the props or state change.

If you have a model with changing properties then you should either keep it in the component's local state.

componentWillMount() {
  this.setState({
    user: this.props.user
  });
}
activator(user) {
  return () => {
    // mutate the user instance
    user.activate();
    // update the state to trigger render
    this.setState({ user });
  };
}
render (
  var user = this.state.user;
  return (
    <div>
      <span>User {user.isActivated ? 'is' : 'is not'} activated.</span>
      <button onClick={activator(user)}>Activate</button>
    </div>
  );
)

Even this is a bit hacky and storing unserializable data (prototype relationships, functions) in state isn't great. We're also mutating the object whilst it's in state (eek!).

An (arguably) more elegant way to solve this problem would be to use a simple object without any instance methods to represent users. Then write a set of pure operation functions that return new user objects reflecting the changes.

// user factory
function User(args) {
  return {
    id: args.id,
    isActivated: args.isActivated || false
  };
}

function activate(user) {
  // returns a new copy with the isActivated property set to true
  return Object.assign({}, user, { isActivated: true });

  // or with ES2016 object spread
  return { ...user, isActivated: true };
}

We can treat these user objects as immutable (freeze them if you want). Then you can modify the activator code to be more functional.

activator(user) {
  return () => {
    this.setState({ user: activate(user) });
  }
}

Now when you click the activate button we update the state of the component with a new object and the old one is free to be garbage collected.

like image 68
Dan Prince Avatar answered Dec 19 '25 05:12

Dan Prince


Solved using MobX, a reactive paradigm that makes state observable and automatically upates observing components.

https://github.com/mobxjs/mobx

like image 42
1in7billion Avatar answered Dec 19 '25 06:12

1in7billion



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!