Getting used to React involves changing how you solve certain kinds of problems. It reminds me a little bit of learning to drive on the other side of the road.
The first time I experienced this, I was visiting Turks and Caicos. They drive on the left there. Being from the US where we drive on the right, this took a bit of reprogramming. I nearly died on the way out of the airport.
The funny thing was, even after I had learned to drive on the left in normal straight-and-level driving, my brain would revert to old habits whenever a different situation came up.
Turning into a parking lot? Habit took over and I drove into the wrong lane. Taking a left at a stop sign? Same problem. Taking a right at a stop sign? You’d think I would’ve learned by now – but no, to my brain, that was different somehow.
I tell this story because I had a similar experience when learning React, and I think many others do too.
Passing props to a component (as if that component were a function) makes sense – our brains are used to that. It looks and works like HTML.
The idea of passing data down and passing events up also makes sense pretty quickly, for simple cases. It’s the “callback” pattern, commonly used elsewhere, so not all that foreign. Passing an onClick
handler to a button is fairly normal.
But what happens when it’s time to open a modal dialog? Or display a Growl-style notification in the corner? Or animate an icon in response to an event? You might find, as I did, that these imperative, “event-based” things don’t come naturally in the declarative and stateful world of React.
How to Develop “Stateful” or “Declarative” Thinking
If you came from jQuery or Angular or any other framework where you call functions to make things happen (“imperative programming”), you need to adjust your mental model in order to work effectively with React. You’ll adjust pretty quickly with practice – you just need a few new examples or “patterns” for your brain to draw from.
Here are a few.
Expanding/Collapsing an Accordion control
The old way: Clicking a toggle button opens or closes the accordion by calling its toggle
function. The Accordion knows whether it is open or closed.
The stateful way: The Accordion is either in the “open” state, or the “closed” state, and we store that information as a flag inside the parent component’s state (not inside the Accordion). We tell the Accordion which way to render by passing isOpen
as a prop. When isOpen
is true
, it renders as open. When isOpen
is false, it renders as closed.
<Accordion isOpen={true}/>
// or
<Accordion isOpen={false}/>
This example is pretty simple. Hopefully nothing too mind-bending. The biggest change is that in the declarative React way, the expand/collapse state is stored outside the Accordion and passed in as a prop.
Opening and Closing a Dialog
The old way: Clicking a button opens the modal. Clicking its Close button closes it.
The stateful way: Whether or not the Modal is open is a state. It’s either in the “open” state or the “closed” state. So, if it’s “open”, we render the Modal. If it’s “closed” we don’t render the modal. Moreover, we can pass an onClose
callback to the Modal – this way the parent component gets to decide what happens when the user clicks Close.
{this.state.isModalOpen && <Modal onClose={this.handleClose}/>}
For more on this, see Modal Dialogs in React.
Notifications
The old way: When an event occurs (like an error), call the notification library to display a popup, like toastr.error("Oh no!")
.
The stateful way: Think of notifications as state. There can be 0 notifications, or 1, or 2… Store those in an array. Put a NotificationTray component somewhere near the root of the app, and pass it the messages to display. You can manage the array of messages in a few different ways:
- If using Redux, keep them in the store. Dispatch actions to add messages.
- If not using Redux, either keep them in the root component’s state (the parent of NotificationTray) or in a global singleton object. Then, you can either pass an
addNotification
prop down to components that need it, orimport
a function that can add to the global singleton.
In your own app, you might just use a library for this, like react-redux-toastr. But the concept is simple enough that you could write it yourself if you wanted.
Animating a Change
Let’s say you have a badge with a counter showing the number of logged-in users. It gets this number from a prop. What if you want the badge to animate when the number changes?
The old way: You might use jQuery to toggle a class that plays the animation, or use jQuery to animate the element directly.
The stateful way: You can respond when props change by implementing the componentWillReceiveProps
lifecycle method and comparing the old value to the new one. If it changed, you can set the “animating” state to true
. Then in render
, when “animating” is true, add a CSS class that does the animation. When “animating” is false
, don’t add that class. Here’s what this might look like:
componentWillReceiveProps(nextProps) {
if(this.props.counter !== nextProps.counter) {
// Set animating to true right now. When that state change finishes,
// set a timer to set animating false 200ms later.
this.setState({ animating: true }, () => {
setTimeout(() => {
this.setState({ animating: false });
}, 200);
});
}
}
render() {
const animatingClass = this.state.animating ? 'animating' : '';
return (
<div className={`badge ${animatingClass}`}>
{this.props.counter}
</div>
);
}
What Else?
A few related posts: