React Watcher – Watching prop changes in your ReactJS applications
While building web applications with ReactJS, I've found myself attaching
behavior to prop changes through the componentWillReceiveProps
lifecycle hook.
In most cases, this behavior is particularly attached to a specific prop
changes.
This generates the following code structure, which is repeated throughout the codebase:
class UserDetail extends React.PureComponent { componentWillMount() { doSomething(this.props.params.id); } componentWillReceiveProps(nextProps) { if (props.params.id !== nextProps.params.id) { doSomething(nextProps.params.id); } } render() { return (/* ... */); } }
These lines of code were repeated throughout multiple components, and so I decided to extract it into it's own little utility: React Watcher.
React Watcher
React Watcher simply introduces a wrapper function, which exposes two
props on the wrapped component: watch
and unwatch
.
The watch
function, allows binding a callback to a prop change, like so:
const { watch } = this.props; watch('params.id', (newId) => { doSomething(newId); });
The first parameter of the watch
function is the path of the prop, which is
then used together with
Lodash get function to access the prop.
The unwatch
function, simply removes the binding:
const { unwatch } = this.props; unwatch('params.id');
That's all there is to it, one thing.
Caveats
React Watcher should be used sparingly, in cases where you can't really solve the problem any other way. In most cases, where you need to do something when some prop changes, you don't really need it. Why? Because in most cases you can usually trigger the event together with the action with caused the prop to be changed, e.g. when triggering a filtering change on a listing. Example:
/* Bad */ class UsersList extends React.Component { render() { const { activeFilter, users } = this.props; return (/* ... */); } toggleActive(event) { dispatch(toggleActiveAction()); } componentWillMount() { this.props.watch('activeFilter', (activeFilter) => { alert('Active filter has changed!'); }); } } export default connect(UsersList);
A better practice would be something along the lines of:
/* Good */ class UsersList extends React.Component { render() { const { activeFilter, users } = this.props; return (/* ... */); } toggleActive(event) { dispatch(toggleActiveAction()); alert('Active filter has changed!'); } }
When should React Watcher be used then? When you have no control over the
prop changes because of some external library. The most common use-case is
react-router
and its params/query updates. Example:
class UserDetail extends React.PureComponent { render() { const { users } = this.props; return (<ul>{/* users.map... */}</ul>); } componentWillMount() { const { params, watch } = this.props; this.dispatch(fetchUser(params.id)); watch('params.id', (newUserId) => { this.dispatch(fetchUser(newUserId)); }); } } export default connect(UserDetail);
So make sure you use React Watcher appropriately!