Beruflich Dokumente
Kultur Dokumente
of Contents
BASICS
Introduction 1.1
Building blocks 1.2
JSX 1.3
Set up 1.4
Component introduction 1.5
Props 1.6
State 1.7
Methods 1.8
Thinking in Components 1.9
Conditional rendering 1.10
Life cycle 1.11
Dealing with input elements 1.12
Forms 1.13
Forms II - validation 1.14
Formik - part II 1.15
AJAX /HTTP 1.16
STYLING
styled-components 2.1
inline styling 2.2
sass/less 2.3
IMAGES
Adding images 3.1
ROUTING
1
Core concepts 4.1
Dealing with router and query params 4.2
Programmatic navigation 4.3
Dealing with lazy loading/ code splitting 4.4
ADVANCED
DataContext API 5.1
Decorators 5.2
PATTERNS
Render props 6.1
Hooks 6.2
TESTING
Jest 7.1
Nock 7.2
Enzyme 7.3
react-testing-library 7.4
cypress 7.5
REDUX
Redux - overview 8.1
Actions 8.2
Reducers 8.3
Store 8.4
Add Redux to React 8.5
Handling side effects with Sagas 8.6
Redux Form 8.7
2
MOBX
Observables 9.1
Computed variables 9.2
Reactions 9.3
LIBRARIES
storybook 10.1
Joi - awesome code validation 10.2
3
Introduction
React
React is a very popular choice when building a SPA, Single Page Application.
A Single Page Application differs from a normal web application in that you remain on the
same URL and thereby the same page, hence the name. To create many pages in a Single
Page Application we instead use the hash, # and listen to changes on it, for example
http://myapp.com#/products and http://myapp.com#/users is considered different pages in
The point of this book is to try to cover what I've learnt and hopefully make other people
better in the process. Everyone benefits from us being better at using something.
If you are new to React spend some time in the basic section before moving on. It's
important to know your basics ( I've learned this lesson the hard way :) )
Credits
Than you to the React team at Facebook for all your efforts in creating React
Thank you John Papa for your PRs and suggestions and making this community book
better
Happy reading
Chris
4
Building blocks
Building blocks
Components
JSX
React
ReactDOM
5
JSX
JSX
JSX is pretty much you writing XML in JavaScript. It's a preprocessor step. You don't have to
have it but it makes life a whole lot easier.
Simple example
This is a simple example on one line of code:
The above looks like XML in JavaScript. When it is being processed it is turned into the
following ES5 code:
Ok so Elem becomes the element name, the second argument that above is null are our
element attributes, which we don't have any. The third and last argument is the elements
value. Let's look at an example below where we give it a property:
// usage:
<div>
<Elem title="a title">
</div>
Above we can see that our attribute title is now part of the second argument.
6
JSX
Multiline
Most o the time you will define JSX over several different rows and starting out new it might
stump you why it doesn't work. You simply need to wrap it in a parenthesis, like so:
const Elem =
(
<div>
<h1>Some title</h1>
<div>Some content</div>
</div>
)
One parent
JSX needs to have one parent. The following would be incorrect:
We can fix this by either wrapping the in div element like so:
const Elem =
(
<div>
<h1>Some title</h1>
<div>Some content</div>
</div>
)
const Elem = (
<React.Fragment>
<h1>Some title</h1>
<div>Some content</div>
</React.Fragment>
)
7
JSX
React.Fragment would be the parent element needed instead of us using a div element just
to make JSX happy.
Summary
This is pretty much all we need to know on JSX to be able to work with it:
8
Set up
Set up
// app.js
// index.html
<html>
<body>
<!-- This is where our app will live -->
<div id="app"></div>
<!-- These are script tags we need for React, JSX and ES2015 features -->
<script src="https://fb.me/react-15.0.0.js"></script>
<script src="https://fb.me/react-dom-15.0.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.
js"></script>
<script type="text/babel" src="app.js"></script>
</body>
</html>
and type:
9
Set up
http-server -p 5000
The drawbacks to the above approach is that everything is compiled at runtime which is
horribly slow but its great for learning React, but please don't put it like this in production.
create-react-app
Create react is a scaffolder developed by Dan Abramov. With it you are able to scaffold a
React application with easy and is up and running in no time. The simplest way to get
started is downloading it, like so:
create-react-app is a very active project and can truly help you in the beginning of you
learning React and even later. Question is really how much you want to be able to configure
your project. When you become skilled enough you may come to a point when you don't
need create-react-app, but until that point, it is your friend. You can read more on it in the
official documentation, https://github.com/facebook/create-react-app
do it yourself
10
Component introduction
// usage in markup
<div>
<Jedi />
</div>
Ok, great I know how to create a Component but how do I actually create a React app?
That's a fair comment. In the previous chapter Set up we show three different ways to help
you get started, unpkg, create-react-app and setting up your own project with Webpack. For
now let's stick to the simpler version with unpkg. For this we will need the following files:
index.html
app.js
11
Component introduction
// index.html
<html>
<body>
<!-- This is where our app will live -->
<div id="app"></div>
<!-- These are script tags we need for React, JSX and ES2015 features -->
<script src="https://fb.me/react-15.0.0.js"></script>
<script src="https://fb.me/react-dom-15.0.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.
js"></script>
<script type="text/babel" src="app.js"></script>
</body>
</html>
12
Props
Introducing props
Adding props to your component means we add attributes to our component element. We
can read what these attributes are and make them part of the markup, like so:
const jediData = {
name: 'Yoda'
};
In the above code we assign jediData to the attribute jedi . An attribute on component is
known as a prop. To access it we simply need to type this.props.jedi . In the render()
method above we type this.props.jedi.name and thereby we access the object we
assigned to it and drill down to the name attribute.
Assigning a list
We usually deal with either rendering an object or a list or primitive. To render a list is almost
as simple as rendering an object. A list of Jedis is only about rendering a list Jedi
components, like so:
13
Props
const jedis = [{
name: 'Yoda'
},
{
name: 'Palpatine'
}]
Above we use a simple map() function to output a list of Jedi elements. For each item in
the list we bind that to our jedi property on our Jedi component.
PropTypes
As our components becomes more and more complicated we want to ensure we use them
correctly. For example if a component has a certain property set it should render it. We might
even go so far as to convey that if a property is not set we should throw an error. The library
prop-types helps us to define what properties are a must and helps us define what type they
are. Let's download it and demonstrate those features:
14
Props
importing it
define the properties your component should have and what type they are
15
Props
Thereafter we extended our component by assigning an object propTypes to it, like so:
Jedi.propTypes = {
jedi : PropType.shape({
name: PropType.string.isRequired,
})
};
Above we are saying that the property jedi is a complex shape that consist of a name
property.
Below in our markup we are provoking an error by creating a Jedi component and giving it
an object that does NOT fullfill the requirements we have on it:
index.js:2178 Warning: Failed prop type: The prop `jedi.name` is marked as required
in `Jedi`, but its value is `undefined`.
The reason we get the above error message is that we provide an object with the property
title instead of name . If we were to change the above to the following, the error would
disappear:
Best practice
It is considered best practice to use this lib. It is further considered a best practice to mark
up every single input with a specific type. Also it's considered a better practice to move in
the propTypes as member of the class, like so:
16
Props
render() {
return (
<div>Name: {this.props.jedi.name}</div>
);
}
}
Summary
We introduce props. They are the way we are able to pass data into a component. We have
learned the following:
We simply declare them as attribute on our React element when we want to pass in
something <Elem attr={data}>
We can pass in object or a list, both works
We should use the library prop-types to ensure our component get the data they expect
so we can capture errors early
17
State
render() {
return (
<div>{this.props.name}</div>
<button onClick={() => this.changeName()} ></button>
)
}
}
// usage
In the above example we try to change the name property but React won't let us do that.
Instead we need to rely on state to do so.
18
State
render() {
// DESTRUCTURING
const { name } = this.props;
return (
<div>{name}</div>
<button onClick={() => this.changeName()} >Change name</button>
)
}
this.setState({
name: 'new name'
})
One important thing to know though. If the state object is way bigger than that, like so:
19
State
state = {
name : '',
products: []
}
constructor() {
this.state = {
name : this.props.name
}
}
changeName() {
this.setState({
name: 'new name'
})
}
render() {
return (
<div>{this.props.name}</div>
<button onClick={() => this.changeName()} >Change name</button>
)
}
}
// usage
Above we are now using the state functionality so we can change the value. We are
however creating a copy of the props value in the constructor :
20
State
constructor() {
this.state = {
name : this.props.name
}
}
someMethod() {
this.setState({
name : 'some value'
});
It's usually better to wait until you are in the render() method and then do your
comparison, like so:
21
State
constructor() {
this.state = {
show : false
}
}
toggleShow() {
this.setState({
show: !this.state.show
})
}
render() {
return (
<div>Element</div>
// better to access the state here, when it has its new value we act accordingly
{ this.state.show &&
<div>show this..</div>
}
<button onClick={() => this.toggleShow()} >Toggle</button>
)
}
}
// usage
<Element />
If you really want to know exactly when the change in the state happens, you can provide a
callback to setState() , like so:
someMethod() {
this.setState({
name : 'some value'
}, () => {
// state changed here
if(this.state.name === 'some value') {
// do something
}
});
}
22
State
23
Methods
Methods
When you build your component you are going to want to add methods to it. You are going
to attach methods to different events such as submit , click , change etc. One thing you
need to keep in mind is that React changes the name and the casing of the event like so:
Event examples
Let's have a look at how to set up a method to an event:
render() {
return (
<button onClick={this.clicked}></button>
)
}
}
24
Methods
clicked() {
console.log('clicked ' + this.state.str);
}
render() {
return (
<button onClick={this.clicked}></button>
)
}
}
The above code WILL give out an error as it doesn't know what state is. This is because our
this points wrong. There are several ways to fix this. Let's look at the first one:
constructor() {
super();
this.clicked = this.clicked.bind(this);
}
state = {
str: 'test'
}
clicked() {
console.log('clicked ' + this.state.str);
}
render() {
return (
<button onClick={this.clicked}></button>
)
}
}
We are above declaring a constructor and binding our method clicked() to the object
instance, like so:
constructor() {
super();
this.clicked = this.clicked.bind(this);
}
25
Methods
In this version we use a lambda in the set up in the markup. The code looks like this:
constructor() {
super();
this.clicked = this.clicked.bind(this);
}
state = {
str: 'test'
}
clicked() {
console.log('clicked ' + this.state.str);
}
render() {
return (
<button onClick={() => this.clicked()}></button>
)
}
}
26
Methods
constructor() {
super();
this.clicked = this.clicked.bind(this);
}
state = {
str: 'test'
}
clicked = () => {
console.log('clicked ' + this.state.str);
}
render() {
return (
<button onClick={this.clicked}></button>
)
}
}
Notice the difference between declaring the method in the old way, like this:
clicked() {
console.log('clicked ' + this.state.str);
}
clicked = () => {
console.log('clicked ' + this.state.str);
}
27
Methods
state = {
str: 'test'
}
clicked = () => {
console.log('current value of the input', this.state.str);
}
render() {
return (
<React.Fragment>
<input onChange={this.changed} placeholder="some value" >
<button onClick={this.clicked}>Save</button>
</React.Fragment>
)
}
}
Above we are hooking up the onChange event to the changed() method. In the changed()
method we are setting the state every time the onChange is invoked, which is on every key
up. Once we press our button we then read from our state.str and we can see that the
latest value of our input field is being printed out.
28
Thinking in Components
A list of todos
We start off by a list of todos and we realise we need to be able to render said list. Before
we consider how we should render it out on the screen, let's think about the underlying data
structure.
A todo has a description. Next interesting thing to think about is how do we complete a todo.
The intention of having a list of things we need to carry out it as at some point in time we will
hopefully carry out those items on the list. Now we have to ask ourselves wether we want to
remove a finished item in our todo list or simply mark it as completed. We opt for the latter to
feel good about ourselves for completing it but also so what we can see what we have
carried out historically. This tells us something about our data structure. It should most likely
look something like this:
[{
title: 'clean',
done: false
}, {
title: 'do dishes',
done: false
}]
The above list seems like a reasonable design of the data structure and now we can turn to
trying to render it in React.
29
Thinking in Components
{todos.map(todo => (
<div>
<input type="checkbox" checked={todo.done} /> {todo.title}
</div>
)}
At this point we are able to render our todo list but we are not able to change the value of the
todo. Let's build this out to a real React component class and add support for changing a
todo to done.
30
Thinking in Components
import './App.css';
const todos = [{
title: 'clean',
done: false,
},
{
title: 'do the dishes',
done: true,
}];
Above we have created a fully working component but we are yet to add support for
changing our todos . Let's do that next. We need to do the following:
Listen to onChange is quite simple, we just need to add a method that listens to it like so:
31
Thinking in Components
After that we need to figure out a way to change a todo in our list. We could be altering the
todos list directly but the more React thing todo would be to create a todos state in the
component, like so:
state = {
todos
}
import './App.css';
const todos = [{
title: 'clean',
done: false,
id: 1,
},
{
title: 'do the dishes',
done: true,
id: 2,
}];
32
Thinking in Components
});
this.setState({
todos: newTodos,
});
}
render() {
return (
<Todos>
<h2>Todos</h2>
{this.state.todos.map(todo => (
<Todo key={todo.id}>
<input type="checkbox" onChange={() => this.handleChecked(todo)} checked={
todo.done} />
{todo.title}
</Todo>
))}
</Todos>
);
}
}
Let's zoom in on the handleChecked() method here to realise what we have done:
this.setState({
todos: newTodos,
});
}
we go through the list, todo by todo until we find the selected todo then we change it state
by doing an object spread:
Another thing worth noting is that our todo now consist of three properties:
title
33
Thinking in Components
done
id
Our Todos.js will contain pretty much all of what App.js used to contain:
// Todos.js
state = {
todos: this.props.todos,
};
34
Thinking in Components
});
this.setState({
todos: newTodos,
});
}
render() {
return (
<TodosContainer>
<h2>Todos</h2>
{this.state.todos.map(todo => (
<Todo key={todo.id}>
<input type="checkbox" onChange={() => this.handleChecked(todo)} checked={
todo.done} /> {todo.title}
</Todo>
))}
</TodosContainer>
);
}
}
35
Thinking in Components
import './App.css';
const todos = [{
title: 'clean',
done: false,
id: 1,
},
{
title: 'do the dishes',
done: true,
id: 2,
}];
Todos, this would render a list of Todo components and handle all the events
Todo, this would only be responsible for rendering a Todo and send any change action
upwards
36
Thinking in Components
// Todo.js
Todo.propTypes = {
todo: PropTypes.shape({
title: PropTypes.string,
done: PropTypes.bool,
id: PropTypes.number,
}),
handleChecked: PropTypes.func,
};
Above we have broken out the Todo rendering into its own component. As you can see the
component is not defined as a class inheriting from React.Component but is simply just a
function. This is called a presentation or dumb component. What makes it dumb is that it
knows nothing about the context it is in only that it relies on input, todo and invokes any
action that it is being provided through its props, namely handleChecked() . Our Todos file
now looks a bit simpler like so:
37
Thinking in Components
// Todos.js
state = {
todos: this.props.todos,
};
this.setState({
todos: newTodos,
});
}
render() {
return (
<TodosContainer>
<h2>Todos</h2>
{this.state.todos.map(todo => (
<Todo todo={todo} key={todo.id} handleChecked={this.handleChecked} />
))}
</TodosContainer>
);
}
}
38
Thinking in Components
Above we simply let the Todo component handle all the rendering and we provide it with the
data todo and the method handleChecked() . Ok so we mentioned presentational
components so far so what is the other kind of component the container component. A
container component is a component where data and logic lives. It 'contains' the meat of the
application. In our app the Todos component is a container component and the Todo is a
presentational component. If you are building your app in the right way you will end up with a
majority of presentational components and a few container components.
Summary
We set out to create a working Todo app. We did so. We also set out to break down the
mentioned app into many small components where each component was more focused on
its task making future changes or reuse a lot easier. We introduced the term
Dumb/Presentation component and Container component and explained what those were.
to show that it only relied on inputs.
39
Conditional rendering
Conditional rendering
Conditional rendering is about deciding what to render given a value of a variable. There are
different approaches we can have here:
render if true
ternary expression
Render if true
Let's have a look at the first version:
state = {
show: false
};
render() {
return (
<React.Fragment>
<div>some data</div>
{ this.state.show &&
<div>body content</div>
}
<button onClick={this.toggle}></button>
</React.Fragment>
);
}
}
Above we can see that look at the variable show in our state and renders a div if show is
truthy:
{ this.state.show &&
<div>body content</div>
}
40
Conditional rendering
Ternary rendering
In this version we define a ternary expression and render different things depending on the
value of our variable:
state = {
loading: false,
data: void 0
};
render() {
return (
<React.Fragment>
{this.state.loading ?
<div>loading...</div> :
<div>{this.state.data}</div>
}
</React.Fragment>
);
}
}
{ this.state.loading ?
<div>loading...</div> :
<div>{this.state.data}</div>
}
41
Conditional rendering
state = {
loading: false,
data: void 0
};
getData() {
if (this.state.loading) {
return <div>loading...</div>;
} else if(this.state.data) {
return <div>{this.state.data}</div>;
}
return <div>{this.state.data}</div>;
}
render() {
return (
<React.Fragment>
<div>something</div>
{ this.getData() }
</React.Fragment>
);
}
}
We can't use if , else if and so on directly in the template but we can have a method
getData() that can decide for us what ot render out. Let's highlight what we did above,
42
Conditional rendering
getData() {
if (this.state.loading) {
return <div>loading...</div>;
} else if(this.state.data) {
return <div>{this.state.data}</div>;
}
return <div>{this.state.data}</div>;
}
{ this.getData() }
43
Life cycle
Life cycle
componentDidMount
component is mounted and ready to be used
componentWillReceiveProps
props values are changed and you should react accordingly. do we need to refetch AJAX
data?
verify that change has actually happened and act accordingly. Sometimes React just calls
this so verify that a change has actually taken place.
shouldComponentUpdate
should return true. This is about props changing values and React asking for permission to
change, given that a change has happened. Default answer is true.
You can improve performance here if you know what you are doing. Example of cases
where you wan't to control this, imagine a big table and you only want to change things that
matter.
componentWillUnmount
44
Dealing with input elements
45
Forms
Forms
The default behaviour when submitting a form is form to post the result and move on to
another page. Your markup usually looks like so:
<Form>
<input type="text" name="name" id="name" />
<button>Submit</button>
</Form>
Controlled components
In React however you tend to want to control Forms a bit more. You usually wants to:
You can verify a forms validity by listening to onSubmit() method. That will give you the
form as input and you are able to inspect and determine wether the forms data should be
persisted. It will look like this:
render() {
return (
<div className="App">
<form onSubmit={this.onSubmit}>
<input name="name" id="name" />
<button>Save</button>
</form>
</div>
);
}
}
46
Forms
render() {
return (
<div className="App">
<form onSubmit={this.onSubmit}>
<div>
<label>First name</label>
<input name="firstname" id="firstname" value={this.state.firstname} onChan
ge={this.handleChange} />
{this.state.firstname}
</div>
<div>
<button>Save</button>
</div>
</form>
</div>
);
}
}
Above we are creating the method handleChange() that reacts every time the change event
is triggered. We can see that we subscribe to change event when we connect the
handleChange() method to the onChange , like so:
47
Forms
<input name="firstname"
id="firstname"
value={this.state.firstname}
onChange={this.handleChange}
/>
48
Forms
render() {
return (
<div className="App">
<form onSubmit={this.onSubmit}>
<div>
<label>First name</label>
<input name="firstname" id="firstname" value={this.state.firstname} onChan
ge={this.handleChange} />
{this.state.firstname}
</div>
<div>
<label>Last name</label>
<input name="lastname" id="lastname" value={this.state.lastname} onChange=
{this.handleChange} />
{this.state.lastname}
</div>
<div>
<button>Save</button>
</div>
</form>
</div>
);
}
}
49
Forms
We replaced setting a specific key by name, firstname in this case, to a more generic
variant [ev.target.name] . This will enable us to hook up the handleChange() method to any
input of the same type.
<div>
Man <input type="radio" name="weather" value="sunshine" onChange={this.handleChang
e} />
Woman <input type="radio" name="weather" value="rain" onChange={this.handleChange}
/>
</div>
As we can see above we can easily attach the handleChanged() method to the onChange .
What about check boxes? Well for checkboxes you can select several different ones, if you
check a checkbox then a checkbox of the same group shouldn't be deselected, unlike a
radio button. Checkboxes are a little bit different in nature as they require us to look at the
property checked instead of value. Let's have a look at their markup:
<div>
Yellow <input type="checkbox" name="yellow" id="yellow" value={this.state.yellow}
onChange={this.handleChange} />
</div>
<div>
Blue <input type="checkbox" name="blue" id="blue" value={this.state.yellow} onChan
ge={this.handleChange} />
</div>
<div>
Red <input type="checkbox" name="red" id="red" value={this.state.yellow} onChange={
this.handleChange} />
</div>
As mentioned we need to look at the property checked , this means we need to alter the
method handleChange() slightly, to this:
50
Forms
this.setState({
[ev.target.name]: value,
});
}
From the above code we see that we inspect to see wether target.type is of type
checkbox , if so we look at the checked property to find our value.
Select list
Select lists are quite easy, we can construct those using a select tag and many option
tags, like so:
Above we simply repeat out a list of products and set the value property and the inner
HTML of the option element. We also set the selected property if our
this.state.product corresponds to the rendered out element.
51
Forms II - validation
Forms II - validation
Validating forms can be done in different ways:
Validate an element as soon as you type, and immediately indicate any error
Validate the form on pressing submit
Validate an element
You can have all the elements and their validation in one giant component but it is usually
better to create a dedicated component for this. A component like this should have the
following:
render the element itself, this will give us more control over how the element is rendered
an element where you can output the validation error
a way to listen to changes and validate itself while the user types
a way to indicate to the form that it is no longer valid should a validation error occur
As you can see above we make the Input component render an input element tag. We
also ensure that all properties set on the component make it to the underlying input by typing
{ ...this.props} . A more manual version of the last one would be to type:
52
Forms II - validation
Depending on how many attributes we want to send to the underlying input, it could be quite
a lot of typing.
As we are now in control of how the input is rendered we can do all sorts of things like
adding a div element, give it padding, margin, borders etc. Best part is we can reuse this
component in a lot of places and all of our inputs will look nice and consistent.
Adding validation
Now we will see that it pays off to wrap our input element in a component. Adding
validation to our element is as easy as:
render() {
return (
<InputContainer>
{this.state.error &&
<ErrorMessage>{this.state.error}</ErrorMessage>
}
<div>
{this.props.desc}
</div>
<InnerInput value={this.state.data} onChange={this.handleChange} {...this.props}
/>
</InputContainer>
);
}
Here we are conditionally displaying the error message, assuming its on the state:
{this.state.error &&
<ErrorMessage>{this.state.error}</ErrorMessage>
}
53
Forms II - validation
Our function above simply tests wether our input value matches a RegEx pattern and if so its
valid, if not, then we return the error message.
We do two things here, firstly we call validate() to see if there was an error and secondly
we set the state, which is our value and the error. If the error is an empty string then it is
counted as falsy . So we can always safely set the error property and any error message
would only be visible when it should be visible.
The full code for our component so far looks like this:
54
Forms II - validation
background: pink;
color: white;
`;
state = {
error: '',
data: '',
}
render() {
return (
<InputContainer>
{this.state.error &&
<ErrorMessage>{this.state.error}</ErrorMessage>
}
<div>
{this.props.desc}
</div>
<InnerInput value={this.state.data} onChange={this.handleChange} {...this.props
} />
</InputContainer>
);
}
}
55
Forms II - validation
Usually when you put input elements in a form you want to be able to tell the form that one
or more invalid inputs exist and you want the stop the form from being submitted. To do so
we need send a message to our form every time a value changes and if there is a validation
error, the form will know. To accomplish that we need to do the following:
add a notify input property, this will be a function we can call as soon as we validated
the latest change
call the notify function
We there update our handleChange() method to now make a call to the notify() function
that we pass in, like so:
56
Forms II - validation
render() {
return (
<FormContainer>
<div>
<Input
errMessage="Must contain 2-3 digits"
desc="2-3 characters"
name="first-name"
notify={this.notify}
title="I am a custom inbox" />
</div>
<button>Submit</button>
</FormContainer>
);
}
}
At this point we have hooked up our notify input property to a method on our component
called notify() , like so:
<Input
errMessage="Must contain 2-3 digits"
desc="2-3 characters"
name="first-name"
notify={this.notify}
title="I am a custom inbox"
/>
As you can see our notify() method doesn't do much yet, but it will:
57
Forms II - validation
So what do we need to accomplish with a call to notify() ? The first thing we need to
accomplish is telling the form that one of your inputs is invalid. The other is to set the whole
form as invalid. Based on that we define our notify() code as the following:
We see above that we after having updated our state for our input element we set the state
for isValid and call the method validForm() to determine its value. The reason for setting
the isValid state like this is that setState() doesn't happen straight away so it is only in
the callback that we can guarantee that it's state has been updated.
isValid is the property we will use in the markup to determine wether our form is valid.
validForm = () => {
const keys = Object.keys(this.state);
for (let i = 0; i < keys.length; i++) {
if (keys[i] === 'isValid') { continue; }
if (!this.state[keys[i]]) {
return false;
}
}
return true;
}
Above we are looping through our state and is looking for wether one of the input elements
are invalid. We skip isValid as that is not an element state.
58
Forms II - validation
If we do the first variant we only need to change the markup to the following:
render() {
return (
<FormContainer onSubmit={this.handleSubmit}>
<div>
<Input
errMessage="Must contain 2-3 digits"
desc="2-3 characters"
name="first-name"
notify={this.notify}
title="I am a custom inbox"
/>
</div>
<button disabled={!this.state.isValid}>Submit</button>
</FormContainer>
);
}
<button disabled={!this.state.isValid}>Submit</button>
We read from our isValid property and we are to disable our button when we want.
The other version of stopping the submit from going through involves us adding some logic
to the method handleSubmit() :
if (!this.state.isValid) {
console.log('form is NOT valid');
} else {
console.log('valid form')
}
}
59
Formik - part II
This is the continuation of our first part on Formik, the amazing Forms library for React
Schema Validation with Yup, there is an alternate way to validate your input elements
and that is by declaring a schema in Yup and simply assign that to an attribute on the
Formik component
Async validation
Built in components, make everything less verbose using some of Formiks built-in
components
Built in components
So far we have been using regular HTML elements like form and input to build our form
and we have connected to events like onSubmit , onChange and onBlur . But we can
actually be typing a lot less. Say hello to the following components:
60
Formik - part II
Let's first look at a simple form and then rewrite it using the above-mentioned components:
Ok, above we see what a minimal implementation looks like the classical way of doing it,
that is using HTML elements like form and input .
61
Formik - part II
Not super impressed? Let's list what we don't need to type anymore:
Not enough? Well imagine you have 10 fields, that is at least 10 lines of code that
disappears and it generally it just looks cleaner. Now to our next improvement, we can
replace our validation() function with a schema , up next.
define a schema using the library Yup. So what does a schema look like :
62
Formik - part II
The above schema defines three different fields firstName , lastName and email and
gives them each attributes that they should adhere to:
As you can see the above is quite readable and by defining your data like this you save
yourself from having to type a lot of if constructs checking if all attributes are fulfilled.
<Formik validationSchema={schema}>
That's it, that is all you need to define your form data in a really expressive way, doesn't that
give you a warm and fuzzy feeling? :)
Async validation
63
Formik - part II
Ok, now to our last topic, asynchronous validation. So what's the scenario? Well sometimes
you have data that you can't really tell on client side only wether the entered value is correct
or not. Imagine you have a form where you want to find out wether a company or certain
web page domain is already taken? At that point you most likely will need to make a call to
an endpoint and the endpoint will not be coming back with the answer instantly.
Ok, we've set the scene, how do we solve this in Formik? Well, the validation property is
able to accept a Promise as well. Really, you think? That easy? Well the solution is in my
mind a bit unorthodox, let me show you what I mean:
<Formik
validate={values => {
console.log('validating async');
let errors = {};
return new Promise((resolve, reject) => {
setTimeout(() => {
errors.companyName = 'not cool';
resolve('done');
},3000);
}).then(() => {
if(Object.keys(errors).length) {
throw errors;
}
});
}}
>
// define the rest here
</Formik>
Looking at our validate implementation we see that we create a Promise that internally
runs a setTimout to simulate it going to an endpoint that it takes time to get an answer
from. At this point we set a errors.companyName to an error text:
setTimeout(() => {
errors.companyName = 'not cool';
resolve('done');
},3000);
In more real scenario we would probably call a function and depending on the functions
answer we would possibly assign errors.companyName . I'll show you below what I mean:
64
Formik - part II
isCompanyNameUnique(values.companyName).then(isUnique => {
if(!isUnique) {
errors.companyName = `companyName is not unique, please select another one`
}
resolve('done')
})
Next thing that happens in our code is that we invoke then() , that happens when we call
resolve() . Something really interesting happens in there, we check the errors for any
properties that might have been set and if so we throw an error with our errors object as
argument, like so:
.then(() => {
if(Object.keys(errors).length) {
throw errors;
}
});
I don't know about you, but to me, this looks a bit weird. I would have thought providing
validation with a Promise would have meant that a reject() of the Promise would have
// this to me would have been more intuitive, but observe, this is NOT how it works, s
o DONT copy this text but refer to the above code instead
65
Formik - part II
This is probably preferred if you got async validation on a field. As for the other fields you
can validate client side it's probably a good idea to define those in on the Formik
components validationSchema and use Yup schemas for that like we've described above.
Words of caution
If we do have async validation in there make sure your validations don't run too often
especially if the validation takes time. You don't want a 3 sec validation to trigger every time
a key is typed, at most you want it when the user leaves the field to start typing in another
field, we refer to this as the blur event. So make sure you set up your Formik component
like this:
<Formik
validateOnBlur={true}
validateOnChange={false} >
This does what you want, setting validateOnBlur to true is what you want, even though
techinally this is true by default. You want to be explicit with the next one though
validateOnChange . You want this to be off, or set to false .
Summary
We've set out to cover built-in components like Form , Field and ErrorMessage , the end
result was us cleaning up a lot of code.
Furthermore we showed how we could get rid of our validation function by defining a
schema using the Yup library.
Finally we covered asynchronous validation and we discussed things to consider like when
to validate and that it is probably best to have a field level validation for those few
asynchronous fields that we have in a form and to use schema validation for the remaining
fields.
That's it, that was the end of our article. I hope this part and the previous one have given you
new hope that dealing with Forms in React doesn't have to that painful
66
Formik - part II
67
AJAX /HTTP
68
styled-components
Styled components
There are a ton of ways to style components in React. This is probably my favourite way of
doing it and it differs somewhat conceptually from other ways of styling.
<div className="card">
<div className="card-header">
<h3>{card.header}</h3>
</div>
<div className="content">{card.content}</div>
</card>
There is really nothing wrong with the above, it is a viable approach even though some of
you might think that's a lot of repeated typing of the word className .
You can argue at this point that I can create component for the card, the card header and
the card component respectively. Yes we can do that. Then it might look like this instead:
<card header={card.header}>
{card.content}
</card>
However these components are likely to be quite simple and only just render their child
element. So what you need to ask yourself is do I really need a component for that, when all
I want to do is add some CSS styling and name my HTML element what I please? If this is
where your thoughts are heading then maybe styled-components library might be for you?
69
styled-components
At this point we don't have anything that works bit it shows you the syntax.
As we can see above we call styled.nameOfElement to define a style for our element. Let's
add some style to it:
What we can see from the above example is that we are able to use normal CSS properties
in combination with pseudo selectors like :disabled and :hover .
If we want to use our Button as part of our JSX we can simply do so, like so:
70
styled-components
<div>
<Button>
press me
</Button>
</div>
We can intermix our Button with all the HTML or JSX that we want and we can treat it just
like the HTML element button , because it is one, just with some added CSS styling.
Using attributes
The styled-component library can apply styles conditionally by looking for the occurrence of
a specified attribute on our element. We can use existing attributes as well as custom ones
that we make up.
Below we have an example of defining a primary button. What do we mean with primary
versus a normal button. Well in an application we have all sorts of buttons, normally we have
a default button but we have also a primary button, this is the most important button on that
page and usually carries out things like saving a form.
It's a quite common scenario to style a primary button in a different way so we see a
difference between such a button and a normal button so the user understands the gravity of
pushing it.
Let's now show how we design such a button and showcase the usage of attributes with
styled-components . We our styled Button the attribute primary , like so:
Our next step is updating our Button definition and write some conditional logic for if this
attribute primary is present.
`}
We use the ${} to signal that we are running some conditional logic and we refer to
something called props . props is simple a dictionary containing all the attributes on our
element. As you can see above we are saying props.primary is to be truthy, it is defined in
71
styled-components
our attributes dictionary. If that is the case then we will apply CSS styling. We can tell the
latter from the above code through our use of css function.
Below we use the above construct add some styling that we should only apply if the
primary attribute is present:
Now we have a more full example of how to test for the existence of a particular attribute.
Just one note, we said we needed to use the css function. This is a function that we find in
the styled-components namespace and we can therefore use it by updating our import
statement to look like this:
Adapting
We've shown how we can look at if certain attributes exist but we can also set different
values on a property depending on wether an attribute exist.
Let's have a look at the below code where we change the border-radius depending wether
a circle attribute is set:
72
styled-components
We can trigger the above code to be rendered by declaring our Button like so:
73
styled-components
// Text.js
import React from 'react';
import PropTypes from 'prop-types';
Text.propTypes = {
text: PropTypes.string,
className: PropTypes.any,
};
Now to style this one we need to use the styled function in a little different way. Instead of
typing "styled``" we need to call it like a function with the component as a parameter like so:
In the component we need to take the className as a parameter in the props and assign
that to our top level div, like so:
// Text.js
import React from 'react';
import PropTypes from 'prop-types';
Text.propTypes = {
text: PropTypes.string,
className: PropTypes.any,
};
As you can see above calling the styled() function means that it under the hood produces
a className that it injects into our component that we need to set to our top level element,
for it to take effect.
74
styled-components
Inheritance
We can easily take an existing style and add to it by using the method extend, like so:
The end result of the above operation is an anchor,a tag with all the styling of a Button.
this:
Above we have chained styled() and attrs() and we end with a double ` tick. Another
example is:
75
styled-components
Theming
Styled components exports a ThemeProvider that allows us to easily theme our styled
components. To make it work we need to do the following:
Set up
In the component where we intend to use a Theme we need to import and declare a
ThemeProvider . Now this can be either the root element of the entire app or the component
you are in. ThemeProvider will inject a theme property inside of either all components or
from the component you add it to and all its children. Let's look at how to add it globally:
ReactDOM.render(
<ThemeProvider theme={{ color: 'white', bgcolor: 'red' }}>
<App />
</ThemeProvider>,
document.getElementById('root'),
);
76
styled-components
Now we are ready to change our components accordingly to start using the theme we set
out:
Let's take the Button component that we defined and have it use our theme, like so:
As you can see we are able to access the themes property by writing props.theme.
<nameOfThemeProperty> .
77
styled-components
Summary
We have introduced a new way of styling our components by using the styled-components
library.
We've also learned that we get a more semantic looking DOM declaration of our
components when we compare it to the classical way of styling using className and
assigning said property CSS classes.
Further reading
The official documentation provides some excellent example of how to further build out your
knowledge styled-components official documentation
Hopefully this has convinced you that this is a good way of styling your React components.
Since I found this library this is all I ever use, but that's me, you do you :)
My twitter
78
inline styling
Inline styling
React has a different take on styling. You can definitly set a style tag with content. However,
what you set to it needs to be an object with properties in it and the properties needs to have
pascal casing. Let's show how this can look:
const styles = {
color: 'red',
backgroundImage: `url(${url})`
};
Note the use of backgroundImage we have to write it like that rather than background-image .
// App.css
.container {
font-size: 16px;
border: solid 1px black;
}
79
inline styling
import './App.css';
class TestComponent {
render() {
return (
<div className={container}></div>
);
}
}
Note in the above code that we are using the attribute className to set a CSS class. By
using the import above our component can now freely use the CSS specified in App.css .
80
sass/less
81
Adding images
Adding images
So how do we deal with images that we have locally. It's really very easy, we just need to
import and assign them to the source property of our image, like so:
So why does this work, well here is an explanation from the official docs:
...You can import a file right in a JavaScript module. This tells Webpack to include that
file in the bundle. Unlike CSS imports, importing a file gives you a string value. This
value is the final path you can reference in your code, e.g. as the src attribute of an
image or the href of a link to a PDF.
To reduce the number of requests to the server, importing images that are less than
10,000 bytes returns a data URI instead of a path. This applies to the following file
extensions: bmp, gif, jpg, jpeg, and png...
82
Core concepts
Routing
Type of routers
BrowserRouter
HashRouter
Router
// index.js
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root'))
83
Core concepts
// Main.js
Worth noting above is our usage of the attribute exact , without it our router wouldn't be
able to tell the difference between /products and /products/:id . Removing it will lead to a
product list being loaded even when we type /products/111 . Why is that? The way the
router is constructed it will match the first pattern it sees. Because we are a bit relaxed on
the rules, that is a router containing /products . The following Route :
Will match /products and /products/114 . To fix this we re-add the exact attribute and
suddenly it will only match /products . This means to match /products/111 it needs to
keep looking in our route definition tree and it will find the following to match it:
84
Core concepts
We have already defined what the Main component looks like above in Main.js. What about
Head component? Well this is typically where we define a menu the user can interact with.
This is where we introduce the Link component. This will help us create links that our
router knows how to respond to. Ultimately it will generate anchor, a -tags.
85
Core concepts
// Head.js
86
Dealing with router and query params
/products/111
/users/1/products
Router params
In the first case we query against a resource /products and is looking for a particular item
111 .
In the second case we are looking at the resource users and the specific user with id
having value 1 .
So router parameters are part of your url. Usually what we do is having a user navigate to a
page and if necessary we dig out the router parameter, cause it needs to be part of our
query. Imagine that the link /products/111 is clicked. This will mean we will take the user to
a ProductDetail component where we will need to:
87
Dealing with router and query params
async componentDidMount() {
const product = await api.getProduct(`/products/${this.props.match.params.id}`);
this.setState({
product,
});
}
render() {
return (
<React.Fragment>
{this.state.product &&
<div>{this.state.product.name}</div>
}
</React.Fragment>
);
}
}
The interesting part here being how we access the router parameter:
this.props.match.params.id
There is a match object that contains a params object that points to our router parameter
id .
Let's quickly remind ourself how this router was set up:
Above you can see that we define the route /products/:id , and thereby we set the wildcard
to :id , which makes it possible for us to access it in code by typing
this.props.match.params.id .
Query params
Let's talk about query parameters next. A query parameter is used to filter down a resource.
A typical example is using parameters like pageSize or page to indicate to the backend
that you only want a small slice of content and not the full list which can be millions of rows
88
Dealing with router and query params
potentially. Query parameters are found after the ? character in the url which would make
the url look like this /products?page=1&pageSize=20 . Let's have a look in code how we can
access query parameters:
async componentDidMount() {
const { location: { search } } = this.props;
const { page, pageSize } = search;
render() {
<React.Fragment>
{this.props.products.map(product => <div>{product.name}</div>)}
</React.Fragment>
}
}
As you can see above we are able to access our query parameters through a location
object that sits on a search object that represents our parameters like so:
{
page: 1
pageSize: 20
}
89
Programmatic navigation
Programmatic navigation
Usually we navigate using the Link component but sometimes we need to navigate based
on an action. Then we are talking about programmatic navigation. There are essentially two
ways of making that happen:
history.push('url')
Redirect component
Using history
To use the first one we need to inject the history inside of our component. This is
accomplished by using withRouter() function. Below is an example of a component using
the described:
render() {
return (
<React.Fragment>
<button onClick={this.navigate}>Navigate</button>
</React.Fragment>
);
}
}
90
Programmatic navigation
91
Dealing with lazy loading/ code splitting
Lazy loading
Oftentimes building an app the resulting bundle tend to be fairly large as our project grows in
size. This will affect the loading time of our app for users on connections with low bandwidth,
mobile users for example. For that reason it's a good idea to only load as much of your
application as you need.
What do we mean by that? Imagine your application consists of many many routes. Some
routes you are likely to be visited often and some not so much. If you instruct your app to
only load the routes the user really needs, at first load, then you can load in more routes as
the user asks for them. We call this lazy loading. We are essentially creating one bundle that
constitutes our initial application then many small bundles as we visit a specific route. We
will need Webpack and React to work together on accomplishing this one.
Let's start by having a look at what our routes currently look like:
We can use the import function to help us import a component when we need it and
thereby accomplishing a lazy load behaviour, like so:
92
Dealing with lazy loading/ code splitting
import('./Home')
Why? This returns a Promise . Not only that, it's not a valid React child so we need to stick it
into a component. For that reason we write a component whose job it is to retrieve the result
and ensure we render out our fetched component, like so:
async componentDidMount() {
const res = await this.props.provider();
this.setState({ Component: res.default });
}
render() {
const { Component } = this.state;
return(
<React.Fragment>
{Component ? <Component /> : <div>Loading...</div>}
</React.Fragment>
)
}
}
However this doesn't look quite nice though, we are accessing the default property of our
import and /Products/Products as route could look better. It would be nicer if we could
define our import url as /Products and be able to avoid accessing the default import. We do
need to alter the component directory a bit from:
/Products
Products.js
to
93
Dealing with lazy loading/ code splitting
/Products
Products.js
index.js
In our newly created index.js we can import our component and export it, like so:
// Products/index.js
Let's head back to Main.js and change the Async component to this:
async componentDidMount() {
const { Component } = await this.props.provider();
this.setState({ Component });
}
render() {
const { Component } = this.state;
return(
<React.Fragment>
{Component ? <Component /> : <div>Loading...</div>}
</React.Fragment>
)
}
}
The big difference here is getting our component like this const { Component } = await
this.props.provider() . Now let's update how we set up the route to this:
Head back to your browser and try to navigating to the different routes. You will notice how
there are bundles being loaded in every time one of our lazy routes are hit. That's it, you
know have lazy loaded routes and your mobile users will thank you.
94
Dealing with lazy loading/ code splitting
95
DataContext API
The React Context exist so you don’t have to pass in data manually at every level.
Context is about sharing data to many components
If you ever get lost following the code samples in the article, have a look at this demo
containing all the code shown below:
https://github.com/softchris/react-context-demo
96
DataContext API
The React Context exist so you don’t have to pass in data manually at every level. Context
is about sharing data to many components. The reason we need the Context API is that it’s
cumbersome to pass data from parent to child via props if there are many components
requiring the same data. By using the Context API we no longer pass this kind of shared
data with props.
data needed in many places , data that needs to be used by many components like a
theme, user or a cart
pass props through many components , sometimes it’s better to use composition
over context when you want to pass a props value through many components
context , the context object is an object holding the current context value and can be
subscribed to
provider , This is a React component that provides the value in question, it grabs it
from the context object
consumer , This is a React component that is able to consume the value provided by
the Provider and is able to show the value
This is all a bit theoretical and may sound a little confusing so let’s dive right into an example
to clear any confusion.
97
DataContext API
Good, we have a project. Now let’s create a file called theme.js , it will hold our Context
object.
It’s quite straight-forward to create a Context object. For that we use the
React.createContext() method like so:
// theme.js
Above we call createContext() and we give it an input parameter which is simply the
default value we want the context to have. We also export the object itself so we can use it
in other places.
That’s very little code to write to use something as powerful as a Context . We haven’t seen
anything yet though, so the fun has just begun:
Declare a Provider
Ok, so we have a Context object let’s grab a reference to a Provider next. For this, we will
first create a component file Sample.js , you can really call it anything you want but the point
is to have a React component to demonstrate how the Context object works. Let’s create a
component:
98
DataContext API
// Sample.js
<Theme.Provider value='dark'>
// declare consumer
</Theme.Provider>
);
Above we are declaring a normal functional React component and we also import our
Theme , our Context object. We then grab a reference to our provider by calling
Theme.Provider . At this point, nothing really works, because we are lacking a Consumer
component that can actually consume the value and thereby show it to a user.
Wait wait, hold on… Didn’t we just set the value to light in our theme.js file, what’s the point
of doing that if we are going to override it in the Provider anyway? Very good question, let’s
save it a bit until we declared a Consumer and then it will all make sense.
Declare a Consumer
So next up is about declaring a Consumer component and show how we can show the value
to the user. Let’s add that to our code:
99
DataContext API
// Sample.js
Above we added our Consumer , in the form of Theme.Consumer component and we can see
that we inside it define a function whose parameter is our theme value. We are then able to
show the theme value in a div.
Ok then, let’s get back to our question, why are we setting the value property in our
Theme.Provider component if we already set a default value in our theme.js file, here:
// theme.js
Well, the default value above won’t be used if we declare a Provider . If we are missing a
Provider component, however, it will use the default value as a fallback. So the following
code will output dark as value, which is the value we give to the Provider :
whereas this code will output light as value, e.g the default value:
100
DataContext API
Usage
Taking our Context for a spin means we need to create a Provider and a Consumer as we
did in the last section, however, most likely the Consumer part is baked into a Component
like so:
// ThemedButton.js
import Theme from 'theme.js';
<Theme.Consumer>
</Theme.Consumer>
);
export default ThemedButton
This means that our code from the last section can be cleaned up somewhat to look like this:
// Sample.js
<Theme.Provider value='dark'>
<ThemedButton />
</Theme.Provider>
);
101
DataContext API
As you can see the value from the Provider is being passed down through the props and
we can inside of the ThemedButton component access the theme property through the
Consumer .
Dynamic Context
What if we want to change the provider value? One way of doing that is by having a dynamic
context. We can achieve that by placing our Provider inside of a component and let its
value depend on the component state like so:
// AnyComponent.js
import React from 'react';
render() {
return (
<ThemeContext.Provider value={ this.state.theme }>
<ThemedButton />
</ThemeContext.Provider>
);
}
}
Now it’s easy for us to change the state and thereby we can change the value the Provider
is providing to any Consumer .
102
DataContext API
// AnyComponent.js
this.setState({
theme: evt.target.value
});
};
render() {
return (
<React.Fragment>
<h2>Any component</h2>
<select value = {this.state.theme}
onChange ={this.handleSelect}>
{ this.state.themes.map(t =>
<option value = {t} >{t}</option>)
}
</select>
<div>
Selected theme: {this.state.theme}
</div>
<Theme.Provider value ={this.state.theme}>
<ThemedButton theme={this.state.theme} />
</Theme.Provider>
</React.Fragment>
);
}
}
We can see from the above code that when the onChange event is triggered we invoke the
handleSelect() method and that leads to the state property theme being updated. That
same property theme is what the Theme.Provider is assigning as its value attribute.
Thereby a change in the droplist leads to the Provider component providing a new value. A
fairly simple code flow but it does show where we should change things to get the Consumer
component to display a new value.
103
DataContext API
// cart.js
});
We start with creating our Context object and this time we give it a more complex data type
than a string or a number. The input parameter to createContext() method is an object {}
with a property cart .
CartPage, this will contain our Consumer component and thereby display the value
from our Context object
CartProvider, this will be a component that will not only provide the value from the
Context object but also expose a method with which we can change the provided
value
104
DataContext API
// CartPage.js
const products = [{
id: 1,
title: 'Fortnite'
}, {
id: 2,
title: 'Doom'
}, {
id: 3,
title: 'Quake'
}]
We see above that we use CartContext component and that we define and display our cart
value, but there is an addition to it in the form of the addItem() method. This method will
allow us to change the cart item, but how you ask? Let’s have a look at our CartProvider
component next to find out the answer:
105
DataContext API
this.state = {
cart: [],
addItem: (item) => {
this.setState({
cart: [...this.state.cart, { ...item }]
})
}
}
}
render() {
return (
<CartContext.Provider value = {this.state} >
<CartPage />
</CartContext.Provider>
);
}
We can see here that the state object consists of the properties cart and addItem and
what gets passed into the value property of the CartContext.Provider is this.state e.g both
cart and addItem() . This means we could easily expand this with a removeItem()
function or whatever we need, this is how we get more than just a value exposed to a
Consumer component.
106
DataContext API
// withCart.js
import CartContext from './cart';
import React from 'react';
As you can see above, we are using a Consumer to make this happen but we also use the
spread parameter { ...context} to transfer what is in the context object to the underlying
component. Now we can easily use this function to decorate our component, like so:
// Header.js
import React from 'react';
import withCart from './withCart';
return (
{cart.length === ?
<div>Empty cart</div> :
<div>Items in cart: ({cart.length})</div>
}
);
}
Summary
In this article, we have covered quite a lot. We have explained what the Context API is and
when to use it. We also talked about its building blocks Provider and Consumer .
Furthermore, we have covered how to update provided values and lastly how we can clean
up a bit using a HOC, a higher order component. Hopefully, you have found this useful. :)
107
DataContext API
108
Decorators
109
Render props
110
Hooks
Hooks is the latest pattern and an experimental feature that's supposedly better than sliced
bread. Everyone used to go nuts over Render props but now it's all hooks.
Hooks are currently in React v16.8.0-alpha.0 so you can try them out already :)
Below I will try to lay out all the different pain points that makes us see Hooks as this new
great thing. A word of caution though, even Hooks will have drawbacks, so use it where it
makes sense. But now back to some bashing and raving how the way we used to build
111
Hooks
There are many problems Hooks are trying to address and solve. Here is a list of offenders:
wrapper hell, we all know the so called wrapper hell. Components are surrounded by
layers of providers , consumers , higher-order components , render props , and other
abstractions, exhausted yet? ;)
Like the whole wrapping itself wasn't bad enough we need to restructure our components
which is tedious, but most of all we loose track over how the data flows.
increasing complexity, something that starts out small becomes large and complex
over time, especially as we add lifecycle methods
life cycle methods does too many things, components might perform some data
fetching in componentDidMount and componentDidUpdate . Same componentDidMount
method might also contain some unrelated logic that sets up event listeners, with
cleanup performed in componentWillUnmount
difficult to test, stateful logic is all over the place, thus making it difficult to test
classes confuse both people and machines, you have to understand how this
works in JavaScript, you have to bind them to event handlers etc. The distinction
between function and class components in React and when to use each one leads to
disagreements and well all know how we can be when we fight for our opinion, spaces
vs tabs anyone :)?.
minify issues, classes present issues for today’s tools, too. For example, classes don’t
minify very well, and they make hot reloading flaky and unreliable. Some of you might
love classes and some of you might think that functions is the only way. Regardless of
which we can only use certain features in React with classes and if it causes these
minify issues we must find a better way.
112
Hooks
What is a hook?
Hooks let you split one component into smaller functions based on what pieces are
related (such as setting up a subscription or fetching data), rather than forcing a split based
on lifecycle methods.
Let's have an overview of the different Hooks available to use. Hooks are divided into Basic
Hooks and Additional Hooks . Let's list the Basic Hooks first and mention briefly what their
role is:
Basic Hooks
useState, this is a hook that allows you to use state inside of function component
useEffect, this is a hook that allows you to perform side effect in such a way that it
replaces several life cycle methods
useContext, accepts a context object (the value returned from React.createContext)
and returns the current context value, as given by the nearest context provider for the
given context. When the provider updates, this Hook will trigger a rerender with the
latest context value.
Additional Hooks
We will not be covering Additional Hooks at all as this article would be way too long but you
are encouraged to read more about them on Additional Hooks
useReducer, alternative to useState , it accepts a reducer and returns a pair with the
current state and a dispatch function
useCallback, will return a memoized version of the callback that only changes if one of
the inputs has changed. This is useful when passing callbacks to optimized child
components that rely on reference equality to prevent unnecessary renders
useMemo, passes a create function and an array of inputs. useMemo will only
recompute the memoized value when one of the inputs has changed. This optimization
helps to avoid expensive calculations on every render.
useRef, returns a mutable ref object whose .current property is initialized to the
passed argument (initialValue). The returned object will persist for the full lifetime of the
component
useImperativeHandle, customizes the instance value that is exposed to parent
components when using ref
useLayoutEffect, the signature is identical to useEffect, but it fires synchronously after
113
Hooks
all DOM mutations. Use this to read layout from the DOM and synchronously re-render
useDebugValue, can be used to display a label for custom hooks in React DevTools
As you can see above I've pretty much borrowed the explanation for each of these
Additional Hooks from the documentation. The aim was merely to describe what exist, give
a one liner on each of them and urge you to explore the documentation once you feel you've
mastered the Basic Hooks .
next we need to upgrade react and react-dom so they are using the experimental version
of React where hooks are included:
Ok we see that we use the Hook useState by invoking it and we invoke it like so:
useState(0)
114
Hooks
This means we give it an initial value of 0. What happens next is that we get an array back
that we do a destructuring on. Let's examine that closer:
Ok, we name the first value in the array counter and the second value setCounter . The
first value is the actual value that we can showcase in our render method. The second
value setCounter() is a function that we can invoke and thereby change the value of
counter . So in a sense, setCounter(3) is equivalent to writing:
this.setState({ counter: 3 })
Just to ensure we understand how to use it fully let's create a few more states:
Above we are creating the states products and cart and in doing so we also get their
respective change funciton setProducts and setCart . We can see in the markup we
invoke the method addToCart() if clicking on any of the items in our products list. This
leads to the invocation of setCart , which leads to the selected product ot be added as a
cart item in cart . This is a simple example but it really showcases the usage of setState
hook.
115
Hooks
The Effect hook is meant to be used to perform side effects like for example HTTP calls. It
performs the same task as life cycle methods componentDidMount , componentDidUpdate , and
componentWillUnmount .
116
Hooks
const api = {
getProducts: () => {
return Promise.resolve(products);
},
getProduct: (id) => {
return Promise.resolve(products.find(p => p.id === id));
}
}
useEffect(() => {
console.log('use effect');
fetchData();
fetchProduct(selected);
}, [selected]);
return (
<React.Fragment>
<h1>Async shop</h1>
<h2>Products</h2>
{products.map(p => <div>{p.name}</div>)}
<h3>Selected product</h3>
{product}
<button onClick={() => setSelected(1)}>Change selected</button>
</React.Fragment>
);
Ok, a lot of interesting things was happening here. Let's start by looking at our usage of
useEffect :
117
Hooks
useEffect(() => {
console.log('use effect');
fetchData();
fetchProduct(selected);
}, [selected]);
What we are seeing above is us calling fetchData and fetchProduct . Both these methods
calls methods marked by async . Why can't we just make the calling function in useEffect
async, well that's a limitation of hooks unfortunately.
Looking at the definition of these two methods it looks like the following:
We see above that we are calling getProducts and getProduct on api which both returns
a Promise. After having received the resolved Promise, using await we call setProducts
and setProduct that are functions we get from our useState hook. Ok, so this explains
how useEffect in this case acts like componentDidMount but there is one more detail. Let's
look at our useEffect function again:
useEffect(() => {
console.log('use effect');
fetchData();
fetchProduct(selected);
}, [selected]);
The interesting part above is the second argument [selected] . This is us looking at the
selected variable and let ourselves be notified of changes, if a change happens to
Now, try hitting the bottom button and you will see setSelected being invoked which trigger
useEffect because we are watching it.
Life cycle
118
Hooks
Hooks replaces the needs for many life cycle methods in general so it's important for us to
understand which ones.
Let's discuss Effect Hooks in particular and their life cycle though.
Let's show how we would use life cycle methods to update the DOM:
componentDidMount() {
document.title = 'Component started';
}
componentDidUpdate() {
document.title = 'Component updated'
}
Accessing the DOM tree with an Effects Hook would look like the following:
useEffect(() => {
document.title = `App name ${title} times`;
})
}
As you can see above we have access to props as well as state and the DOM.
Let's remind ourselves what we know about our Effect Hook namely this:
Our effect is being run after React has flushed changes to the DOM — including the
first render
That means that two life cycle methods can be replaced by one effect.
119
Hooks
useEffect(() => {
// set up
// perform side effect
return () => {
// perform clean up here
}
});
Above we see that inside of our useEffect() function we perform our side effect as usual,
but we can also set things up. We also see that we return a function. Said function will be
invoked the last thing that happens.
What we have here is set up and tear down. So how can we use this to our advantage?
Let's look at a bit of a contrived example so we get the idea:
useEffect(() => {
const id = setInterval(() => console.log('logging'));
return () => {
clearInterval(id);
}
})
The above demonstrates the whole set up and tear down scenario but as I said it is a bit
contrived. You are more likely to do something else like setting up a socket connection for
example, e.g some kind of subscription, like the below:
useEffect(() => {
chatRoom.subscribe('roomId', onMessage)
return () => {
chatRoom.unsubscribe('roomId');
}
})
120
Hooks
Yes you can. With useState and useEffect the world is completely open. You can create
whatever hook you need.
Ask yourself the following questions; Will my component have a state? Will I need to do a
DOM manipulation or maybe an AJAX call? Most of all, is it something usable that more than
one component can benefit from? If there are several yes here you can use a hook to create
it.
Let's look at some interesting candidates and see how we can use hooks to build them out:
a modal, this has a state that says wether it shows or not and we will need to
manipulate the DOM to add the modal itself and it will also need to clean up after itself
when the modal closes
a feature flag, feature flag will have a state where it says wether something should be
shown or not, it will need to get its state initially from somewhere like localStorage
and/or over HTTP
a cart, a cart in an e-commerce app is something that most likely follows us everywhere
in our app. We can sync a cart to localStorage as well as a backend endpoint.
Feature flag
Let's try to sketch up our hook and how it should be behaving:
function useFeatureFlag(flag) {
let flags = localStorage.getItem("flags");
flags = flags ? JSON.parse(flags) : null;
return [enabled];
}
Above we have created a hook called useFeatureFlag . This reads its value from
localStorage and it uses useState to set up our hook state. The reason for us not
destructuring out a set method in the hook is that we don't want to change this value unless
we reread the whole page, at which point we will read from localStorage anew.
Now we have create our custom Hook, let's take it for a spin:
121
Hooks
</React.Fragment>
);
};
// using it
<TestComponent flag="experiment1">
Imagine you have an admin page. On that admin page it would be neat if we could list all the
flags and toggle them any way we want to. Let's write such a component:
122
Hooks
What we are doing above is to read out the flags from localStorage and then we render
them all out. While rendering them out, flag by flag, we also hook-up ( I know we are talking
about hooks here but no pun intended, really :) ) a method on the onClick . That method is
toggleFlag that let's us change a specific flag. Inside of toggleFlag we not only set the
new flag value but we also ensure our flags have the latest updated value.
It should also be said that us creating useFlags hook have made the code in FlagsPage
quite simple, so hooks are good at cleaning up a bit too.
Summary
123
Hooks
In this article we have tried to explain the background and the reason hooks where created
and what problems it was looking to address and hopefully fix.
we can use one or both of the mentioned hook types and create really cool and reusable
functionality, so go out there, be awesome and create your own hooks.
Further reading
Hooks documentation
Motivation behind hooks
124
Jest
Jest
In this article we will cover the testing framework Jest. We will learn how to:
write tests, it's a breeze to write tests and assert on specific conditions
manage our test suite, by running specific tests as well as specific test files by utilising
the pattern matching functionality
debug our tests, by augmenting VS Code we can gain the ability to set break points in
our tests and create a real nice debugging experience
snapshot mastery, learn how using snapshots can give you increased confidence that
your components are still working after a change you made
leverage mocking, mocking dependencies can ensure you only test what you want to
test and Jest has great defaults when it comes to mocking
coverage reports, we have come to expect a good coverage tool to be included in all
good testing libraries. Jest is no different and it's really easy to run coverage reports and
quickly find what parts of your code that could benefit from some more testing
125
Jest
What makes is delightful? It boosts that it has a zero-configuration experience. Ok, we are
getting closer to the answer.
Get started
Let's try to set it up and see how much configuration is needed. If you just want to try it,
there is a Jest REPL where you will be able to write tests among other things.
Ok, so now we know how Jest will find our files, how about writing a test?
// add.js
function add(a, b) {
return a + b;
}
module.exports = add;
// __tests__/add.js
We see above that we are using describe to create a test suite and it to create a test
within the test suite. We also see that we use expect to assert on the result. The expect
gives us access to a lot of matchers, a matcher is the function we call after the expect :
expect(something).matcher(value)
126
Jest
As you can see in our test example we are using a matcher called toBe() which essentially
matches what's inside the expect to what's inside the matcher, example:
expect(1).toBe(1) // succeeds
expect(2).toBe(1) // fails
There are a ton of matchers so I urge you to have a look at the ones that exists and try to
use the appropriate matcher Matchers
yarn test
127
Jest
// src/App.test.js
As we can see it has created a test using it and have also created a component using
ReactDOM.render(<App />, div) , followed by cleaning up after itself by calling
ReactDOM.unmount(div) . We haven't really done any assertions at this point, we have just
tried to create a component with no errors as the result, which is good to know though.
How bout we try adding add.js file and its corresponding test?
// add.js
function add(a,b) {
return a + b;
}
// __tests__/add.js
128
Jest
Debugging
Any decent test runner/framework should give us the ability to debug our tests. It should give
us the ability to:
// subtract.js
function subtract(a,b) {
return a - b;
}
129
Jest
// __tests__/subtract.js
import subtract from '../subtract';
Let's have a look at our terminal again and especially at the bottom of it:
If you don't see this press w as indicated on the screen. The above give us a range of
commands which will make our debugging easier:
p this will allow us to specify a pattern, typically we want to specify the name of a file
Given the above description we will try to filter it down to only test the add.js file so we type
p :
130
Jest
This takes us to a pattern dialog where we can type in the file name. Which we do:
Above we can now see that only the add.js file will be targeted.
// __tests__/add.js
131
Jest
So we have two passing tests in the same file but we only want to run a specific test. We do
that by adding the .only call to the test, like so:
132
Jest
We can see that adding .only works really fine if we only want to run that test. We can use
.skip to make the test runner skip a specific test:
133
Jest
134
Jest
Install this extension and head back to your code. Now we have some added capabilities. All
of our tests should have a Debug link over every single test. At this point we can add a
breakpoint and then press our Debug link. Your break point should now be hit and it should
look like so:
Snapshot testing
Snapshot is about creating a snapshot, a view of what the DOM looks like when you render
your component. It's used to ensure that when you or someone else does a change to the
component the snapshot is there to tell you, you did a change, does the change look ok?
If you agree with the change you made you can easily update the snapshot with what DOM
it now renders. So snapshot is your friend to safeguard you from unintentional changes.
Let's see how we can create a a snapshot. First off we might need to install a dependency:
Next step is to write a component and a test to go along with it. It should look something like
this:
// Our component
import React from 'react';
135
Jest
// The test
This is followed by using the renderer to create an instance of our component. Next step is
turn that component into a JSON representation like so component.toJSON() and lastly we
assert on this by calling expect(tree).toMatchSnapshot() , this will call a snapshot that will
place itself in a __snapshots__ directory under your tests directory.
136
Jest
We see that our todo is an object instead of a string so it has a title and description
property. This WILL make our snapshot react and it will say the following:
It clearly indicates something is different and asks us to inspect the code. If we are happy
with the changes we should press u to update the snapshot to its new version. So look at
the code and yes this is an intended change so therefore we press u . We end up with the
following image telling us everything is ok:
Mocking
Mocking is one of those things that needs to work well. Mocking in Jest is quite easy. You
need to create your mocks in a directory that is adjacent to your module, or more like a child
directory to the module. Let's show what I mean in code. Imagine you have the following
module:
137
Jest
// repository.js
const data = [{
title: 'data from database'
}];
// __tests__/repository.js
Not the best of tests but it is "a test". Let's create our mock so that our file structure look like
so:
repository.js
__mocks__/repository.js
// __mocks__/repository.js
const data = [{
title: 'mocked data'
}];
To use this mock we need to call jest.mock() inside of our test, like so:
138
Jest
// __tests__/repository.js
Now it uses our mock instead of the actual module. Ok you say, why would I want to mock
the very thing I want to test. Short answer is - you wouldn't. So therefore we are going to
create another file consumer.js that use our repository.js . So let's look at the code for
that and its corresponding test:
// consumer.js
Above we clearly see how our consumer use our repository.js and now we want to mock
it so we can focus on testing the consumer module. Let's have a look at the test:
We use jest.mock and mocks away the only external dependency this module had.
What about libs like lodash or jquery , things that are not modules that we created but is
dependant on? We can create mocks for those at the highest level by creating a __mocks__
directory.
There is a lot more that can be said about mocking, for more details check out the
documentation Mocking docs
139
Jest
Coverage
We have come to the final section in this chapter. This is about realizing how much of our
code is covered by tests. To check this we just run:
This will give us a table inside of the terminal that will tell us about the coverage in
percentage per file. It will also produce a coverage directory that we can navigate into and
find a HTML report of our coverage. But first off let's change the add.js file to add a piece
of logic that needs a test, like so:
function add(a,b) {
if(a > 0 && b > 0 ) {
return a + b;
}
throw new Error('parameters must be larger than zero');
}
Now we can see we have more than one path through the application. If our input params
are larger than zero then we have existing tests that cover it. However if one or more
parameters is below zero then we enter a new execution path and that one is NOT covered
by tests. Let's see what that looks like in the coverage report by navigating to
coverage/lcov-report . We can show this by typing for example http-server -p 5000 and
140
Jest
Now we can clearly see how our added code is indicated in red and that we need to add a
test to cover that new execution path.
describe('add', () => {
it('testing addition', () => {
const actual = add(1,2);
expect(actual).toBe(3);
})
Our second case should now cover for the execution path that leads to an exception being
thrown. Let's rerun our coverage report:
141
Jest
Summary
We've looked at how to write tests. We've also looked at how to debug our tests using an
extension from VS Code which have allowed us to set breakpoints.
Furthermore we've learned what snapshots are and how to best use them to our advantage.
Next up we've been looking at leveraging mocking to ensure we are in complete isolation
when we test.
Lastly we've looked at how we can generate coverage reports and how that can help you to
track down parts of your code that could really benefit from some more testing.
Further reading
The official docs for Jest can be found here Official docs
142
Nock
Scenario
Imagine we have the following files:
// products.js
return json.products;
}
143
Nock
// ProductsList.js
async componentDidMount() {
const products = await getProducts();
this.setState({
products
});
}
render() {
return (
<Products products={this.state.products} />
);
}
}
We can see that we use product.js module and call getProducts() in the
componentDidMount() and end up rendering the data when it arrives.
Testing it
If we wanted to test ProductsList.js module we would want to focus on mocking away
products.js cause it is a dependency. We could use the library nock for this. Let's start off
Let's now create a test __tests__/ProductsList.js and define it like the following:
144
Nock
// __tests__/ProductsList.js
As you can see from the above it attempts to perform a network request. We should never
do that when running a test. We could add a Jest mock for this that is definitely one way to
solve it, then it would look like this:
// __mocks__/products.js
export const getProducts = async () => {
const products = await Promise.resolve([{ name: 'test' }]);
return products;
}
That works but let's look at how to solve it with nock . Because we are attempting to call
fetch() in a node environment we need to ensure it is set up correctly. Suggestion is to set
145
Nock
// setupTests.js
global.fetch = require('node-fetch');
Note above how we invoke the nock() method by first giving it the baseUrl
http://myapi.com followed by the path /products and the HTTP verb get and how we
define what the response should look like with reply() . We also give the reply() method
a second argument to ensure CORS plays nicely. At this point our test works.:
146
Nock
Query parameters
What if we have a url that looks like this:
http://myapi.com/products?page=1&pageSize=10;
How we do we set up our nock to match it? Well, we can use the helper method query for
that, like so:
nock('http://myapi.com')
.get('/products')
.query({ page: 1, pageSize: 10 })
scope.done();
So what happens when we set up a mock and it's not being it? Well let's add another call to
our test, like so:
147
Nock
// setupTests.js
Summary
We have briefly explained what nock is and how to use it for different cases. This is just
one way of many to handle HTTP calls.
There are a lot more you can do with nock , we have barely scraped the surface. Have a
look at the official documentation Nock documentation
148
Enzyme
149
react-testing-library
component rather than the internals. You can change your components as much as you
want as long as they render the data the same way or React in the same way if you fill in
data or press a button for example.
This is what the author the library Kent Dodds says about it:
Simple and complete React DOM testing utilities that encourage good testing practices
It's a lightweight solution for testing React components. It provides utility functions on top of
react-dom and react-dom/utils . Your tests work on DOM nodes over React component
instances.
writing a test, show how simple it is to write a test, instantiate a component and assert
on it
dealing with events, we will learn how we can trigger event and assert on the resulting
150
react-testing-library
component afterwards
asynchronous actions, we will learn how we can trigger and wait for asynchronous
actions to finish
manage input, we will learn how to send key strokes to input elements on our
components and assert on the result
It's easy to get started with, you just need to install react-testing-library :
Writing a test
Let's look at a real scenario and see what we mean. We will create:
Todos.js a component that allows you to render a list of Todos and it also allows you to
select a specific Todo item
Todos.test.js, our test file
151
react-testing-library
// Todos.js
render() {
return (
<Todos { ...this.props } select={this.select} selected={this.state.todo} />
);
}
}
152
react-testing-library
// Todos.test.js
const todos = [{
title: 'todo1'
},
{
title: 'todo2'
}];
describe('Todos', () => {
it('finds title', () => {
const {getByText, getByTestId, container} = render(<Todos todos={todos} />);
})
});
We can see from the above code that we are using some helpers from react-testing-
library :
Simulate , this will help us trigger things like a click event or change the input data
for example
wait , this allows us to wait for an element to appear
Looking at the test itself we see that when we call render we get an object back that we do
destructuring on:
153
react-testing-library
// Todos.test.js
const todos = [{
title: 'todo1'
},
{
title: 'todo2'
}];
describe('Todos', () => {
it('finds title', () => {
const {getByText, getByTestId, container} = render(<Todos todos={todos} />);
const elem = getByTestId('item');
expect(elem.innerHTML).toBe('todo1');
})
});
As we can see above we are able to render our element and is also able query for an h3
element by using the container and the querySelector . Finally we assert on it.
Handling actions
Let's have a look at our component again. Or rather let's look at an excerpt of it:
// excerpt of Todos.js
We see above that we try to set the CSS class selected if a todo is selected. The way to
get a Todo selected is to click on it, we can see how we invoke the select method when
we click on the button that is rendered, one per item. Let's try to test this out by adding a
154
react-testing-library
test:
const todos = [{
title: 'todo1'
},
{
title: 'todo2'
}];
describe('Todos', () => {
it('finds title', () => {
const {getByText, getByTestId, container} = render(<Todos todos={todos} />);
const elem = getByTestId('item');
expect(elem.innerHTML).toBe('todo1');
})
Our last added test is using the Simulate helper to perform a click and we can see that
we are using the getByText helper to find the button. Thereafter we again use the
container to find and assert on the selected CSS class.
Note.js , a component that allows us enter data and save down the results, it will also
155
react-testing-library
// Note.js
import React from 'react';
save = () => {
this.setState({
saved: `Saved: ${this.state.content}`,
});
}
load = () => {
var me = this;
setTimeout(() => {
me.setState({
data: [{ title: 'test' }, { title: 'test2' }]
})
}, 3000);
}
render() {
return (
<React.Fragment>
<label htmlFor="change">Change text</label>
<input id="change" placeholder="change text" onChange={this.onChange} />
<div data-testid="saved">{this.state.saved}</div>
{this.state.data &&
<div data-testid="data">
{this.state.data.map(item => (
<div className="item" >{item.title}</div>
))}
</div>
}
<div>
<button onClick={this.save}>Save</button>
<button onClick={this.load}>Load</button>
</div>
</React.Fragment>
);
156
react-testing-library
}
}
// __tests__/Note.js
describe('Note', () => {
it('save text', async() => {
const {getByText, getByTestId, getByPlaceholderText, container, getByLabelText} =
render(<Select />);
const input = getByLabelText('Change text');
console.log('saved', getByTestId('saved').innerHTML);
expect(getByTestId('saved')).toHaveTextContent('input text')
})
});
We can see above that we use the helper getByLabelText to get a reference to our input
and we simply do input.value = 'input text' at that point. Thereafter we need to invoke
Simulate.change(input) for the change to happen. After that we can assert on the results by
157
react-testing-library
load = () => {
var me = this;
setTimeout(() => {
me.setState({
data: [{ title: 'test' }, { title: 'test2' }]
})
}, 3000);
}
We can see above that the change doesn't happen straight away, this due to us using a
setTimeout . Having a look at our component we can see that we don't render out the data
{this.state.data &&
<div data-testid="data">
{this.state.data.map(item => (
<div className="item" >{item.title}</div>
))}
</div>
}
Our test, we are about to write, needs to cater to this and wait for the div with attribute
data-testid="data" to be present before it can assert on it. Let's see what that looks like, by
158
react-testing-library
describe('Note', () => {
it('save text', async() => {
const {getByText, getByTestId, getByPlaceholderText, container, getByLabelText} =
render(<Select />);
const input = getByLabelText('Change text');
console.log('saved', getByTestId('saved').innerHTML);
expect(getByTestId('saved')).toHaveTextContent('input text')
})
Simulate.click(getByText('Load'));
await wait(() => getByTestId('data'))
const elem = getByTestId('item');
expect(elem).toHaveTextContent('test');
})
});
Above we see the construct await wait(() => getByTestId('data')) that halts until the
element is present. Thereafter we assert on the result.
Summary
We had a look at the library itself and wrote a few tests. We learned how to deal with events,
asynchronous actions as well as how to manage input. We covered most things this library
has to offer but more importantly we learned how to think about testing in a different way.
Maybe we don't have to test the internals but rather the surface of our components?
Further reading
There is a lot more to this library and you are encouraged to look at the
159
react-testing-library
160
cypress
161
Redux - overview
Redux
Redux is about two things
State management
A state in our app can exist inside a component but also in a global object. Typically state
that is only needed in one component should stay there. State that is used by more than one
component is usually put in a global store. Another thing we need to think about is that at
some point the state needs to be saved down, that's usually done by using performing an
AJAX call towards an endpoint.
We have two components, one create component and one list component. When an
item is created in the create component we want this information to reach the list
component. We don't want them to talk directly but rather indirectly. We usually solve
that by using a pub-sub, publish-subscribe pattern
We have our scenario and we are trying to define what should happen in what order:
User interaction, user enters values for a new item and presses a save button
The values are captured into a message and is being passed on, dispatched
A central store processes the message that leads to a change of its internal state
The central store then communicates out to all its listeners that the internal state has
changed, one such listener is our list component
162
Redux - overview
Why is this called unidirecitonal? Well the data in this case is only flowing in one direction.
We start with a user interaction and we end up with another UI component being updated.
What about other scenarios such as fetching initial data to our list component? Well in this
case we might have the following flow:
list component asks for data from the central store by sending it a message
central store in turn sends a message that leads to the data being fetched from an
endpoint via AJAX
when the data arrives the state of the central store is changed and an event is emitted
that a listener, in this case the list component is listening to
As you can see we communicate with messages that ends up being interpreted and leads to
a change of the state of our internal store. That change is broadcasted to a listener/s and the
UI gets updated
163
Actions
Actions
An Action is a message that we need to pass on to our centralized store. It carries the
intention of what we are trying to do as well as data, also called payload. An Action is usually
represented as an object
The type is our intention and should be a verb saying what we are trying to achieve. The
payload carries our data and can be a string or an object or whatever best represents our
data.
Action creator
It is quite common to use something called an action creator. It is simply a function that
makes it easier for us to create our action object. It looks something like this:
164
Reducers
Reducer
A reducer is a function that is able to process our message, our Action. A reducer takes the
existing state and applies the message on it. The end result is a new state. A reducer
typically operates on a slice of state.
Defining a reducer
A reducer typically looks like this:
let id = 1;
const actionCreator = (title) => ({ type: 'CREATE_ITEM', payload: { id: counter++, ti
tle } })
What we can see above is how we can use the reducer and take initial state, and apply an
action to it. We also see how we can take an existing state and another action and simply
create a new state which consists of the old state + action .
Reducer Types
165
Reducers
There are different types of reducers. We have shown a list reducer so far but it is possible
to define reducers for:
lists
objects
primitives
Object reducer
We have already showcase the list reducer so let's have a look at an object reducer.
The aim of an object reducer is simply to either load the object or update parts of it, if we are
editing it for example. Let's show some code:
Primitive reducer
This is quite an easy one. It looks like this if we are dealing with an integer:
166
Store
Store
Well this all seems well and good but how does a reducer fit in with a having a global store
of data? Well reducers are used as the access point to our global state. It's a way to protect
the global state if you will. If we didn't use a reducer to manage our global state then
potentially anything could change and we would quickly loose control. Let's show how we
can let reducers handle our global state.
Imagine that we have a function dispatch that is our entry point for changing the global
state. Let's sketch it out:
Ok, so we take to inparameters action . The job of dispatch is to take an action and make
sure it finds the correct reducer and have that reducer calculate a new state. What we do we
mean by the right reducer? Well here is the thing. A reducer only operates on a slice of
state, it never manages the entire state. There is usually one reducer for our property of our
state object. Let us show this in code:
{
list: listReducer(state.list, action),
user: userReducer(state.user, action)
}
As you can see above we can connect every property of our state object with a reducer
function. Of course the above isn't valid code so what we need to do is to put this in a
function, like so:
167
Store
As you can see above we also introduce a state variable that is nothing more than the
state of our store. It looks like this:
let state = {
list: [],
user: void 0
}
let state = {
list: [],
user: void 0
}
Now the above is the very engine in Redux. This is how we can send a message, have it
processed via a reducer and finally the state changes accordingly. There are of course other
things to this like:
168
Store
As you can see above we give it a parameter fn and end up return fn(state) and
thereby we let the input parameter decide what slice of state it wants. Let's showcase how
we can call the select method:
Notify listeners
Last but not least we need a way to communicate that our state has changed. This is easily
achieved by using letting a listener subscribe, like so:
Then to notify all the listeners we need to call all these listeners when a change to our state
happens, we need to place some code in the dispatch :
169
Store
// store.js
let state = {
list: [],
user: void 0
};
170
Store
Example
So far we have described how you can build a store from scratch. How do you use it in
practice though?
When a create item component is create a new item, it needs to express this as an action
and call dispatch on the store, like so:
171
Store
// create-action.js
export const createItem = (item) => ({ type: 'CREATE_ITEM', payload: { title: item } }
)
// create-item.js
import { createItem } from './create-action';
import { dispatch } from './store';
create() {
onCreate = () => {
dispatch(createItem(this.state.content));
this.setState({
content: ''
})
}
render () {
return (
<React.Fragment>
<input onChange={onChange}>
<button onClick={onCreate}>Save</button>
</React.Fragment>
);
}
}
So much for the create-item component, what about the list component, how does it listen to
the store and how does it handle updates to the store?
172
Store
// list-component
import { select, subscribe } from './store';
componentDidMount() {
this.setState({
list: select((state) => state.list)
});
subscribe(this.update.bind(this));
}
update = () => {
console.log('store is updated');
this.setState({
list: select((state) => state.list)
});
}
render() {
return (
<React.Fragment>
<h3>My list</h3>
{this.state.list.map(item => <div>{item.title}</div>)}
</React.Fragment>
);
}
}
We answered both those questions with the code above, we initially fetch the store value we
care about in the componentDidMount like so:
this.setState({
list: select((state) => state.list)
});
we also listen to the store by calling subscribe and passing our update method like so:
subscribe(this.update.bind(this));
lastly when update is invoked we make sure to reread the state slice we care about by
calling it again, like so:
173
Store
this.setState({
list: select((state) => state.list)
});
174
Add Redux to React
Get started
We need to install a couple of dependencies for this:
create a store
expose the store with a Provider
create a container component
Creating a store
Creating a store is about creating the needed reducers, use a few helper functions and tell
Redux about it. Let's have a look at what creating the reducers might look like:
175
Add Redux to React
// store.js
import { combineReducers } from 'redux';
Above we have only defined one reducer, in the same file as store.js no less. Normally we
would define one reducer per file and have them imported into store.js . Now we have
everything defined in one file to make it easy to understand, what is going on. One thing
worth noting is our usage of the helper function combineReducers . This is the equivalent of
writing:
It's not an exact match but it is pretty much what goes on internally in combineReducers .
176
Add Redux to React
// index.js - excerpt
import { createStore } from 'redux';
import app from './store';
Next step is to wrap our root component App in a Provider and set its store property to
our newly created store, like so:
// index.js - excerpt
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
// index.js - excerpt
Full code
The full code with all the needed imports and calls looks like this:
177
Add Redux to React
// index.js
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
registerServiceWorker();
Container component
A container component is simply a component that contains the data and in this case has
knowledge of Redux. A presentational component relies fully on its inputs wether it is about
rendering data or invoking a method. Let's look at a non Redux example that shows this.
178
Add Redux to React
As you can see above the components are relying fully on input wether that input is pure
data to be rendered or functions to be invoked.
Next up let's define a container component, the component that sits on data and behavior:
render() {
<React.Fragment>
<PresentationComponent todos={this.state.todos} />
<PresentationComponentInput onChange={this.change} add={add} />
</React.Fragment>
}
}
179
Add Redux to React
Above you can see how we have a state and methods that we pass on to the components
being rendered:
<React.Fragment>
<PresentationComponent todos={this.state.todos} />
<PresentationComponentInput onChange={this.change} add={add} />
</React.Fragment>
Redux
Ok, so we understand the basic idea. Applying this to Redux is about using a method called
connect that helps us create container components. Let's have a look what that looks like
in code:
Above we can see how we invoke connect and we are able to create a ListContainer
component. There are three things here we need to explain though, namely:
mapStateToProps , this is function that returns an object of store states our component
able to call
List , a presentation component
mapStateToProps
It's job is to decide what data from the store we want to provide to a presentation
component. We only want a a slice of state, never the full application state. It for example
makes sense for a list component to have access to a list state but not a user for
example.
180
Add Redux to React
We can see above that we define a function that takes a state as parameter and ends up
returning an object. We can see that the returned object has a property items that gets its
value from state.list , this means we are reading the list property from the store and it
is being exposed as items .
mapDispatchToProps
This is a function the produces an object, when invoked. Let's have a look at its
implementation:
const addItem = (item) => ({ type: 'CREATE_ITEM', payload: { title: item } });
Above we see that we take a dispatch method in. This method when called will allow us to
dispatch actions that leads to the stores state being changed. We define a onAddItem
method that when invoked will call on addItem method. It looks at first glance like we will
add an item that is ultimately going to be added to a list in a store.
181
Add Redux to React
const addItem = (item) => ({ type: 'CREATE_ITEM', payload: { title: item } });
182
Add Redux to React
// components/List.js
List.propTypes = {
items: PropTypes.array,
};
Just focusing on the rendering part of this component we see that it renders a list of data but
also have the ability to add an item:
183
Add Redux to React
What's interesting here is we see that it takes items and onAddItem as props . Now this is
exactly what the connect method does for us when it glues together Redux container
data/behaviour with a presentation component. Remember this from our container
component:
The items property came from the object returned from mapStateToProps and onAddItem
came from mapDispatchToProps .
Make it work
What you end up rendering is container components like so:
// App.js
<ListContainer />
Our container component knows how to grab data from the store but also how to invoke
functions that adds/changes store data.
184
Add Redux to React
Summarising
Your app React/Redux is just more of the same. You will have a number of container
components and a number of presentation components and the connect() method is how
you ensure the presentation component renders data and is able to invoke a method that
leads to an action being dispatched and ultimately changes the stores state.
To see a fully working example of what's been described in this chapter please have a look
at this repo:
React Redux
185
Handling side effects with Sagas
Ok, so above we see that we are defining a mapDispatchToProps and create two properties
on the object we return back, add and get . Now what happens inside them is that we use
some way to perform an AJAX call and when done we talk to Redux and our store by calling
dispatch .
We can definitely clean up the above a little so it doesn't look so bad but the fact remains we
are mixing AJAX communication with Redux. There is a way to handle this a bit more
elegantly namely by using Sagas.
So Sagas promises to create a bit more order. The idea with Sagas is that the only thing you
should see in a container component are dispatching of actions. Sagas acts as listeners to
specific actions. When a specific action happens the Saga will have the ability to intervene,
186
Handling side effects with Sagas
you can do its AJAX interaction and then end it all with dispatching an action.
Next step is about telling Redux we want to use Sagas. Sagas are a so called middleware,
something that intercepts the normal Redux flow which means we need to do the following
to make Sagas work:
Next step is creating the middleware and registering it with Redux, like so:
As you can see we now give createStore a second parameter instead of just the root
reducer. That second parameter instructs Redux to run Sagas as a middleware.
Writing a Saga
We have one bit of set up left which is:
187
Handling side effects with Sagas
For now we will create our handwritten Sagas in a file called sagas.js but as our project
grows we will need to break it down in many small files to make it maintable:
// sagas.js - excerpt
Above we add the needed imports in the form delay and put and takeEvery :
delay, this is really just a utility function that resolves a promise after x numver of
milliseconds
put, this is pretty much the dispatch of Sagas, you add an action as input parameter to
it.
takeEvery, this listens to a specific action type and runs a generator function in
response to a specific action occurring
Approach
Let's take a step back and think about what we are about to do. What we wan't to do and
what Sagas promise to help us with is to clean up the flow a bit when dealing with
asynchronous actions. To accomplish that a Saga has the following data flow to it:
Let's build a Saga with that flow in mind. Let's take something dead simple like incrementing
a number. I need you to imagine that this performs an AJAX request and when its done
resolves a Promise. Let's start with step one though, listening to a specific action:
// sagas.js - excerpt
export function* watchIncrementAsync() {
console.log("I'm hit first");
yield takeEvery('INCREMENT_ASYNC', incrementAsync)
188
Handling side effects with Sagas
Above we define the function watchIncrementAsync and we see that it calls takeEvery on
the action type INCREMENT_ASYNC . The second parameter of the function says what function it
should run in the response to this action occurring, namely incrementAsync . The above is
also called a watcher function.
Next step is about us creating and defining the function incrementAsync , so let's do that
next:
// sagas.js - excerpt
We can see above that the first thing we do is to call yield delay(1000) , think of this as
writing:
await fetch(url);
What we mean by that is that this is the point where we should perform an AJAX call,
instead of writing await we use yield which is the corresponding keyword for generators.
Last thing to happen in this function is us calling put which dispatches an action and
thereby leaves control back to Redux. The full file looks like this:
189
Handling side effects with Sagas
As you can see above there isn't much to it. Let's repeat again the steps we need to take for
every Saga we write:
// index.js - excerpt
sagaMiddleware.run(watchIncrementAsync)
190
Handling side effects with Sagas
// IncrementContainer.js
We can see above how we create the container component IncrementContainer by using
the presentation component Increment and augmenting it with the method increment and
the state value . We also see that upon invoking increment() we dispatch the action of
type INCREMENT_ASYNC .
For the presentation component we just need to wire up the increment method to a button
so we can invoke the method and thereby dispatch the action so our Saga will be targeted.
Let's define the presentation component:
191
Handling side effects with Sagas
// Increment.js
Further reading
There is a lot more to Sagas, have a read at the official docs Official docs
192
Redux Form
Redux Form
redux-form is a library that makes it easier to handle forms and make them fit into the
Redux system. Let's discuss briefly how we normally approach Forms without using this
library. Our approach is usually this:
state = {
value: ''
}
render() {
return (
<form onSubmit={this.onSubmit}>
<div>
<input onChange={this.onChange} value={this.state.value}>
</div>
</form>
}
validating input, this is a big topic as it could be everything from checking wether an
input is there at all or matching some pattern or may even need to check on server side
wether something validates or not
check if its pristine, untouched, if so no need to validate and definitely we shouldn't
allow a submit to happen
Here is the good news, Redux Form library helps us with all these things, so let's show the
awesomeness of this library in this chapter.
193
Redux Form
Setting it up is quite easy as well. What we need to do is to add some code in the file where
we create the root reducer. I usually create a store.js file like so:
// store.js
value in the state of our store. To get redux-form to work we need to create the value
194
Redux Form
// store.js - excerpt
import { combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
// reducer definition
As you can see above we added the following line in our call to combineReducers :
form: formReducer
and formReducer comes from redux-form and is something we import, like so:
That concludes the set up we need to do, onwards to create a form component in our next
section.
a container component
a presentation component
There is a difference though, instead of using a connect function, to create our container
component, we will be using a function called reduxForm to achieve it. Let's create a simple
form and turn it into a redux-form :
195
Redux Form
// TodoForm.js - excerpt
Ok, so now we have our presentation form and as you can see above we are passing a
handleSubmit function through our props . Another thing is the usage of Field component
that is an abstraction over any type of input that we want to create. In this case we use to
create a normal input tag but it can be made to represent any type of form field.
Let's turn into a container component. We do that by simply calling reduxForm , like so:
// TodoForm.js - excerpt
TodoForm = reduxForm({
form: 'todo' // a unique name for this form
})(TodoForm);
Aa you can see above we are calling reduxForm with an object that has the property form
that needs to have a unique value, so we give it the value todo . This is so our store can
differ between different forms when tracking its values. Let's first have a look at the full file
TodoForm.js before putting it in use:
196
Redux Form
// TodoForm.js
At this point let's put this in use. We will create a TodoPage component like so:
// TodoPage.js
197
Redux Form
What we can see above is how we render out the TodoForm component and we also assign
a function to its property onSubmit . Let's have a closer look at the handle method of our
component:
// TodoPage.js - excerpt
Worth noting is how our input is the paraneter values that is an object representing our
form looking like so:
This is where redux-form has done its magic, in a normal form the input parameter on an
onSubmit for a form would be an event instead.
Now back to our TodoForm.js , we will try to enhance it a little bit by adding the above
properties:
198
Redux Form
// TodoForm.js
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
We have omitted the form definition above to make it easier to see what we are doing. The
change consists of digging out pristine , reset and submitting from props . Now let's
add them to our form markup. Where should we add them though? Well we can pristine
and submitting on our button and disable the button if either of those properties are true.
It really makes no sense to allow a submit button to be pushed in the middle of submitting or
when the user hasn't even interacted with the form. Our markup therefore now looks like so:
// TodoForm.js - excerpt
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="title">Title</label>
<Field name="title" component="input" type="text"/>
</div>
<div>
<label htmlFor="email">Email</label>
<Field name="email" component="input" type="email"/>
</div>
<button disabled={ pristine || submitting } type="submit">Submit</button>
</form>
What about our last property, reset . Well this is a functionality that we can assign to a
button, like so:
199
Redux Form
It makes sense to add the pristine condition to its disabled attribute as there is no point
resetting a form that hasn't been interacted with. Our form with some inputs now looks like
this:
Because we started to interact with the form you can see that the reset button is enabled.
Pressing reset button however leads to this:
200
Redux Form
provide a property validate to the object you give to reduxForm , this property should
point to a validation function that you need to write
define a custom input control where you are able to show your input as well as errors, if
there are any
201
Redux Form
// TodoForm.js - excerpt
TodoForm = reduxForm({
form: 'todo',
validate
})(TodoForm);
As you can see above we are adding the property validate that points to a function
validate that we have yet to specify.
Next step is defining the validate function which we can do like so:
Now what? Well this function is expected to return a dictionary of your errors. The input
parameter values contain all the field values so if you have a field title , values will
contain:
if (!values.title) {
errors.title = 'title must be entered';
}
return errors;
}
Above we have now defined the validate function. We go through the values input
parameter which is an object containing all of our field values. If we find that a certain values
isn't what it should be, like:
not existing
not conforming to a pattern then we create an entry in the dictionary errors with an
error message. The method validate will be invoked on each keyup event. Ok now
we have everything set up and we are ready to move on to the next bit which is how do
we display errors?
202
Redux Form
Customize an input
We have been using Field as the component that represents an input. So far we have set
an attribute component like so:
We can give component a function as an argument instead of a string. This means that we
decide what gets rendered. Let's specify such a function:
const renderField = ({
input,
label,
type,
meta: { touched, error }
}) => {
return (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched &&
((error && <Error>{error}</Error>))}
</div>
</div>
)
}
What we see above is that renderField is function that takes an object as a parameter and
expects us to return a markup. Looking a little deeper at the input parameter we see that we
have the following interesting bits:
Having a look at the markup, we see that we can lay out this input parameters to create a
nice looking input and a suitable place for our validation error:
203
Redux Form
(
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched &&
((error && <Error>{error}</Error>))}
</div>
</div>
)
Note, that we look on the property touched , which means if we interacted with the element
and based on it being in that state then we display the error:
Lastly we need to instruct our Field component to use this function, like so:
Finishing up
We have defined our validation function. We have defined a function that let's create our
own representation of an input. Putting it all together our TodoForm.js should now look like
this:
// TodoForm.js
input {
204
Redux Form
button {
border: solid 1px grey
}
`;
return errors;
}
const renderField = ({
input,
label,
type,
meta: { touched, error }
}) => {
return (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched &&
((error && <Error>{error}</Error>))}
</div>
</div>
)
}
205
Redux Form
<div>
<Field name="email" component="input" type="email"/>
</div>
<button onClick={reset} disabled={ pristine } type="submit">Reset</button>
<button disabled={ pristine || submitting } type="submit">Submit</button>
</form>
</FormContainer>
);
}
}
Further reading
There is a lot more to this wonderful library. Have a look at the official docs for more
advanced scenarios Official docs
206
Observables
Observables
TODO
207
Computed variables
Computed variables
TODO
208
Reactions
Reactions
TODO
209
storybook
Storybook
Imagine when your project grows and the number of components and developers with it. It
might get hard to keep track of all the components and how they are rendered differently
depending on what input they get. For those situations there exists a number of tools where
you can visually display your components - a style guide. This article is about one such tool
called Storybook.
npm i -g @storybook/cli
Once we have done so we can create our React project and from the project root we can
type:
getstorybook
add the following to package.json and the scripts tag: "storybook": "start-storybook -
p 9009 -s public"
addons.js
config.js config.js tells storybook where our stories can be found. The stories
directory contains an index.js and stories which is something that storybook picks up
and renders as a HTML page.
210
storybook
storiesOf('Button', module)
.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
.add('with some emoji', () => (
<Button onClick={action('clicked')}>
<span role="img" aria-label="so cool">
</span>
</Button>
));
Run
By running
yarn storybook
It will startup story book and we can navigate to http://localhost:9000 . It will look like this:
211
storybook
Let's look at the code that produces this in stories/index.js . Every call storiesOf('name of
module', module) produces a section. We can then chain a call to id add('name of component
variant', () => ()) . Now the add method is worth looking closer at:
The second argument renders out a React component and we seem to be able to set
whatever property we want in there. Let's try to create a component and add it to
stories/index.js next.
Add a story
Let's do the following:
// Todos.js
import React from 'react';
import styled from 'styled-components';
212
storybook
// stories/index.js
storiesOf('Button', module)
.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
.add('with some emoji', () => (
<Button onClick={action('clicked')}>
<span role="img" aria-label="so cool">
</span>
</Button>
));
storiesOf('Todos', module)
.add('with todos', () => <Todos todos={mocks.todos} />)
The mocks.js is a file we created to supply our components with data. We chose to place it
in a separate file so we don't clutter up index.js . The mocks.js is a very simple file
looking like this:
// stories/mocks.js
const mocks = {
todos: [{ title: 'test' }, { title: 'test2' }]
};
Now back to our index.js file and let's look at the part where we set up our story:
213
storybook
storiesOf('Todos', module)
.add('with todos', () => <Todos todos={mocks.todos} />)
As you can see we set up the story and define a variant of that story by calling add . Let's
have a look at the result:
function loadStories() {
require('../src/stories');
}
configure(loadStories, module);
The loadStories function allows us to add more entries in there, which is perfect. Let's do
that:
function loadStories() {
require('../src/stories');
require('../src/stories/common');
}
configure(loadStories, module);
As you can see we can now point out ../src/stories/common as yet another place our
stories can exist on. That directory now needs to consist of an index.js and we can write
our stories per usual like so:
214
storybook
// stories/common/index.js
import React from 'react';
storiesOf('Button', module)
.add('with text', () => <Button title={'title for a button'}>text</Button>)
.add('with markup', () => <Button title={'title for a button'}><h3>My button</h3><sp
an>button text</span></Button>)
215
storybook
So if you start to have components divided up into different directories it can be a good idea
to bring that directory structure with you.
Further reading
Official docs Great article on different style guide for React
216
storybook
217
Joi - awesome code validation
if (!data.paramterX) {
throw new Exception('parameterX missing')
}
try {
let value = parseInt(data.parameterX);
} catch (err) {
throw new Exception('parameterX should be number')
}
if(!/[a-z]/.test(data.parameterY)) {
throw new Exception('parameterY should be lower caps text')
}
I think you get the idea from the above cringeworty code. We tend to perform a lot of tests
on our parameters to ensure they are the right type and/or their values contains the
allowed values .
218
Joi - awesome code validation
As developers we tend to feel really bad about code like this so we either start writing a lib
for this or we turn to our old friend NPM and hope that some other developer have felt this
pain and had too much time on their hands and made a lib that you could use.
There are many libs that will do this for you. I aim to describe a specific one called Joi.
Throughout this article we will take the following journey together:
Introducing Joi
Installing Joi is quite easy. We just need to type:
After that we are ready to use it. Let's have a quick look at how we use it. First thing we do is
import it and then we set up some rules, like so:
const dataToValidate = {
name 'chris',
birthyear: 1971
}
219
Joi - awesome code validation
Ok, now we understand the basic motions. What else can we do?
Well Joi supports all sorts of primitives as well as Regex and can be nested to any depth.
Let's list some different constructs it supports:
string, this says it needs to be of type string, and we use it like so Joi.string()
number, Joi.number() and also supporting helper operations such as min() and
max() , like so Joi.number().min(1).max(10)
required, we can say wether a property is required with the help of the method
required , like so Joi.string().required()
any, this means it could be any type, usually we tend to use it with the helper allow()
that specifies what it can contain, like so, Joi.any().allow('a')
optional, this is strictly speaking not a type but has an interesting effect. If you specify
for example prop : Joi.string().optional . If we don't provide prop then everybody's
happy. However if we do provide it and make it an integer the validation will fail
array, we can check wether the property is an array of say strings, then it would look
like this Joi.array().items(Joi.string().valid('a', 'b')
regex, it supports pattern matching with RegEx as well like so Joi.string().regex(/^[a-
zA-Z0-9]{3,30}$/)
The whole API for Joi is enormous. I suggest to have a look and see if there is a helper
function that can solve whatever case you have that I'm not showing above Joi API
Nested types
Ok so we have only showed how to declare a schema so far that is one level deep. We did
so by calling the following:
Joi.object().keys({
});
This stated that our data is an object. Then we added some properties to our object like so:
Joi.object().keys({
name: Joi.string().alphanum().min(3).max(30).required(),
birthyear: Joi.number().integer().min(1970).max(2013)
});
Now, nested structures are really more of the same. Let's create an entirely new schema, a
schema for a blog post, looking like this:
220
Joi - awesome code validation
Note especially the comments property, that thing looks exactly like the outer call we first
make and it is the same. Nesting is as easy as that.
221
Joi - awesome code validation
res.json({
message: 'Resource created',
data: createdPost
})
}
});
The above works. But we have to define the schema call validate() on each request to a
specific route. It's, for lack of a better word, lacks elegance. We want something more slick
looking.
Building a middleware
Let's see if we can't rebuild it a bit to a middleware. Middlewares in Express is simply
something we can stick into the request pipeline whenever we need it. In our case we would
want to try and verify our request and early on determine wether it is worth proceeding with it
or abort it.
222
Joi - awesome code validation
app.post(
'/blog',
middleware,
handler
)
It would be neat if we could provide a schema to our middleware so all we had to do in the
middleware function was something like this:
We should create a module with a factory function and module for all our schemas. Let's
have a look at our factory function module first:
res.status(422).json({
error: message
})
}
}
}
module.exports = middleware;
Let's thereafter create a module for all our schemas, like so:
223
Joi - awesome code validation
// schemas.js
const schemas = {
blogPOST: Joi.object().keys({
title: Joi.string().required
description: Joi.string().required()
})
// define all the other schemas below
};
module.exports = schemas;
// app.js
res.json(req.body);
});
Testing it out
There are many ways to test this out. We could do a fetch() call from a browser console
or use cURL and so on. We opt for using a chrome plugin called Advanced REST Client .
Let's try to make a POST request to /blog . Remember our schema for this route said that
title and description were mandatory so let's try to crash it, let's omit title and see
what happens:
224
Joi - awesome code validation
Aha, we get a 422 status code and the message title is required , so Joi does what it is
supposed to. Just for safety sake let's re add title :
225
Joi - awesome code validation
query parameters , here it makes sense to check that for example parameters like
page and pageSize exist and is of type number . Imagine us doing a crazy request
number if we should get a number that is ( we could be sending GUIDs for example )
and maybe check that we are not sending something that is obviously wrong like a 0
or something
226
Joi - awesome code validation
Ok, we know of query parameters in Express that they reside under the request.query . So
the simplest thing we could do here is to ensure our middleware.js takes another
parameter, like so:
and our full code for middleware.js would therefore look like this:
res.status(422).json({
error: message
})
}
}
}
module.exports = middleware;
This would mean we would have to have a look at app.js and change how we invoke our
middleware() function. First off our POST request would now have to look like this:
res.json(req.body);
});
Let's now add the request who's query parameters we are interested in:
227
Joi - awesome code validation
As you can see all we have to do here is adding the argument query . Lastly let's have a
look at our schemas.js :
// schemas.js
const Joi = require('joi');
const schemas = {
blogPOST: Joi.object().keys({
title: Joi.string().required(),
description: Joi.string().required(),
year: Joi.number()
}),
blogLIST: {
page: Joi.number().required(),
pageSize: Joi.number().required()
}
};
module.exports = schemas;
Testing it out
228
Joi - awesome code validation
Let's head back to Advanced REST client and see what happens if we try to navigate to
/products without adding the query parameters:
As you can see Joi kicks in and tells us that page is missing.
229
Joi - awesome code validation
Let's ensure page and pageSize is added to our URL and try it again:
// app.js
app.get('/products/:id', middleware(schemas.blogDETAIL, 'params'), function(req, res)
{
console.log("/products/:id");
const { id } = req.params;
res.json(req.params);
})
At this point we need to go into schemas.js and add the blogDetail entry so schemas.js
should now look like the following:
230
Joi - awesome code validation
const schemas = {
blogPOST: Joi.object().keys({
title: Joi.string().required(),
description: Joi.string().required(),
year: Joi.number()
}),
blogLIST: {
page: Joi.number().required(),
pageSize: Joi.number().required()
},
blogDETAIL: {
id: Joi.number().min(1).required()
}
};
module.exports = schemas;
Try it out
231
Joi - awesome code validation
Last step is trying it out so let's first test to navigate to /products/abc . That should throw an
error, we are only OK with numbers over 0:
232
Joi - awesome code validation
Summary
We have introduced the validation library Joi and presented some basic features and how
to use it. Lastly we have looked at how to create a middleware for Express and use Joi in a
smart way.
Further reading
Joi, official docs Official docs
exhaustive blog post on Joi validation, if you need more complex examples Blog post
Demo repository
My Twitter
233
Joi - awesome code validation
234