Book
Book
Introduction 1.1
In brief 1.2
Foundation
Communication 2.1
Input 2.1.1
Output 2.1.2
Event handlers 2.2
Composition 2.3
Using React's children API 2.3.1
Data flow
One direction data flow 3.1
Flux 3.2
2
Redux 3.3
Good to know
Dependency injection 4.1
Styled-components 4.2.4
Integration of third-party libraries 4.3
Summary
Summary 5.1
3
Introduction
React in patterns
A book about common design patterns used while developing with
React. It includes techniques for composition, data flow, dependency
management and more.
Web (https://krasimir.gitbooks.io/react-in-patterns/content/)
PDF (https://github.com/krasimir/react-in-
patterns/blob/master/book.pdf)
Mobi (https://www.gitbook.com/download/mobi/book/krasimir/react-
in-patterns)
ePub (https://www.gitbook.com/download/epub/book/krasimir/react-
in-patterns)
GitHub (https://github.com/krasimir/react-in-patterns)
4
Introduction
5
In brief
In brief
This cookbook is targeting developers that already have a basic
understanding of what React is and how it works. It's not meant to be
used as a complete how-to guide but as an introduction to popular
concepts/design patterns. Paradigms that more or less are introduced
by the community. It points you to an abstract thinking. For example,
instead of talking about Flux, it talks about data flow. Instead of talking
about higher-order components it talks about composition.
Also notice that English is not my native language. If you see a typo or
something sounds weird please contribute here
github.com/krasimir/react-in-patterns. If you are reading from a printed
version of this book then feel free to use a pen ¯\(ツ)/¯
6
Communication
Communication
Every React component is like a small system that operates on its own.
It has its own state, input and output. In the following section we will
explore these characteristics.
Input
The input of a React component is its props. That's how we pass data to
it:
// Title.jsx
function Title(props) {
return <h1>{ props.text }</h1>;
}
Title.propTypes = {
text: PropTypes.string
};
Title.defaultProps = {
text: 'Hello world'
};
// App.jsx
function App() {
return <Title text='Hello React' />;
}
7
Communication
The Title component has only one input (prop) - text . The parent
component ( App ) should provide it as an attribute while using the
<Title> tag. Alongside the component definition we also have to define
at least propTypes . In there we define the type of every property and
React hints us in the console if something unexpected gets sent.
defaultProps is another useful option. We may use it to set a default
value of component's props so that if the developer forgets to pass them
we have meaningful values.
8
Communication
children } as part of the Title 's body the <span> tag will not be
rendered.
Output
The first obvious output of a React component is the rendered HTML.
Visually that is what we get. However, because the prop may be
everything including a function we could also send out data or trigger a
process.
9
Communication
Very often we need an entry point of our logic. React comes with some
handy lifecycle methods that may be used to trigger a process. For
example we have an external resource that needs to be fetched on a
specific page.
10
Communication
Final thoughts
It is nice that we may think about every React component as a black
box. It has its own input, lifecycle and output. It is up to us to compose
these boxes. And maybe that is one of the advantages that React offers.
Easy to abstract and easy to compose.
11
Event handlers
Event handlers
React provides a series of attributes for handling events. The solution is
almost the same as the one used in the standard DOM. There are some
differences like using camel case or the fact that we pass a function but
overall it is pretty similar.
12
Event handlers
However, this means that the bind function is called again and again
because we may render the button many times. A better approach
would be to create the bindings in the constructor of the component:
13
Event handlers
The constructor is also a nice place for partially executing our handlers.
For example, we have a form but want to handle every input in a single
function.
14
Event handlers
Final thoughts
There is not much to learn about event handling in React. The authors
of the library did a good job in keeping what's already there. Since we
are using HTML-like syntax it makes total sense that we also have a
DOM-like event handling.
15
Composition
Composition
One of the biggest benefits of React is composability. I personally don't
know a framework that offers such an easy way to create and combine
components. In this section we will explore a few composition
techniques which proved to work well.
Let's get a simple example. Let's say that we have an application with a
header and we want to place a navigation inside. We have three React
components - App , Header and Navigation . They have to be nested
into each other so we end up with the following dependencies:
// app.jsx
import Header from './Header.jsx';
// Header.jsx
import Navigation from './Navigation.jsx';
// Navigation.jsx
export default function Navigation() {
return (<nav> ... </nav>);
}
16
Composition
17
Composition
It now becomes easier to test because we may render the Header with
an empty <div> . This will isolate the component and will let us focus on
one piece of our application.
18
Composition
Higher-order component
For a long period of time higher-order components were the most
popular way to enhance and compose React elements. They look really
similar to the decorator design pattern because we have component
wrapping and enhancing.
The very first thing that the higher-order component does is to render
the original component. It's a good practice to proxy pass the props to
it. This way we will keep the input of our original component. And here
comes the first big benefit of this pattern - because we control the input
of the component we may send something that the component usually
has no access to. Let's say that we have a configuration setting that
OriginalTitle needs:
19
Composition
20
Composition
Again, the OriginalTitle knows that it receives two props and has to
render them next to each other. Its only concern is how the data looks
like not where it comes from and how.
Dan Abramov made a really good point that the actual creation of the
higher-order component (i.e. calling a function like enhanceComponent )
should happen at a component definition level. Or in other words, it's a
bad practice to do it inside another React component because it may be
slow and lead to performance issues.
21
Composition
function App() {
const user = {
firstName: 'Krasimir',
lastName: 'Tsonev'
};
return (
<UserName>{ user }</UserName>
);
}
This may look weird but in fact is really powerful. Like for example when
we have some knowledge in the parent component and don't necessary
want to send it down to children. The example below prints a list of
TODOs. The App component has all the data and knows how to
determine whether a TODO is completed or not. The TodoList
22
Composition
function App() {
const todos = [
{ label: 'Write tests', status: 'done' },
{ label: 'Sent report', status: 'progress' },
{ label: 'Answer emails', status: 'done' }
];
const isCompleted = todo => todo.status === 'done';
return (
<TodoList todos={ todos }>
{
todo => isCompleted(todo) ?
<b>{ todo.label }</b> :
todo.label
}
</TodoList>
);
}
Notice how the App component doesn't expose the structure of the
data. TodoList has no idea that there is label or status properties.
The so called render prop pattern is almost the same except that we use
the render prop and not children for rendering the todo.
23
Composition
return (
<TodoList
todos={ todos }
render={
todo => isCompleted(todo) ?
<b>{ todo.label }</b> : todo.label
} />
);
These two patterns, function as children and render prop are probably
one of my favorite ones recently. They provide flexibility and help when
we want to reuse code. They are also a powerful way to abstract
imperative code.
24
Composition
We do say what we want to happen but not how. That is hidden inside
the DataProvider . These days we used this pattern at work where we
had to restrict some UI to certain users having read:products
<Authorize
permissionsInclude={[ 'read:products' ]}
render={ () => <ProductsList /> } />
goes to our identity provider and checks what are the permissions of the
current user. If he/she is allowed to read our products we render the
ProductList .
Final thoughts
Did you wonder why HTML is still here. It was created in the dawn of the
internet and we still use it. That is because it's highly composable. React
and its JSX looks like HTML on steroids and as such it comes with the
same capabilities. So, make sure that you master the composition
because that is one of the biggest benefits of React.
25
Composition
26
Controlled and uncontrolled inputs
The result of this code is an input element that we can focus but can't
change. It is never updated because we have a single source of truth -
the App 's component state. To make the input work as expected we
have to add an onChange handler and update the state (the single
source of truth). Which will trigger a new rendering cycle and we will see
what we typed.
27
Controlled and uncontrolled inputs
On the opposite side is the uncontrolled input where we let the browser
handle the user's updates. We may still provide an initial value by using
the defaultValue prop but after that the browser is responsible for
keeping the state of the input.
That <input> element above is a little bit useless because the user
updates the value but our component has no idea about that. We then
have to use Refs to get access to the actual element.
28
Controlled and uncontrolled inputs
The ref prop receives a string or a callback. The code above uses a
callback and stores the DOM element into a local variable called input .
Later when the onChange handler is fired we get the new value and
send it to the App 's state.
Final thoughts
controlled versus uncontrolled inputs is very often underrated. However I
believe that it is a fundamental decision because it dictates the data flow
in the React component. I personally think that uncontrolled inputs are
kind of an anti-pattern and I'm trying to avoid them when possible.
29
Controlled and uncontrolled inputs
30
Presentational and container components
Let's start with a simple example that illustrates the problem and then
split the component into container and presentation. We will use a
Clock component. It accepts a Date object as a prop and displays the
time in real time.
31
Presentational and container components
_formatTime(time) {
var [ hours, minutes, seconds ] = [
time.getHours(),
time.getMinutes(),
time.getSeconds()
].map(num => num < 10 ? '0' + num : num);
The problems
There are couple of things happening in our component. It looks like it
has too many responsibilities.
knows the current value. If there is another part of the system that
depends on this data it will be difficult to share it.
32
Presentational and container components
33
Presentational and container components
// Clock/index.js
import Clock from './Clock.jsx'; // <-- that's the presentational
component
It still accepts time (a date object), does the setInterval loop and
knows details about the data ( getHours , getMinutes and getSeconds ).
In the end renders the presentational component and passes three
numbers for hours, minutes and seconds. There is nothing about how
the things look like. Only business logic.
34
Presentational and container components
Presentational component
Presentational components are concerned with how the things look.
They have the additional markup needed for making the page pretty.
Such components are not bound to anything and have no
dependencies. Very often implemented as a stateless functional
components they don't have internal state.
// Clock/Clock.jsx
export default function Clock(props) {
var [ hours, minutes, seconds ] = [
props.hours,
props.minutes,
props.seconds
].map(num => num < 10 ? '0' + num : num);
Benefits
Splitting the components in containers and presentation increases the
reusability of the components. Our Clock function/component may
exist in application that doesn't change the time or it's not working with
JavaScript's Date object. That's because it is pretty dummy. No details
about the data are required.
The containers encapsulate logic and we may use them together with
different renderers because they don't leak information about the visual
part. The approach that we took above is a good example of how the
35
Presentational and container components
container doesn't care about how the things look like. We may easily
switch from digital to analog clock and the only one change will be to
replace the <Clock> component in the render method.
Even the testing becomes easier because the components have less
responsibilities. Containers are not concern with UI. Presentational
components are pure renderers and it is enough to run expectations on
the resulted markup.
Final thoughts
The concept of container and presentation is not new at all but it fits
really nicely with React. It makes our applications better structured, easy
to manage and scale.
36
One direction data flow
37
One direction data flow
var Store = {
_flag: false,
set: function(value) {
this._flag = value;
},
get: function() {
return this._flag;
}
};
function App() {
return <Switcher onChange={ Store.set.bind(Store) } />;
};
Our Store object is a singleton where we have helpers for setting and
getting the value of the _flag property. By passing the setter to the
Switcher we are able to update the data externally. More or less our
application workflow looks like that:
38
One direction data flow
Let's assume that we are saving the flag value to a back-end service via
the Store . When the user comes back we have to set a proper initial
state. If the user left the flag as true we have to show "lights on" and
not the default "lights off". Now it gets tricky because we have the data
in two places. The UI and the Store have their own states. We have to
communicate in both directions from the store to the switcher and from
the switcher to the store.
39
One direction data flow
All this leads to managing two states instead of one. What if the Store
40
One direction data flow
var Store = {
_handlers: [],
_flag: '',
subscribe: function(handler) {
this._handlers.push(handler);
},
set: function(value) {
this._flag = value;
this._handlers.forEach(handler => handler(value))
},
get: function() {
return this._flag;
}
};
Then we will hook our main App component and we'll re-render it every
time when the Store changes its value:
41
One direction data flow
<Switcher
value={ Store.get() }
onChange={ Store.set.bind(Store) } />
Final thoughts
The benefit that comes with this pattern is that our components become
dummy representation of the store's data. There is only one source of
truth and this makes the development easier. If you are going to take
one thing from this book I would prefer to be this chapter. The one-
direction data flow drastically changed the way of how I think when
designing a feature so I believe it will have the same effect on you.
42
Flux
Flux
I'm obsessed by making my code simpler. I didn't say smaller because
having less code doesn't mean that is simple and easy to work with. I
believe that big part of the problems in the software industry come from
the unnecessary complexity. Complexity which is a result of our own
abstractions. You know, we (the programmers) like to abstract. We like
placing things in black boxes and hope that these boxes work together.
43
Flux
The main actor in this pattern is the dispatcher. It acts as a hub for all
the events in the system. Its job is to receive notifications that we call
actions and pass them to all the stores. The store decides if it is
interested or not and reacts by changing its internal state/data. That
change is triggering re-rendering of the views which are (in our case)
React components. If we have to compare Flux to the well known MVC
we may say that the store is similar to the model. It keeps the data and
its mutations.
The actions are coming to the dispatcher either from the views or from
other parts of the system, like services. For example a module that
performs a HTTP request. When it receives the result it may fire an
action saying that the request was successful.
The dispatcher
In most of the cases we need a single dispatcher. Because it acts as a
glue for the rest of the parts it makes sense that we have only one. The
dispatcher needs to know about two things - actions and stores. The
actions are simply forwarded to the stores so we don't necessary have
to keep them. The stores however should be tracked inside the
dispatcher so we can loop through them:
44
Flux
45
Flux
Using a helper
Some of the flux implementations available out there provide a helper
function that does the job. For example:
Framework.attachToStore(view, store);
46
Flux
With a mixin
What if we use React's mixins.
Using a context
Another technique that may answer the question is React's context. It is
a way to pass props to child components without the need to specify
them in every level of the tree. Facebook suggests context in the cases
where we have data that has to reach deeply nested components.
I see similarity with the mixins here. The context is defined somewhere
at the top and magically serves props for all the children below. It's not
immediately clear where the data comes from.
47
Flux
48
Flux
}));
My choice
The last option above, higher-order components, is really close to what
I'm searching for. I like the fact that the view decides what it needs. That
knowledge anyway exists in the component so it makes sense to keep it
there. That's also why the functions that generate higher-order
components are usually kept in the same file as the view. What if we
can use similar approach but not passing the store at all. Or in other
words, a function that accepts only the consumer. And that function is
called every time when there is a change in the store.
So far our implementation interacts with the store only in the register
method.
49
Flux
I decided to send the whole store to the consumer function and not the
data that the store keeps. Like in the higher-order components pattern
the view should say what it needs by using store's getters. This makes
the store really simple and there is no trace of presentational logic.
Here is how the register method looks like after the changes:
50
Flux
The last bit in the story is how the store says that its internal state is
changed. It's nice that we collect the consumer functions but right now
there is no code that executes them.
51
Flux
Notice how we push change together with store inside the _stores
A common use case is to render the view with the initial state of the
store. In the context of our implementation this means firing all the
consumers at least once when they land in the library. This could be
easily done in the subscribe method:
52
Flux
53
Flux
The actions
You probably noticed that we didn't talk about the actions. What are
they? The convention is that they should be simple objects having two
properties - type and payload :
{
type: 'USER_LOGIN_REQUEST',
payload: {
username: '...',
password: '...'
}
}
The type says what exactly the action is and the payload contains the
information associated with the event. And in some cases we may leave
the payload empty.
It's interesting that the type is well known in the beginning. We know
what type of actions should be floating in our app, who is dispatching
them and which of the stores are interested. Thus, we can apply partial
application and avoid passing the action object here and there. For
example:
54
Flux
55
Flux
And instead of exporting the dispatcher we may export only these two
functions createAction and createSubscriber . Here is how the final
code looks like:
module.exports = {
create: function () {
var dispatcher = Dispatcher();
56
Flux
return {
createAction: function (type) {
if (!type) {
throw new Error('Please, provide action\'s type.');
} else {
return function (payload) {
return dispatcher.dispatch({
type: type,
payload: payload
});
}
}
},
createSubscriber: function (store) {
return dispatcher.register(store);
}
}
}
};
Wrapping up
We have a module that provides two helpers for building a Flux project.
Let's write a simple counter app that doesn't involve React so we see
the pattern in action.
57
Flux
The markup
We'll need some UI to interact with it so:
<div id="counter">
<span></span>
<button>increase</button>
<button>decrease</button>
</div>
The span will be used for displaying the current value of our counter.
The buttons will change that value.
The view
subscribeToStore([updateState, render]);
increaseBtn.addEventListener('click', increase);
decreaseBtn.addEventListener('click', decrease);
};
58
Flux
After that we define a render function which puts the value inside the
span tag. updateState will be called every time when the store
changes. So, we pass these two functions to subscribeToStore
because we want to get the view updated and we want to get an initial
rendering. Remember how our consumers are called at least once by
default.
The last bit is calling the action functions when we press the buttons.
The store
Every action has a type. It's a good practice to create constants for
these types so we don't deal with raw strings.
Very often we have only one instance of every store. For the sake of
simplicity we'll create ours as a singleton.
const CounterStore = {
_data: { value: 0 },
getValue: function () {
return this._data.value;
},
update: function (action, change) {
if (action.type === INCREASE) {
this._data.value += 1;
} else if (action.type === DECREASE) {
this._data.value -= 1;
}
change();
}
};
59
Flux
_data is the internal state of the store. update is the well known
method that our dispatcher calls. We process the action inside and say
change() when we are done. getValue is a public method used by the
view so it reaches the needed info. In our case this is just the value of
the counter.
And that's it. Our view is subscribed to the store and it renders by default
because one of our consumers is actually the render method.
A live demo
A live demo could be seen in the following JSBin
http://jsbin.com/koxidu/embed?js,output. If that's not enough and it
seems too simple for you please checkout the example in Fluxiny
repository https://github.com/krasimir/fluxiny/tree/master/example. It
uses React as a view layer.
60
Flux
61
Redux
Redux
Redux is a library that acts as a state container and helps managing
your application data flow. It was introduced back in 2015 at
ReactEurope conference (video) by Dan Abramov. It is similar to Flux
architecture and has a lot in common with it. In this section we will
create a small counter app using Redux alongside React.
62
Redux
63
Redux
our data changes lives in pure functions called reducers. Once the store
receives an action it asks the reducers about the new version of the
state by sending the current state and the given action. Then in
immutable fashion the reducer needs to return the new state. The store
continues from there and updates its internal state. As a final step, the
wired to the store React component gets re-rendered.
The concept is pretty linear and again follows the one-direction data
flow. Let's talk about all these pieces and introduce a couple of new
terms that support the work of the Redux pattern.
Actions
The typical action in Redux (same as Flux) is just an object with a type
The visible property is the meta data that we mentioned above. It has
nothing to do with Redux. It means something in the context of the
application.
64
Redux
changeVisibility(false);
// { type: CHANGE_VISIBILITY, visible: false }
Store
Redux provides a helper createStore for creating a store. Its signature
is as follows:
65
Redux
provides an API for extending Redux with third party middlewares and
basically plug some functionally which is not baked-in. Like for example
an instrument for handling async processes.
store.dispatch(changeVisibility(false));
That is the place where we use our action creators. We pass the result
of them or in other words action objects to this dispatch method. It
then gets spread to the reducers in our application.
Reducer
The reducer function is probably the most beautiful part of Redux. Even
before that I was interested in writing pure functions with an immutability
in mind but Redux forced me to do it. There are two characteristics of
the reducer that are quite important and without them we basically have
a broken pattern.
(1) It must be a pure function - it means that the function should return
the exact same output every time when the same input is given.
66
Redux
(2) It should have no side effects - stuff like accessing a global variable,
making an async call or waiting for a promise to resolve have no place
in here.
There are no side effects and we return a brand new object every time.
We accumulate the new value based on the previous state and the
incoming action type.
(1) <Provider> component - it's a component that accepts our store and
makes it available for the children down the React tree via the React's
context API. For example:
67
Redux
connect(
[mapStateToProps],
[mapDispatchToProps],
[mergeProps],
[options]
)
and the props send to the component and gives us the opportunity to
accumulate better props. Like for example if we need to fire two actions
we may combine them to a single prop and send that to React. options
68
Redux
The "Add" and "Subtract" buttons will simply change a value in our store.
"Visible" and "Hidden" will control its visibility.
69
Redux
There is something that we didn't talk about while explaining the store
and reducers. We usually have more then one reducer because we
want to manage multiple things. The store is one though and we in
theory have only one state object. What happens in most of the apps
running in production is that the application state is a composition of
slices. Every slice represents a part of our system. In this very small
example we have counting and visibility slices. So our initial state looks
like that:
const initialState = {
counter: {
value: 0
},
visible: true
};
Redux comes with a helper that allows us to target a specific part of the
state and assign a reducer to it. It is called combineReducers :
70
Redux
The reducer for our counter slice should take into account both actions
ADD and SUBTRACT and based on them calculates the new counter
state.
Every reducer is fired at least once when the store is initialized. In that
very first run the state is undefined and the action is { type:
"@@redux/INIT"} . In this case our reducer should return the initial value
of our data - { value: 0 } .
The reducer for the visibility is pretty similar except that it waits for
CHANGE_VISIBILITY action:
71
Redux
Selectors
Before moving to the React components we have to mention the
concept of a selector. From the previous section we know that our state
is usually divided into different parts. We have dedicated reducers to
update the data but when it comes to fetching it we still have a single
object. Here is the place where the selectors come in handy. The
selector is a function that accepts the whole state object and extracts
only the information that we need. For example in our small app we
need two of those:
A counter app is too small to see the real power of writing such helpers.
However, in a big project is quite different. And it is not just about saving
a few lines of code. Neither is about readability. Selectors come with
these stuff but they are also contextual and may contain logic. Since
they have access to the whole state they are able to answer business
logic related questions. Like for example "Is the user authorize to do X
while being on page Y". This may be done in a single selector.
React components
Let's first deal with the UI that manages the visibility of the counter.
72
Redux
);
}
73
Redux
Final thoughts
Redux is a wonderful pattern. Over the years the JavaScript community
developed the idea and enhanced it with couple of new terms. I think a
typical redux application looks more like this:
74
Redux
75
Redux
76
Dependency injection
Dependency injection
Many of the modules/components that we write have dependencies. A
proper management of these dependencies is critical for the success of
the project. There is a technique (most people consider it a pattern)
called dependency injection that helps solving the problem.
// Title.jsx
export default function Title(props) {
return <h1>{ props.title }</h1>;
}
// Header.jsx
import Title from './Title.jsx';
// App.jsx
import Header from './Header.jsx';
77
Dependency injection
// inject.jsx
const title = 'React in patterns';
// -----------------------------------
// Header.jsx
import inject from './inject.jsx';
import Title from './Title.jsx';
78
Dependency injection
React has the concept of context. The context is something that every
React component has access to. It's something like an event bus but for
data. A single store which we access from everywhere.
79
Dependency injection
Notice that we have to specify the exact signature of the context object.
With childContextTypes and contextTypes . If those are not specified
then the context object will be empty. That can be a little bit frustrating
because we may have lots of stuff to put there. That is why it is a good
practice that our context is not just a plain object but it has an interface
that allows us to store and retrieve data. For example:
// dependencies.js
export default {
data: {},
get(key) {
return this.data[key];
},
register(key, value) {
this.data[key] = value;
}
}
Then, if we go back to our example, the App component may look like
that:
80
Dependency injection
And our Title component gets it's data through the context:
// Title.jsx
export default class Title extends React.Component {
render() {
return <h1>{ this.context.get('title') }</h1>
}
}
Title.contextTypes = {
data: React.PropTypes.object,
get: React.PropTypes.func,
register: React.PropTypes.func
};
// Title.jsx
import wire from './wire';
function Title(props) {
return <h1>{ props.title }</h1>;
}
The wire function accepts a React component, then an array with all
the needed dependencies (which are register ed already) and then a
function which I like to call mapper . It receives what is stored in the
context as a raw data and returns an object which is later used as props
81
Dependency injection
for our component ( Title ). In this example we just pass what we get -
a title string variable. However, in a real app this could be a
collection of data stores, configuration or something else.
82
Dependency injection
new one which I think is more natural and easy to work with.
Let's use the same example with the string that needs to reach a
<Title> component.
We will start by defining a file that will contain our context initialization:
// context.js
import { createContext } from 'react';
accepts our context in the form of a value prop. The consumer is used
to access the context and basically read data from it. And because they
usually live in different files it is a good idea to create a single place for
their initialization.
Let's say that our App component is the root of our tree. At that place
we have to pass the context.
83
Dependency injection
The wrapped components and their children now share the same
context. The <Title> component is the one that needs the title
function Title() {
return (
<Consumer>{
({ title }) => <h1>Title: { title }</h1>
}</Consumer>
);
}
Notice that the Consumer class uses the function as children (render
prop) pattern to deliver the context.
The new API feels easier to understand and eliminates the boilerplate. It
is still pretty new but looks promising. It opens a whole new range of
possibilities.
Modules are cached after the first time they are loaded. This
means (among other things) that every call to require('foo') will get
exactly the same object returned, if it would resolve to the same
file.
84
Dependency injection
How is that helping for our injection? Well, if we export an object we are
actually exporting a singleton and every other module that imports the
file will get the same object. This allows us to register our
dependencies and later fetch them in another file.
Let's create a new file called di.jsx with the following content:
85
Dependency injection
86
Dependency injection
// app.jsx
import Header from './Header.jsx';
import { register } from './di.jsx';
// -----------------------------------
// Header.jsx
import Title from './Title.jsx';
// -----------------------------------
// Title.jsx
import { wire } from './di.jsx';
If we look at the Title.jsx file we'll see that the actual component and
the wiring may live in different files. That way the component and the
mapper function become easily unit testable.
87
Dependency injection
Final thoughts
Dependency injection is a tough problem. Especially in JavaScript. Lots
of people didn't realize that but putting a proper dependency
management is a key process of every development cycle. JavaScript
ecosystem offers different tools and we as developers should pick the
one that fits in our needs.
88
Styling
<h1 className='title'>Styling</h1>
Inline styling
The inline styling works just fine. Similarly to HTML we are free to pass
styles directly via a style attribute. However, while in HTML the value
is a string, in JSX it must be an object.
const inlineStyles = {
color: 'red',
fontSize: '10px',
marginTop: '2em',
'border-top': 'solid 1px #000'
};
89
Styling
const theme = {
fontFamily: 'Georgia',
color: 'blue'
};
const paragraphText = {
...theme,
fontSize: '20px'
};
We have some basic styles in theme and with mix them with what is in
paragraphText . Shortly, we are able to use the whole power of
JavaScript to organize our CSS. What matters at the end is that we
generate an object that goes to the style attribute.
CSS modules
CSS modules is building on top of what we said so far. If we don't like
the JavaScript syntax then we may use CSS modules and we will be
able to write plain CSS. Usually this library plays its role at bundling
time. It is possible to hook it as part of the transpilation step but normally
is distributed as a build system plugin.
90
Styling
/* style.css */
.title {
color: green;
}
// App.jsx
import styles from "./style.css";
function App() {
return <h1 style={ styles.title }>Hello world</h1>;
}
That is not possible by default but with CSS modules we may import
directly a plain CSS file and use the classes inside.
And when we say plain CSS we don't mean that it is exactly like the
normal CSS. It supports some really helpful composition techniques. For
example:
.title {
composes: mainColor from "./brand-colors.css";
}
Styled-components
Styled-components took another direction. Instead of inlining styles the
library provides a React component. We then use this component to
represent a specific look and feel. For example, we may create a Link
component that has certain styling and use that instead of the <a> tag.
91
Styling
<Link href='http://google.com'>Google</Link>
There is again a mechanism for extending classes. We may still use the
Link component but change the text color like so:
<AnotherLink href='http://facebook.com'>Facebook</AnotherLink>
Final thoughts
There are multiple ways to style your React application. I experienced all
of them in production and I would say that there is no right or wrong. As
most of the stuff in JavaScript today you have to pick the one that fits
better in your context.
92
Integration of third-party libraries
Third-party integration
React is probably one of the best choices for building UI. Good design,
support and community. However, there are cases where we want to
use an external service or we want to integrate something completely
different. We all know that React works heavily with the actual DOM and
basically controls what's rendered on the screen. That's why integrating
of third-party components may be tricky. In this section we will see how
to mix React and jQuery's UI plugin and do it safely.
The example
I picked tag-it jQuery plugin for my example. It transforms an unordered
list to input field for managing tags:
<ul>
<li>JavaScript</li>
<li>CSS</li>
</ul>
to:
93
Integration of third-party libraries
Now, let's create a simple React app that will use the plugin:
// Tags.jsx
class Tags extends React.Component {
componentDidMount() {
// initialize tagit
$(this.refs.list).tagit();
}
render() {
return (
<ul ref="list">
{
this.props.tags.map(
(tag, i) => <li key={ i }>{ tag } </li>
)
}
</ul>
);
}
};
// App.jsx
class App extends React.Component {
constructor(props) {
super(props);
The entry point is our App class. It uses the Tags component that
displays an unordered list based on the passed tags prop. When
React renders the list on the screen we know that we have a <ul> tag
94
Integration of third-party libraries
Force a single-render
The very first thing that we have to do is to force a single-render of the
Tags component. That is because when React adds the elements in
the actual DOM we want to pass the control of them to jQuery. If we skip
this both React and jQuery will work on same DOM elements without
knowing for each other. To achieve a single-render we have to use the
lifecycle method shouldComponentUpdate like so:
By always returning false here we are saying that our component will
never re-render. If defined shouldComponentUpdate is used by React to
understand whether to trigger render or not. That is ideal for our case
because we want to place the markup on the page using React but we
don't want to rely on it after that.
95
Integration of third-party libraries
96
Integration of third-party libraries
To illustrate the whole process we will add an input field to the App
component.
this._addNewTag = this._addNewTag.bind(this);
this.state = {
tags: ['JavaScript', 'CSS' ],
newTag: null
};
}
_addNewTag() {
this.setState({ newTag: this.refs.field.value });
}
render() {
return (
<div>
<p>Add new tag:</p>
<div>
<input type='text' ref='field' />
<button onClick={ this._addNewTag }>Add</button>
</div>
<Tags
tags={ this.state.tags }
newTag={ this.state.newTag } />
</div>
);
}
}
We use the internal state as a data storage for the value of the newly
added field. Every time when we click the button we update the state
and trigger re-rendering of Tags component. However, because of
shouldComponentUpdate we have no any updates on the screen. The
97
Integration of third-party libraries
only one change is that we get a new value of the newTag prop which
may be captured via another lifecycle method -
componentWillReceiveProps :
98
Integration of third-party libraries
</ul>
);
}
};
Final thoughts
Even though React is manipulating the DOM tree we are able to
integrate third-party libraries and services. The available lifecycle
methods give us enough control on the rendering process so they are
the perfect bridge between React and non-React code.
99
React and separation of concerns
In this article we will see how React and its ecosystem has quite good
separation of concerns. We will prove that markup, styles and logic may
live in the same JavaScript land and still be separated.
Styling
React components render to DOM elements. Nothing stops us to use
the good old class attribute and attach a CSS class to the produced
HTML element. The only one difference is that the attribute is called
className instead of class . The rest still works which means that if
we want we may put our styles into external .css files. Following this
approach we are not breaking the separation of concerns principle and
still build a React app using JSX.
// assets/css/styles.css
.pageTitle {
color: pink;
}
// assets/js/app.js
function PageTitle({ text }) {
return <h1 className='pageTitle'>{ text }</h1>;
}
100
React and separation of concerns
This is the pain point. The place where we are mixing CSS and markup
or we may say mixing styling and structure. To solve the issue let's keep
UserCard still responsible for the structure, but extract the styling out
into dedicated components Card and Avatar :
101
React and separation of concerns
So, as we can see, it is all matter of composition. React even makes our
applications more compact because everything is defined as a reusable
components and lives in the same context - JavaScript.
102
React and separation of concerns
really well. The result of such libraries is usually a ready for use
component that encapsulates the styling and renders a specific HTML
tag.
Logic
Very often we write logic inside our React components which is more
then clicking a button and showing a message. The snippet below
demonstrates a component that fetches data from a fake API and
renders users on the screen.
this.state = {
loading: false,
users: null,
error: null
};
}
componentDidMount() {
this.setState({ loading: true }, () => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => this.setState({ users, loading: false }))
.catch(error => this.setState({ error, loading: false }));
});
}
render() {
const { loading, users, error } = this.state;
103
React and separation of concerns
Quite a lot of things are happening isn't it. When first rendered the
component shows nothing - null . Then we get the life-cycle
componentDidMount method fired where we set the loading flag to
true and fire the API request. While the request is in flight we display a
paragraph containing the text "Loading" . In the end, if everything is ok
we turn loading to false and render list of user names. In case of error
we display "Ops, sorry. No data loaded" .
Now I agree that the App component is kind of violating the separation
of concerns. It contains data fetching and data representation. There are
couple of ways to solve this problem but my favorite one is FaCC
(Function as Child Components). Let's write a FetchUsers component
that will take care for the API request.
this.state = {
loading: false,
users: null,
error: null
};
}
componentDidMount() {
this.setState({ loading: true }, () => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => this.setState({ users, loading: false }))
.catch(error => this.setState({ error, loading: false }));
});
}
render() {
const { loading, users, error } = this.state;
104
React and separation of concerns
The very first thing that we notice is that the constructor and
componentDidMount method are just copy-pasted from the App
function App() {
return (
<FetchUsers>
{({ loading, users, error }) => {
if (loading) return <p>Loading</p>;
if (error) return <p>Ops, sorry. No data loaded.</p>;
if (users) return users.map(({ name }) => <p>{name}</p>);
return null;
}}
</FetchUsers>
);
}
At this point our markup is separated from the logic. We still operate with
the same data and as a bonus we have this reusable FetchUsers
Markup
JSX syntax is following the XML/HTML semantics and as such comes
with a huge benefit - composability. My opinion is that React is one level
up over the HTML because it allows us to group complex markup into a
single component. For example we have a <header> with some <h1> ,
<nav> and <p> tags inside. We may easily create a <Header>
component and put all those bits inside. We still keep them together but
now they are easy to move around. Perhaps, there are places where we
write some logic directly into the markup like so:
105
React and separation of concerns
Conclusion
No, I don't think that React is against the separation of concerns. It is all
about design and composition. There are patterns that help us to
compose and logically separate our apps. We can still write well
organized programs with clearly defined responsibilities.
106
Summary
Summary
React became one of the most popular libraries for building UIs. It
comes with a great API which is simple and powerful. The tricky part
though is that React itself is not always enough for building complex
applications. There are concepts that we must know to make it right.
Design patterns that are introduced by the community and work well at
scale. This book teaches most of those patterns in a slightly opinionated
style. I hope you liked it :)
107