React components

Jérôme Cukier
8 min readJun 29, 2018

--

This article is part of my series Visualization with React. Previous article: Coding with React

Using React to create elements out of data is nice, using JSX is hip but until you use your own components, you won’t use React to the fullest.
So let’s do that. Fair warning: that’s the speed bump, especially if you’re not familiar with javascript concepts like ‘this’, which you’re not really required to master with d3.

In the previous examples, we created an element that held a bunch of elements. We are now going to create a custom Point component, to replace our circles. We’re going to do more than just replace it: our new component will be able to do more stuff. Each Point element should remember whether it’s highlighted or not. If it’s highlighted, it will be displayed in red. Else, it will stay in gray. Clicking on a Point will switch its highlighted status.

The way we are going to maintain that highlighted status is by using the state of the component (remember that switch example?).

Our custom component is an ES6 class (gasp!) and it’s based on the existing class, React.Component.
So we declare our custom component like this:

class Point extends React.Component {
... a bunch of stuff which is different from React.Component
...
}

Specifically we are going to specify two properties of our new class: constructor and render.

  • constructor describes what happens when an element of this component is first created.
  • render is what should be displayed on screen for that element.

Don’t confuse the component and the elements.

The component is the type of things that we are going to create, you can think of it as the mold. The elements are what are created with this component, think of it as casts.

Creating the component describes how the elements should be created. Later in the code, we are going to create, or instantiate, the individual elements using the component. In Javascript, as in many other languages, by convention, classes like our component have a name that starts with an upper case.

So let’s look at the first property.

constructor(props) {
super(props);
this.state = {highlighted: false};
}

what’s the argument of constructor, props? In React world, props is short for properties. Until now, we never really had to manipulate properties inside the component.
In this specific example, we don’t really do anything with props, so we could write just constructor() {…}. But it’s a convention, and if at some point we want to do something with these properties right when the component is first created, we might.
The second line is super(props). What this does is that it passes whatever arguments the constructor had to the constructor of the original React.Component class. You don’t have to know what happens then, just that it’s a mandatory step.
The third line introduces us to the state. we assign an object to “this.state”.

But what is this? the 10000 ft view is that it refers to a specific context or scope (the article under the link above is fantastic if you want to know more).

We’re going to have many Point elements. Each of them can be highlighted or not. Each of them also corresponds to a different data point. Each of them can access and manipulate data that no other can. This is where this comes from. Properties of this (that is, whatever comes after “this.”) are going to be private to that element, in other words, not accessible from the outside.
And there are going to be two very important properties: props and state.

this.props are the properties of the element, which the element cannot change. They come from its parent element.
this.state is the state of the element, which the element can change: and when it does, the element will be re-rendered.

Speaking of rendering, let’s look at the next property of our class: the render method.
That method must render a React element or null.

So:

render() {
return (<circle
onClick={() =>
this.setState({
highlighted: !this.state.highlighted
})
}
r={5}
style={{
fill: this.state.highlighted ? 'red' : '#222',
opacity: 0.2,
transition: 'fill .5s'
}}
/>);}

Our component will create a element. But it could also be another custom component! Let’s just keep it simple for now.
cx, this time, will be expressed as a calculation based on this.props.birth. (and likewise cy will be computed from this.props.death).
birth and death are the properties that will have to be passed to the component to create an element (which we’ll see in the end).

We have a new property: onClick. onClick, unsurprisingly, handles click events. So when a user clicks, that will trigger a function which will do the following:

this.setState({
highlighted: !this.state.highlighted
});

The intention here is to assign to the highlighted property of the state the value which is opposite to that it currently holds. That value is stored at this.state.highlighted.

Remember that originally, we stored {highlighted: false} in this.state, so this.state.highlighted is where it’s at. And so, !this.state.highlighted is the opposite of the current highlighted status.

this.setState adds the relevant property to the state. So, this construct effectively reverses the value of this.state.highlighted.
Whenever the state changes, the component is automatically re-rendered without any other action required (we can prevent that if needed, but we’ll see that later).

Finally, let’s look at what’s happening towards the end, with our style.
As we’ve seen several times before, we pass an object to style. Because of the JSX notation, that’s two sets of curly braces.

The fill property of the style depends on the state. So, if highlighted is true, it’s going to be red, else it’s going to be gray. Just as we said. I’m also adding to the style a transition property, so that instead of just blinking from red to gray, our component smoothly fades from one color into the other.

Pure functional components

Our Point component is somewhat complicated because we maintain its state. Because we care about its state, we need to initialize it, we need to capture events that could affect it, and have our output depend on it.

By contrast, if all we had were properties (which, again, do not change), what the component does could be much simpler: it takes an input, and produces an output. The same input produces the same output. Just like each time when you add two numbers, the result is the same if the numbers you add are the same.

