CHAPTER-5
Advanced Component Configuration with props, state, and children
Intro
In this chapter we’re going to dig deep into the configuration of components.
A ReactComponent is a JavaScript object that, at a minimum, has a render() function.
render() is expected to return a ReactElement.
Recall that ReactElement is a representation of a DOM element in the Virtual DOM.
In the chapter “JSX and the Virtual DOM” we talked about ReactElement extensively. Checkout that chapter if you want to understand ReactElement better.
The goal of a ReactComponent is to
- render() a ReactElement (which will eventually become the real DOM) and
- attach functionality to this section of the page
“Attaching functionality” is a bit ambiguous; it includes attaching event handlers, managing state, interacting with children, etc. In this chapter we’re going to cover:
- render() – the one required function on every ReactComponent
- props – the “input parameters” to our components
- context – a “global variable” for our components
- state – a way to hold data that is local to a component (that affects rendering)
- Stateless components – a simplified way to write reusable components
- children – how to interact and manipulate child components
- statics – how to create “class methods” on our components
Let’s get started!
Unlike the rest of the chapters in this book, this chapter is intended on being read as an in-depth, deep dive into the different features of React, from an encylopedia-like perspective. For this reason, we did not include a step-by-step follow-along style project in this section of the book.
Instead, in this chapter we’re going to be using a React devtool called styleguidist, which we cover below.
How to use this chapter
This chapter is built using a specific tool called styleguidist. In the code included with this course is a section called advanced- components/components-cookbook, which accompanies this chapter with the styleguidist tool bundled in with it. To use the styleguidist tool, which allows introspection into the components themselves, we can boot up the section through the chapter.
In order to get it started, change into the directory of the advanced-components/components-cookbook/ code. We’ll need to change a few files to get the code running. Since we’ll be using a few variables to define configuration, we’ll need to include these in our code.
In terminal, change into the root directory and issue the following commands. First, we’ll need to get the dependencies for the project using npm install:
npm install
To start the application, issue the npm start command:
npm start
Once the server is running, we can navigate to our browser and head to the URL of http://localhost:6060. We’ll see the styleguide running with all the components exposed by this chapter, where we can navigate through running examples of the components executing in real-time.
Component
Creating Components – ES6 Classes or Functional Components
As discussed in the first chapter, there are two ways to define a ReactComponent instance:
- ES6 classes
- Function components
As we’ve seen, the two methods of creating components are roughly equivalent:
import React from "react";
// ES6 class-style
class ComponentApp extends React.Component {
render() {
return <div />;
}
}
export default ComponentApp;
and
// Functional Component Style
const FunctionComponent = props => {
return <div />;
};
export default FunctionComponent;
When defining a class component, React expects a render() function. When using a function component, the function itself is, essentially, just the render() function.
render() Returns a ReactElement Tree
The render() method is the only required method to be defined on a ReactComponent.
After the component is mounted and initialized, render() will be called. The render() function’s job is to provide React a virtual representation of a native DOM component.
An example of using a function component to create an h1 tag might look like this:
1 const FunctionComponentHeading = props => {
2 return <h1>Hello</h1>;
3 };
Or with ES6 class-style components:
3 class Heading extends React.Component {
4 render() {
5 return <h1>Hello</h1>;
6 }
7 }
The above code should look familiar. It describes a Heading component class with a single render() method that returns a simple, single Virtual DOM representation of a <h1> tag.
Remember that this render() method returns a ReactElement which isn’t part of the “actual DOM”, but instead a description of the Virtual DOM.
React expects the method to return a single child element. It can be a virtual representation of a DOM component or can return the falsy value of null or false. React handles the falsy value by rendering an empty element (a <nonscript /> tag ). This is used to remove the tag from the page.
Keeping the render() method side-effect free provides an important optimization and makes our code easier to understand.
Getting Data into render()
Of course, while render is the only required method, it isn’t very interesting if the only data we can render is known at compile time. That is, we need a way to:
- input “arguments” into our components and
- maintain state within a component.
React provides ways to do both of these things, with props and state, respectively.
Understanding these are crucial to making our components dynamic and useable within a larger app.
In React, props are immutable pieces of data that are passed into child components from parents.
Component state is where we hold data, local to a component. Typically, when our component’s state changes, the component needs to be re-rendered. Unlike props, state is private to a component and is mutable.
We’ll look at both props and state in detail below. Along the way we’ll also talk about context, a sort of “implicit props” that gets passed through the whole component tree.
Let’s look at each of these in more detail.
props are the parameters
props are the inputs to your components. If we think of our component as a function, we can think of the props as the arguments.
Let’s look at an example:
<div>
<Header headerText="Hello world" />
</div>
In the example code, we’re creating both a <div> and a <Header> element, where the <div> is a usual DOM element, while is an instance of our Header component.
In this example, we’re passing data from the component (the string “Hello world”) through the attribute headerText to the component.
Passing data through attributes to the component is often called passing props.
When we pass data to a component through an attribute it becomes available to the component through the this.props property. So in this case, we can access our headerText through the property this.props.headerText:
import React from 'react';
export class Header extends React.Component {
render() {
return (
<h1>{this.props.headerText}</h1>
);
}
}
While we can access the headerText property, we cannot change it.
By using props we’ve taken our static component and allowed it to dynamically render whatever headerText is passed into it. The <Header> component cannot change the headerText, but it can use the headerText itself or pass it on to its children.
We can pass any JavaScript object through props. We can pass primitives, simple JavaScript objects, atoms, functions etc. We can even pass other React elements and Virtual DOM nodes.
We can document the functionality of our components using props and we can specify the type of each prop by using PropTypes.
PropTypes
PropTypes are a way to validate the values that are passed in through our props. Well-defined interfaces provide us with a layer of safety at the run time of our apps. They also provide a layer of documentation to the consumer of our components.
We include the prop-types package in our package.json.
We define PropTypes by setting a static (class) property propTypes. This object should be a map of prop-name keys to PropTypes values:
1 class MapComponent extends React.Component {
2 static propTypes = {
3 lat: PropTypes.number,
4 lng: PropTypes.number,
5 zoom: PropTypes.number,
6 place: PropTypes.object,
7 markers: PropTypes.array
8 }
9 // ...
10 };
In the example above, our component will validate that lat, lng, and zoom are all numbers, while place is an object and marker is an array.
There are a number of built-in PropTypes, and we can define our own.
We’ve written a code example for many of the PropTypes validators in “Appendix A: PropTypes.” For more details on PropTypes, check out that appendix.
For now, we need to know that there are validators for scalar types:
- string
- number
- boolean
We can also validate complex types such as:
- function
- object
- array
- arrayOf – expects an array of a particular type
- node
- element
We can also validate a particular shape of an input object, or validate that it is an instanceOf a particular class.
Default props with getDefaultProps()
Sometimes we want our props to have defaults. We can use the static property defaultProps to do this.
For instance, create a Counter component definition and tell the component that if no initialValue is set in the props to set it to 1 using defaultProps:
class Counter extends React.Component {
static defaultProps = {
initialValue: 1
};
// ...
};
Now the component can be used without setting the initialValue prop. The two usages of the component are functionally equivalent:
<Counter />
<Counter initialValue={1} />
Context
Sometimes we might find that we have a prop which we want to expose “globally”. In this case, we might find it cumbersome to pass this particular prop down from the root, to every leaf, through every intermediate component.
Starting in React 16.3.0, React adds a new API that allows us to specify variables we want to carry downwards through the component tree, rather than needing to pass down variables manually through component parent to child.
The React context API is much more efficient than the older, experimental version of context in that it enables static type-checking and deep updates.
In order to tell React we want to pass a context “global” variable down, we need to specify it using the context API. An example of where context comes in handy is passing a theme or preferences down the component hierarchy that many components in the tree require.
When we specify a context, React will take care of passing down context from component to component so that at any point in the tree hierarchy, any component can reach up to the “global” context where it’s defined and get access to the parent’s variables.
In order to tell React we want to pass a variable through the context, we’ll need to define a context to pass down through. We can do this by first defining a context Provider/Consumer context using the React.createContext() method.
We’ll then pass the context down through the react tree using the context’s Provider component which specifically is designed for passing through contexts. We can access the context from the Provider by using the Consumer component as a child of the Provider element.
Let’s see how this works… Let’s say that we want to provide the user the ability to pick a theme for a site. We’ll look at a light and dark theme:
In order to define a context, we’ll need to create a react context to hold on to the theme for us. We’ll do this with the React.createContext() method:
import React from 'react';
// ...
export const ThemeContext = React.createContext(themes.dark);
The React.createContext() method accepts a single argument which is the default value the context provides. In this case, our theme will default to the themes.dark value.
Now that we have the ThemeContext, we’ll want to provide this theme down to child components. With the ThemeContext created, we can use the Provider component to pass down the Theme.
For instance, in our demo application, we’ll have an App component that uses a Header component. In our App component, we can specify a theme.
class App extends Component {
state = {theme: themes.dark};
// ...
render() {
return (
<div className="App">
<ThemeContext.Provider value={this.state.theme}>
<Header />
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to re\
load.
</p>
<button onClick={this.changeTheme}>Change theme</button>
</ThemeContext.Provider>
</div>
);
}
Passing down the theme through the ThemeContext.Provider allows us to pluck the theme from components lower down. Notice that we are passing the value prop in the ThemeContext.Provider component. Without this value prop, children components cannot access the value of the provider.
The ThemeContext.Provider component is a special component that is specifically designed to only pass down data to child components.
In order to consume the value of the context, we’ll need to use a different component exported by the ThemeContext we previously exported: the Consumer component.
Let’s look at component. We want the component to have access to the global theme context, so we’ll import the ThemeContext here:
import {ThemeContext} from './theme';
Now we can use the ThemeContext consumer to pluck the theme from the parent Provider:
export const Header = props => (
<ThemeContext.Consumer>
{theme => (
<header
className="App-header"
style={{backgroundColor: theme.background}}
>
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title" style={{color: theme.foreground}}>
Welcome to React
</h1>
</header>
)}
</ThemeContext.Consumer>
);
The usage of the Consumer component might look a bit different than what we are used to where the child is a method that passes the value of the Provider as an argument to the method. Using this method, we have access to the passed down prop.
If we want to be able to dynamically update the provided value, we can just change the state.theme in the App as we would change the state normally.
class App extends Component {
state = {theme: themes.dark};
// ...
changeTheme = evt => {
this.setState(state => ({
theme: state.theme === themes.dark ? themes.light : themes.dark
}));
};
// ...
}
Default value
What about that default value we passed in earlier?
import React from 'react';
// ...
export const ThemeContext = React.createContext(themes.dark);
The default value is used within the consumer in the case that the child component is not wrapped in a ThemeContext.Provider component.
Multiple contexts
It’s possible to wrap multiple context providers in our application just as normal. In fact, we don’t have to do anything special. We can simply wrap our components in multiple context Provider components.
Let’s say we have a UserContext, for instance:
import React from 'react';
// ...
export const UserContext = React.createContext(null);
We can get access to the User context in the same way:
import {UserContext} from './user';
// ...
export const Body = props => (
<ThemeContext.Consumer>
{theme => (
<header
className="App-header"
style={{backgroundColor: theme.background}}
>
<UserContext.Consumer>
<h1>{user => (user ? 'Welcome back' : 'Welcome')}</h1>
</UserContext.Consumer>
</header>
)}
</ThemeContext.Consumer>
);
The Consumer component must be derived from the created context. Without this, the value won’t be passed on down. For this reason, we create the context and export it from a file. Otherwise the value won’t have an effect.
state
The second type of data we’ll deal with in our components is state. To know when to apply state, we need to understand the concept of stateful components. Any time a component needs to hold on to a dynamic piece of data, that component can be considered stateful.
For instance, when a light switch is turned on, that light switch is holding the state of “on.” Turning a light off can be described as flipping the state of the light to “off.”
In building our apps, we might have a switch that describes a particular setting, such as an input that requires validation, or a presence value for a particular user in a chat application. These are all cases for keeping state about a component within it.
We’ll refer to components that hold local-mutable data as stateful components. We’ll talk a bit more below about when we should use component state. For now, know that it’s a good idea to have as few stateful components as possible. This is because state introduces complexity and makes composing components more difficult. That said, sometimes we need component-local state, so let’s look at how to implement it, and we’ll discuss when to use it later..
Using state: Building a Custom Radio Button
In this example, we’re going to use internal state to build a radio button to switch between payment methods. Here’s what the form will look like when we’re done:
Simple Switch
Let’s look at how to make a component stateful:
3 class Switch extends React.Component {
4 state = {};
5
6 render() {
7 return (
8 <div>
9 <em>Template will be here</em>
10 </div>
11 );
12 }
13 }
That’s it! Of course, just setting state on the component isn’t all that interesting. To use the state on our component, we’ll reference it using this.state.
4 const CREDITCARD = "Creditcard";
5 const BTC = "Bitcoin";
6
7 class Switch extends React.Component {
8 state = {
9 payMethod: BTC
10 };
11
12 render() {
13 return (
14 <div className="switch">
15 <div className="choice">Creditcard</div>
16 <div className="choice">Bitcoin</div>
17 Pay with: {this.state.payMethod}
18 </div>
19 );
20 }
21 }
22
23 export default Switch;
In our render function, we can see the choices our users can pick from (although we can’t change a method of payment yet) and their current choice stored in the component’s state. This Switch component is now stateful as it’s keeping track of the user’s preferred method of payment.
Our payment switch isn’t yet interactive; we cannot change the state of the component. Let’s hook up our first bit of interactivity by adding an event handler to run when our user selects a different payment method.
In order to add interaction, we’ll want to respond to a click event. To add a callback handler to any component, we can use the onClick attribute on a component. The onClick handler will be fired anytime the component it’s defined on is clicked.
20 render() {
21 return (
22 <div className="switch">
23 <div
24 className="choice"
25 onClick={this.select(CREDITCARD)} // add this
26 >
27 Creditcard
28 </div>
29 <div
30 className="choice"
31 onClick={this.select(BTC)} // ... and this
32 >
33 Bitcoin
34 </div>
35 Pay with: {this.state.payMethod}
36 </div>
37 );
38 }
Using the onClick attribute, we’ve attached a callback handler that will be called every time either one of the elements are clicked.
The onClick handler expects to receive a function that it will call when the click event occurs. Let’s look at the select function:
11 select = choice => {
12 return evt => {
13 // <-- handler starts here
14 this.setState({
15 payMethod: choice
16 });
17 };
18 };
Notice two things about select:
- It returns a function
- It uses setState
Returning a New Function
Notice something interesting about select and onClick: the attribute onClick expects a function to be passed in, but we’re calling a function first. That’s because the select function will return a function itself.
This is a common pattern for passing arguments to handlers. We close over the choice argument when we call select. select returns a new function that will call setState with the appropriate choice.
When one of the child <div> elements are clicked, the handler function will be called. Note that select is actually called during render, and it’s the return value of select that gets called onClick.
Updating the State
When the handler function is called, the component will call setState on itself. Calling setState triggers a refresh, which means the render function will be called again, and we’ll be able to see the current state.payMethod in our view.
setState has performance implications
Since the setState method triggers a refresh, we want to be careful about how often we call it.
Modifying the actual-DOM is slow so we don’t want to cause a cascade of setStates to be called, as that could result it poor performance for our user.
Viewing the Choice
In our component we don’t (yet) have a way to indicate which choice has been selected other than the accompanying text.
It would be nice if the choice itself had a visual indication of being the selected one. We usually do this with CSS by applying an active class. In our example, we use the className attribute.
In order to do this, we’ll need to add some logic around which CSS classes to add depending upon the current state of the component.
But before we add too much logic around the CSS, let’s refactor component to use a function to render each choice:
19 renderChoice = choice => {
20 return (
21 <div className="choice" onClick={this.select(choice)}>
22 {choice}
23 </div>
24 );
25 };
26
27 render() {
28 return (
29 <div className="switch">
30 {this.renderChoice(CREDITCARD)}
31 {this.renderChoice(BTC)}
32 Pay with: {this.state.payMethod}
33 </div>
34 );
35 }
Now, instead of putting all render code into render() function, we isolate the choice rendering into its own function.
Lastly, let’s add the .active class to the choice component.
20 renderChoice = choice => {
21 // create a set of cssClasses to apply
22 const cssClasses = ["choice"];
23
24 if (this.state.payMethod === choice) {
25 cssClasses.push("active"); // add .active class
26 }
27
28 return (
29 <div className={cssClasses.join(" ")} onClick={this.select(choice\
30 )}>
31 {choice}
32 </div>
33 );
34 };
35
36 render() {
37 return (
38 <div className="switch">
39 {this.renderChoice(CREDITCARD)}
40 {this.renderChoice(BTC)}
41 Pay with: {this.state.payMethod}
42 </div>
43 );
44 }
Stateful components
Defining state on our component requires us to set an instance variable called this.state in the object prototype class. In order to do this, it requires us to set the state in one of two places, either as a property of the class or in the constructor.
Setting up a stateful component in this way:
- Allows us to define the initial state of our component.
- Tells React that our component will be stateful. Without this method defined,
our component will be considered to be stateless.
For a component, this looks like:
class InitialStateComponent extends React.Component {
// ...
constructor(props) {
super(props)
this.state = {
currentValue: 1,
currentUser: {
name: 'Ari'
}
}
}
// ...
}
In this example, the state object is just a JavaScript object, but we can return anything in this function. For instance, we may want to set it to a single value:
class Counter extends React.Component {
constructor(props) {
super(props)
this.state = 0
}
}
Setting props inside of our component is always a bad idea. Setting the initial value of the state property is the only time we should ever use props when dealing with a component’s state. That is, if we ever want to set the value of a prop to the state, we should do it here.
For instance, if we have a component where the prop indicates a value of the component, we should apply that value to the state in the constructor() method.
A better name for the value as a prop is initialValue, indicating that the initial state of the value will be set.
For example, consider a Counter component that displays some count and contains an increment and decrement button. We can set the initial value of the counter like this:
const CounterWrapper = props => (
<div key="counterWrapper">
<Counter initialValue={125} />
</div>
);
From the usage of the <Counter> component, we know that the value of the Counter will change simply by the name initialValue. The Counter component can use this prop in constructor(), like so:
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
value: this.props.initialValue
};
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
}
// ...
}
Since the constructor is run once and only once before the component itself is mounted, we can use it to establish our initial state.
State updates that depend on the current state
Counter has buttons for incrementing and decrementing the count:
When the “-“ button is pressed, React will invoke decrement(). decrement() will subtract 1 from state’s value. Something like this would appear to be sufficient:
decrement = () => {
// Appears correct, but there is a better way
const nextValue = this.state.value - 1;
this.setState({
value: nextValue
});
};
However, whenever a state update depends on the current state, it is preferable to pass a function to setState(). We can do so like this:
this.decrement = this.decrement.bind(this);
setState() will invoke this function with the previous version of the state as the first argument.
Why is setting state this way necessary? Because setState() is asynchronous.
Here’s an example. Let’s say we’re using the first decrement() method where we pass an object to setState(). When we invoke decrement() for the first time, value is 125. We’d then invoke setState(), passing an object with a value of 124.
However, the state will not necessarily be updated immediately. Instead, React will add our requested state update to its queue.
Let’s say that the user is particularly fast with her mouse and her computer is particularly slow with its processing. The user manages to click the decrement button again before React gets around to our previous state update. Responding to user interactions are high-priority, so React invokes decrement(). The value in state is still 125. So, we enqueue another state update, again setting value to 124.
React then commits both state updates. To the dismay of our astute and quickfingered user, instead of the correct value of 123 the app shows a count of 124.
In our simple example, there’s a thin chance this bug would occur. But as a React app grows in complexity, React might encounter periods where it is overloaded with high-priority work, like animations. And it is conceivable that state updates might be queued for consequential lengths of time.
Whenever a state transition depends on the current state, using a function to set the state helps to avoid the chance for such enigmatic bugs to materialize.
For further reading on this topic, see our own Sophia Shoemaker’s post Using a function in setState instead of an object⁵².
Thinking About State
Spreading state throughout our app can make it difficult to reason about. When building stateful components, we should be mindful about what we put in state and why we’re using state.
Generally, we want to minimize the number of components in our apps that keep component-local state.
If we have a component that has UI states which:
- cannot be “fetched” from outside or
- cannot be passed into the component,
that’s usually a case for building state into the component.
However, any data that can be passed in through props or by other components is usually best to leave untouched. The only information we should ever put in state are values that are not computed and do not need to be sync’d across the app.
The decision to put state in our components or not is deeply related to the tension between “object-oriented programming” and “functional programming”.
In functional programming, if you have a pure function, then calling the same function, with the same arguments, will always return the same value for a given set of inputs. This makes the behavior of a pure function easy to reason about, because the output is consistent at all times, with respect to the inputs.
In object-oriented programming you have objects which hold on to state within that object. The object state then becomes implicit parameters to the methods on the object. The state can change and so calling the same function, with the same arguments, at different times in your program can return different answers.
This is related to props and state in React components because you can think of props as “arguments” to our components and state as “instance variables” to an object.
If our component uses only props for configuring a component (and it does not use state or any other outside variables) then we can easily predict how a particular component will render.However, if we use mutable, component-local state then it becomes more difficult to understand what a component will render at a particular time.
So while carrying “implicit arguments” through state can be convenient, it can also make the system difficult to reason about.
That said, state can’t be avoided entirely. The real world has state: when you flip a light switch the world has now changed - our programs have to be able to deal with state in order to operate in the real world.The good news is that there are a variety of tools and patterns that have emerged for dealing with state in React (notably Flux and its variants), which we talk about elsewhere in the book. The rule of thumb you should work with is to minimize the number of components with state.
Keeping state is usually good to enforce and maintain consistent UI that wouldn’t otherwise be updated. Additionally, one more thing to keep in mind is that we should try to minimize the amount of information we put into our state. The smaller and more serializable we can keep it (i.e. can we easily turn it into JSON), the better. Not only will our app be faster, but it will be easier to reason about. It’s usually a red-flag when our state gets large and/or unmanageable.
One way that we can mitigate and minimize the complex states is by building our apps with a single stateful component composed of stateless components: components that do not keep state.
Stateless Components
An alternative approach to building stateful components would be to use stateless components. Stateless components are intended to be lightweight components that do not need any special handling around the component.
Stateless components are React’s lightweight way of building components that only need the render() method.
Let’s look an example of a stateless component:
const Header = function(props) {
return (<h1>{props.headerText}</h1>)
}
Notice that we don’t reference this when accessing our props as they are simply passed into the function. The stateless component here isn’t actually a class in the sense that it isn’t a ReactElement.
We won’t reference this when working with functional, stateless components. They are just functions and do not have a backing instance. These components cannot contain state and do not get called with the normal component lifecycle methods.
React does allow us to use propTypes and defaultProps on stateless components.
With so many constraints, why would we want to use stateless components? There are two reasons:
First, as we discussed above, stateful components often spread complexity throughout a system. Using stateless components where possible can help contain the state in fewer locations. This makes our programs easier to reason about.
Second, using functional components can have performance benefits. There’s less “ceremony” around component setup and tear-down. The React core team has mentioned that more performance improvements may be introduced for functional components in the future.
A good rule of thumb is to use stateless components as much as we can. If we don’t need any lifecycle methods and can get away with only a rendering function, using a stateless component is a great choice.
Switching to Stateless
Can we convert our Switch component above to a stateless component? Well, the currently selected payment choice is state and so it has to be kept somewhere.
While we can’t remove state completely, we could at least isolate it. This is a common pattern in React apps: try to pull the state into a few parent components.
In our Switch component we pulled each choice out into the renderChoice function. This indicates that this is a good candidate for pulling into its own stateless component. There’s one problem: renderChoice is the function that calls select, which means that it indirectly is the function that calls setState. Let’s take a look at how to handle this issue:
7 const Choice = ({ active, onClick, label }) => {
8 const cssClasses = ["choice"];
9
10 if (active) {
11 // <-- check props, not state
12 cssClasses.push("active");
13 }
14
15 return (
16 <div className={cssClasses.join(" ")} onClick={onClick}>
17 {label} {/* <-- allow any label */}
18 </div>
19 );
20 };
Here we’ve created a Choice function which is a stateless component.
The syntax might look a little odd if you haven’t been using JavaScript very long. What we’re doing here is destructuring the props. This is idomatic for functional React components.
Instead of passing the argument as props and calling, props.active, props.label, etc. We destructure props into the variables active, onClick,
and label as the function is called.
You’ll see this pattern all the time in modern React code.
But we have a problem: if our component is stateless then we can’t read from state. What do we do instead? Pass the arguments down through props.
In Choice we make three changes (which is marked by comments in the code above):
- We determine if this choice is the active one by reading active
- When a Choice is clicked, we call whatever function that is on onClick
- The label is determined by label
All of these changes mean that Choice is decoupled from the Switch statement. We could now conceivably use Choice anywhere, as long as we pass active, onClick, and label through the props.
Let’s look at how this changes Switch:
35 render() {
36 return (
37 <div className="switch">
38 <Choice
39 onClick={this.select(CREDITCARD)}
40 active={this.state.payMethod === CREDITCARD}
41 label="Pay with Creditcard"
42 />
43 <Choice
44 onClick={this.select(BTC)}
45 active={this.state.payMethod === BTC}
46 label="Pay with Bitcoin"
47 />
48 Paying with: {this.state.payMethod}
49 </div>
50 );
51 }
Here we’re using our Choice component and passing the three props (parameters) active, onClick, and label. What’s neat about this is that we could easily:
- Change what happens when we click this choice by changing the input to onClick
- Change the condition by which a particular choice is considered active by changing the active prop
- Change what the label is to any arbitrary string
By creating a stateless component Choice we’re able to make Choice reusable and not be tied to any particular state.
Stateless Encourages Reuse
Stateless components are a great way to create reusable components. Because stateless components need to have all of their configuration passed from the outside, we can often reuse stateless components in nearly any project, provided that we supply the right hooks.
Now that we’ve covered both props, context, and state we’re going to cover a couple more advanced features we can use with components.
Our components exist in a hierarchy and sometimes we need to communicate (or manipulate) the children components. The the next section, we’re going to discuss how to do this.
Talking to Children Components with props.children
While we generally specify props ourselves, React provides some special props for us. In our components, we can refer to child components in the tree using this.props.children.
For instance, say we have a Newspaper component that holds an Article:
3 const Newspaper = props => {
4 return (
5 <Container>
6 <Article headline="An interesting Article">
7 Content Here
8 </Article>
9 </Container>
10 )
11 }
The container component above contains a single child, the Article component. How many children does the Article component contain? It contains a single child, the text Content Here.
In the Container component, say that we want to add markup around whatever the Article component renders. To do this, we write our JSX in the Container component, and then place this.props.children:
3 class Container extends React.Component {
4 render() {
5 return <div className="container">{this.props.children}</div>;
6 }
The Container component above will create a div with class=’container’ and the children of this React tree will render within that div.
Generally, React will pass the this.props.children prop as a list of components if there are multiple children, whereas it will pass a single element if there is only one component.
Now that we know how this.props.children works, we should rewrite the previous Container component to use propTypes to document the API of our component. We can expect that our Container is likely to contain multiple Article components, but it might also contain only a single Article. So let’s specify that the children prop can be either an element or an array.
If PropTypes.oneOfType seems unfamiliar, refer to “Appendix A: PropTypes](#appendix_prop_types)” which explains how it works.
1 class DocumentedContainer extends React.Component {
2 static propTypes = {
3 children: PropTypes.oneOf([PropTypes.element, PropTypes.array])
4 };
5 // ...
6 render() {
7 return <div className="container">{this.props.children}</div>;
8 }
9 }
It can become cumbersome to check what type our children prop is every time we want to use children in a component. We can handle this a few different ways:
- Require children to be a single child every time (e.g., wrap our children in their own element).
- Use the Children helper provided by React.
The first method of requiring a child to be a single element is straightforward. Rather than defining the children above as oneOfType(), we can set the children to a be a single element.
1 class SingleChildContainer extends React.Component {
2 static propTypes = {
3 children: PropTypes.element.isRequired
4 };
5 // ...
6 render() {
7 return <div className="container">{this.props.children}</div>;
8 }
9 }
Inside the SingleChildContainer component we can deal with the children always being able to be rendered as a single leaf of the hierarchy.
The second method of is to use the React.Children utility helper for dealing with the child components. There are a number of helper methods for handling children, let’s look at them now.
React.Children.map() & React.Children.forEach()
The most common operation we’ll use on children is mapping over the list of them. We’ll often use a map to call React.cloneElement() or React.createElement() along the children.
map() and forEach()
Both the map() and forEach() function execute a provided function once per each element in an iterable (either an object or array).
[1, 2, 3].forEach(function(n) {
console.log("The number is: " + n);
return n; // we won't see this
})
[1, 2, 3].map(function(n) {
console.log("The number is: " + n);
return n; // we will get these
})
The difference between map() and forEach() is that the return value of map() is an array of the result of the callback function, whereas forEach() does not collect results.
So in this case, while both map() and forEach() will print the console.log statements, map() will return the array [1, 2, 3] whereas forEach() will not.
Let’s rewrite the previous Container to allow a configurable wrapper component for each child. The idea here is that this component takes:
1.A prop component which is going to wrap each child
2.A prop children which is the list of children we’re going to wrap
To do this, we call React.createElement() to generate a new ReactElement for each child:
1 class MultiChildContainer extends React.Component {
2 static propTypes = {
3 component: PropTypes.element.isRequired,
4 children: PropTypes.element.isRequired
5 };
6 // ...
7 renderChild = (childData, index) => {
8 return React.createElement(
9 this.props.component,
10 {}, // <~ child props
11 childData // <~ child's children
12 );
13 };
14 // ...
15 render() {
16 return (
17 <div className="container">
18 {React.Children.map(this.props.children, this.renderChild)}
19 </div>
20 );
21 }
22 }
Again, the difference between React.Children.map() and React.Children.forEach() is that the former creates an array and returns the result of each function and the latter does not. We’ll mostly use .map() when we render a child collection.
React.Children.toArray()
props.children returns a data structure that can be tricky to work with. Often when dealing with children, we’ll want to convert our props.children object into a regular array, for instance when we want to re-sort the ordering of the children elements. React.Children.toArray() converts the props.children data structure into an array of the children.
1 class ArrayContainer extends React.Component {
2 static propTypes = {
3 component: PropTypes.element.isRequired,
4 children: PropTypes.element.isRequired
5 };
6 // ...
7 render() {
8 const arr = React.Children.toArray(this.props.children);
9
10 return <div className="container">{arr.sort((a, b) => a.id < b.id)}\
11 </div>;
12 }
13 }
Summary
By using props and context we get data into our components and by using PropTypes we can specify clear expectations about what we require that data to be.
By using state we hold on to component-local data, and we tell our components to re-render whenever that state changes. However state can be tricky! One technique to minimize stateful components is to use stateless, functional components.
Using these tools we can create powerful interactive components. However there is one important set of configurations that we did not cover here: lifecycle methods.
Lifecycle methods like componentDidMount and componentDidUpdate provide us with powerful hooks into the application process. In the next chapter, we’re going to dig deep into component lifecycle and show how we can use those hooks to validate forms, hook in to external APIs, and build sophisticated components.