In javascript terms, if you had a function that didn’t use global variables, randomness or external APIs, when you pass the same argument to that function, you get the same result. This is what’s called a pure function. Pure functions have a lot of good things going for them, not least their stability and predictability and simplicity.

So let’s suppose we didn’t care about the state of our Point component.
React let us write it as a pure functional component.
Here’s how:

function Point({
birth,
death
}) {
return (<circle
cx = {birth * 10}
cy = {300 - death * 10}
r = {5}
style = {{
fill: '#222',
opacity: 0.2
}}
/>);
}

Our component is now just a function! we pass it an object with properties {birth, death} and you can use them directly in the body of the function. No need for this.props.birth or whatever.

The second part of the code doesn’t change, the elements are still created exactly the same way.

Combining components: I’ll call you back

In a real world situation, you’ll probably have many custom components being parts of one another and passing data back and forth.
So, let’s step up in complexity.

Let’s use the same dataset, but this time we’ll make a bar chart.
Initially, we’ll show birth rates. But we’ll also add a switch! and if the user touches the switch, we’ll show death rates instead.

So. Let’s think this through a little bit.

We’ll have a Chart component that is going to be at the top level.
That component will have a Switch component as a child.
It will also have several Bar components that will correspond to the actual data. We can make the Bar components out of our birthdeathrates dataset as before.

<Chart>
<Switch />
<Bar />
<Bar />
<Bar />
...
</Chart>

There will be an event attached to the switch, so that when it’s clicked, the Bars can change.
Now the real question is: which component’s state should be changed by the switch?

We’ve seen how the switch could change its own state. But the Bar components wouldn’t be able to read it.
Ideally, the switch will trigger some kind of change in the Bar component, but likewise, it cannot reach the state of those.

So: we’ll have to find a way to get our switch to update the state of the Chart component. When the state of Chart updates, it re-renders. That means that it can pass new properties to its children. It can tell its Bars to use the death property instead of the birth one.
But how to access the state of the parent from one of its children? That’s possible using callback functions.

If we are within the parent element, we can access its state.
So, we could create a function that would do:

updateMetric(metric) {
this.setState({metric}); // this.setState({metric: metric})
}

That function would work within our future Chart component. Now what if… we passed that function to Switch as a property? then, when executed, it would change the state within Chart. That would trigger a re-render, and Chart could pass different props to all of its children.

Let’s make it work.

Lots of stuff going on here!

class Chart extends React.Component {
constructor(props) {
super(props);
this.state = {
metric: 'birth'
};
}
// ...

This is the constructor of our class. We’ve seen it before, the only special thing is that we give it an initial start here by giving a value to this.state.

render() {
const metric = this.state.metric;
const data = this.props.data.sort((a,b) => b[metric] - a[metric])
.map((d, i) => ({...d, rank: i}))
.sort((a, b) => b.country > a.country);

Here, metric gets the value of whatever we put in the state.
And data is: whatever is passed to the props as data, first sorted by the value of the corresponding metric, then given a rank property which is just its order in that sorted list, and finally sorted alphabetically. So whatever the metric, the same country will always be at the same position in this array, only its rank property, which was computed while the list was sorted, would be different.

return (<div className="chart">
<span className="label">Birth rate</span>
<Switch
metric={metric}
updateMetric={d => this.setState({ metric: d })}
/>
<span className="label">Death rate</span>
{data.map(d => (<Bar
country={d.country}
key={d.country}
rank={d.rank}
value={d[metric]}
/>))}
</div>);

Nothing that we’ve never seen before in that render function — we’re just creating divs or instances of components that remain to be described, and we pass them props. The only new thing is that updateMetric property of Switch. Instead of passing a value or an array, we pass a function. And in that function, we call this.setState.

Because we are still within the Chart component, this.setState will change the state of a Chart component. But wait: we are actually passing that function to another component, Switch! This component, a child of Chart, receives a function that lets it change the state of its parent.
That’s the callback function.

Here’s that Switch component:

function Switch({ metric, updateMetric }) {
return (<div
className="switch__track"
onClick={() => updateMetric(metric === "birth" ? "death" : "birth")}
>
<div className={"switch__thumb " + metric} />
</div>);
}

Switch doesn’t have a state of its own, so we can just use a functional component. Switch receives updateMetric as a property. Switch doesn’t know anything about this function — it doesn’t have to. It doesn’t need to know that this function will affect its parent. All it does is launch this function when it’s being clicked — that’s what the line with onClick does.

Here’s another example presented without comments with similar data, with a slightly different ways to order the components.

This time, the top level component is Dashboard, which has 2 Chart components, which each have several Bar components as children. Mousing over Bar components updates the state of the Dashboard components, which re-renders its Charts children and their Bar children.

Our next article is Beyond Rendering

--

--