Functional React Quick Start Hooks 2nd PDF
Functional React Quick Start Hooks 2nd PDF
Functional React Quick Start Hooks 2nd PDF
Cristian Salcescu
The basic unit for a piece of the user interface is the component. With React
Hooks we can express all the user interface using function components.
This is a book for beginners in React, but not for beginners in JavaScript.
For a better understanding of JavaScript and functional programming
consider reading Discover Functional JavaScript .
Through this book, we are going to build a shopping cart. We will build all
components using functions. Classes will not be used to create components
or any other objects. More than that, the this pseudo-parameter will not be
used at all.
We are going to look at state management with React Hooks and with
external libraries like Redux or MobX .
Source Code
Feedback
Package Manager
NPM Packages
With npm we can install additional packages. These packages are the
application dependencies.
For example, here is the command for installing the Redux package:
npm install --save redux
The package.json file stores all the node packages used in the project.
These packages are the application dependencies. The application can be
shared with other developers without sharing all the node packages.
Installing all the packages defined in the package.json file can be done
using the npm install command.
The easiest way to start with a React application is to use Create React App
.
To do that, run one of the following commands:
npm init react-app appname
IDE
To start the application, first, open the application folder in Visual Studio
Code. Then open the terminal from Terminal→New Terminal and run: npm
start . This launches the development server and opens the React
application in the browser.
Chapter 01: Functional JavaScript
One great thing in JavaScript is functions. Functions are treated as values.
We can simply define a variable, storing a function. Here is an example:
const sum = function(x, y){
return x + y;
}
sum(1, 2);
//3
The same function can be written using the arrow syntax as follows:
const sum = (x, y) => x + y;
We favor declaring functions using the const keyword instead of the let
keyword to prevent its modification.
Pure Functions
A pure function is a computation. It computes the result using only its input.
Given a specific input, it always returns the same output.
A pure function has no side-effects. It does not use variables from outside
its scope that can change. It does not modify the outside environment in any
way.
Pure functions have a big set of advantages. They allow focusing attention
in one place, the function itself without its surrounding environment, and
that makes it easier to read, understand, and debug.
Immutability
An immutable object is an object that cannot be modified after it is
created.
product.quantity = 2;
//Cannot assign to read only property 'quantity'
Object.freeze() does a shallow freeze and as such the nested objects can
be changed. For a deep freeze, we need to recursively freeze each property
of type object. Check the deepFreeze() implementation.
Functional Array
In the functional style, we get rid of the for statement and use the array
methods.
Before going forward, note that primitives, objects, and functions are all
values.
filter
function isOdd(number){
return number % 2 !== 0;
}
A predicate function is a function that takes one input value and returns
true or false based on whether the value satisfies the condition.
Point-free style
map
reduce
The first argument, the reducer, is a function that takes an accumulator and
the current value and computes the new accumulator. reduce() executes the
reducer function for each element in the array.
The second argument is the initial value for the accumulator. If no value is
provided, it sets the first element in the array as the initial value and starts
the loop from the second element.
function sum(total, number){
return total + number;
}
Chaining
Chaining is a technique used to simplify code by appling multiple
methods on an object one after another.
Below is an example where the filter() and map() methods are both
applied to an array:
const numbers = [1, 2, 3];
//["*", "***"]
Closures
Closure is an inner function that has access to variables from outer
function, even after the outer function has executed.
Higher-order functions
A higher-order function is a function that takes another function as an
input, returns a function, or does both.
Currying
Currying is the process of transforming a function with several
parameters into a series of functions each taking a single parameter.
Currying comes in handy when aiming for pure functions. Consider the
next code:
const fruits = ['apple', 'mango', 'orange']
As you can see, startsWith() has two parameters and filter() needs a
callback expecting one parameter.
Then, it can be called providing only one parameter, the text to search for.
const fruits = ['apple', 'mango', 'orange'];
Final Thoughts
Functions are values and thus they can be treated as any other values.
A closure is a function that has access to the variables and parameters of its
outer function, even after the outer function has executed.
Pure functions return a value using only on its input and have no side-
effects.
An immutable value is a value that once created cannot be changed.
Changing immutable values means creating changed copies.
filter() , map() and reduce() are the core methods for working with
collections in a functional style.
Chapter 02: Functional Components
React is a library for building user interfaces. The basic unit in React is the
component. The main idea is to split the page into small, reusable
components and then combine them together to create the whole page.
Functional Components
Functional components are functions returning an HTML-like syntax called
JSX .
function Header() {
return (
<header>
<h1>A Shopping Cart</h1>
</header>
);
}
The Header component can then be used in another component called App .
import React from 'react';
import Header from './Header';
function App() {
return (
<div>
<Header />
</div>
);
}
At this point, we have established a relation between App and Header . App
is the parent of Header .
Props
Functional components can take properties.
function ProductItem(props) {
return (
<div>{props.product.name}</div>
);
}
We can make the code cleaner by using the destructuring assignment syntax
in the parameter list.
function ProductItem({product}) {
return (
<div>{product.name}</div>
);
}
const product = {
id: 1,
name : 'mango'
};
function App() {
return (
<div>
<Header />
<div>
<ProductItem product={product} />
</div>
</div>
);
}
Entry Point
The application has a single entry point. In our case, it is the index.js file.
This is the place where the root component App is rendered. When the root
component is rendered, all the other components are rendered.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />,
document.getElementById('root'));
React elements are immutable, once created they cannot be changed. The
only way to update the UI is by creating a new React element.
The code in the index.js file renders the React element created by the App
functional component into the root DOM node.
ReactDOM.render(<App />,
document.getElementById('root'));
The “real” DOM is made of DOM elements. The virtual DOM is made of
React elements.
The virtual DOM can be inspected using the React Developer Tools
extension.
Final Thoughts
Functional components are functions returning objects describing part o the
user interface.
React makes it possible to split the page into small functions rendering parts
of the user interface and combine these parts to create the whole page.
Transpiling
The JSX code is transpiled to JavaScript. Check the code below:
//JSX
ReactDOM.render(
<header>
<h1>A Shopping Cart</h1>
</header>,
document.getElementById('root')
)
//Transpiled to JavaScript
ReactDOM.render(
React.createElement('header', null,
React.createElement('h1', null, 'A Shopping Cart')
),
document.getElementById('root')
);
Expressions
JSX accepts any valid JavaScript expression inside curly braces.
Here are some examples of expressions:
function Message(){
const message = '1+1=2';
return <div>{message}</div>
}
function Message(){
return <div>1+1={1+1}</div>
}
function Message(){
const sum = (a, b) => a + b;
return <div>1+1={sum(1, 1)}</div>
}
ReactDOM.render(<Message/>, document.querySelector('#root'));
ReactDOM.render(<Valid valid={false}/>,
document.querySelector('#root'));
return resultJSX;
}
ReactDOM.render(<Valid valid={false}/>,
document.querySelector('#root'));
ReactDOM.render(<Message />,
document.querySelector('#root'));
ReactDOM.render(<Message />,
document.querySelector('#root'));
Syntax
Function components can return a single tag. When we want to return
multiple tags we need to wrap them into a new tag.
All tags need to be closed. For example, <br> must be written as <br /> .
In JSX, lower-case tag names are considered to be HTML tags, not React
components.
Attributes
The attributes in JSX use camelCase. For example tabindex becomes
tabIndex , onclick becomes onClick .
In this case, the valid property of the props object gets the value false .
For attributes, we should use quotes to assign string values or curly braces
to assign expressions, but not both.
Final Thoughts
JSX allows using markup directly in JS. In a sense, JSX is a mix of JS and
HTML.
Let’s understand how. First, remember that map() transforms a list of values
into another list of values using a mapping function.
ProductList Component
The ProductItem component creates the interface for a single product.
function ProductList({products}) {
return (
<div>
{products.map(product =>
<ProductItem
key={product.id}
product={product}
/>
)}
</div>
);
}
The same way we use map() to transform a list of values into another list of
values, we can use map() to transform a list of values into a list of React
elements.
Keys are useful only when rendering a list. The key should be kept on the
<ProductItem /> elements in the array and not in the ProductItem itself.
Keys are not passed to components. When we need the same value in the
component we need to pass it in a property with a different name.
Spread Attributes
Spread attributes make it easy to pass all the props to a child component.
<ChildComponent {...props} />
function ProductItem({name}) {
return (
<div>{name}</div>
);
}
Here is how the spread attributes we can be used in ProductList to pass all
properties of a product to the ProductItem component.
import React from 'react';
import ProductItem from './ProductItem';
function ProductList({products}) {
return (
<div>
{products.map(product =>
<ProductItem
key={product.id}
{...product}
/>
)}
</div>
);
}
App Component
The App root component takes a list of products and sends it to the
ProductList component.
function App({products}) {
return (
<div>
<Header />
<div>
<ProductList products={products} />
</div>
</div>
);
}
Entry Point
The entry point file index.js creates an array of products and sends it to
the App component.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
const products = [
{
'id' : 1,
'name' : 'mango',
'price' : 10
},
{
'id' : 2,
'name' : 'apple',
'price': 5
}];
Final Thoughts
List components create the visual interface for a list of values.
It is a good practice to extract out each item from the list in its own
component.
Chapter 05: Communication between
Components
Splitting the page into small components makes things easier to manage and
reuse, but it creates a new challenge, the communication between these
components.
Data as props
We have already seen how data is sent from parent to child using props.
Consider the ProductItem functional component:
function ProductItem({product}) {
return (
<div>
<div>{product.name}</div>
</div>
);
}
Functions as props
We can handle communication from child to parent using functions as
props. Those functions will be used as event handlers.
React events are handled similar to DOM events. The attributes for React
events are named using camelCase. onClick , onChange , onSubmit are
examples of attributes for event handling. The callback functions for
handling events are passed inside curly braces.
When the Add button is clicked, we need to execute the callback function
with the current product as an argument.
<button onClick={() => onAddClick(product)} />
The parent component should pass a callback function to handle the event.
Let’s see how.
We define the callback function in the root component. App handles the
onAddClick event by writing the product to console.
function App({products}) {
function addToCart(product) {
console.log(product);
}
return (
<div>
<Header />
<div>
<ProductList
products={products}
onAddClick={addToCart} />
</div>
</div>
);
}
With the partial() utility function, from lodash from example, we can
create an event handler with the product argument already applied.
import partial from 'lodash/partial';
In the next chapters, I will continue to use anonymous functions to wrap the
call with additional arguments.
Data Flow
At this point, let’s analyze the data flow between components.
The entry point creates the products and passes them to App . From there,
App sends them to ProductList . Then ProductList passes each product
to the ProductItem components.
Data flow with plain objects as props: Entry point → App → ProductList
→ ProductItem .
When the Add button is clicked in ProductItem , it calls the onAddClick()
provided by ProductList , which calls the onAddClick() callback provided
by App . In a sense, data travels from children to parents using callbacks.
Final Thoughts
The communication between components can be done with props.
ShoppingItem Component
ShoppingItem gets a product and creates the JSX markup for one item in
the shopping cart.
ShoppingCart Component
ShoppingCart gets a cart object and creates the JSX markup for it.
Props
Presentation components communicate only throw their own props. They
take two kinds of inputs, plain data objects, and function callbacks.
The plain objects are the data to be transformed into a user interface.
Final Thoughts
Presentation components transform data into a visual interface.
Block vs inline
Block-level elements include tags like div , header , footer . They act as
containers for other inline or block elements. A block element starts on a
new line and extends to the width of its parent. It can be sized using width
and height .
Inline elements include tags like span , input , select . They have the
width and height of their content. Inline elements are displayed on the
same line unless there is not enough space on that line. Inline elements can’t
be sized using width and height .
The normal document flow is the default way for displaying elements on a
page. Block elements are put on new rows from top to bottom and inline
elements are displayed one after the other, from left to right.
At the core of the page layout is the box model. Any visible HTML element
is considered to be in a rectangular box defined by content (width , height
), padding , border , and margin .
Selectors
CSS Syntax
A CSS rule consists of a selector and a set of CSS properties. Each property
has a name and a value.
The next rule selects the header tag and sets the text-align property to
center .
header {
text-align: center;
}
Flex Layout
display: flex defines the flex container. By default, all its direct children
are displayed from left to right on a single line.
The flex-direction property defines the direction in which flex items are
placed in the flex container. The row value displays the flex items
horizontally, from left to right and the column value displays the flex items
vertically, from top to bottom.
Styling Components
We will define a CSS file specific for each component. The CSS file will
have the same name as the JSX file.
ProductItem CSS
The .product-list-item class defines the style for each product item.
.product-list-item {
padding: 10px;
margin: 10px;
background-color: #CC6500;
color: #FFF;
}
Create React App uses Webpack for handling all assets, including CSS
files. Webpack extends the concept of import beyond JavaScript. To use a
CSS file inside a component, we need to import it.
ShoppingItem CSS
The .shopping-cart-item class defines the style for each shopping item.
.shopping-cart-item {
padding: 10px;
margin: 10px;
color: #FFF;
background-color: #FF9C00;
display: flex;
flex-direction: column;
}
.shopping-cart-item div {
margin: 5px;
}
ShoppingCart CSS
The .shopping-cart-total class defines the style for the total price.
.shopping-cart-total {
padding: 10px;
margin: 10px;
color: #654321;
border: 1px solid #FF9C00;
}
App CSS
The App.css defines the page layout and the default look for elements
common to all components, like buttons.
button {
background: #FAF1DD;
border: 1px solid #FAF1DD;
color: #654321;
padding: 5px;
cursor: pointer;
}
header {
text-align: center;
}
//layout
.content {
display: flex;
width: 70%;
margin: 0 auto;
}
Final Thoughts
CSS defines the look and feel of an HTML page.
In normal document flow block elements are displayed on new lines from
top to bottom and inline elements are displayed on the same line from left
to right.
State
State is data that is stored and can be changed.
React Hooks enables to create functional components that store state. React
Hooks is a collection of hook functions. They are easy to recognize as they
must start with the word "use" .
Hook function calls should be done at the top of the functional component.
The first hook we are going to look at is the useState() function, that can
be used to add local state to a functional component.
import React, { useState } from 'react';
function App({products}) {
const [shoppingMap, setShoppingMap] = useState({});
//...
}
The App component stores a map with all products added to the shopping
cart and their quantity.
import './App.css';
function App({products}) {
const [shoppingMap, setShoppingMap] = useState({});
function addToCart(product) {
setShoppingMap(map => addProductToMap(product, map));
}
function removeFromCart(product) {
setShoppingMap(map => removeProductFromMap(product, map));
}
return (
<div>
<Header />
<div className="content">
<ProductList
products={products}
onAddClick={addToCart} />
<ShoppingCart
cart={toCartView(shoppingMap)}
onRemoveClick={removeFromCart} />
</div>
</div>
);
}
addToCart() handles the Add click by adding the new product to the map.
Pure Functions
We are going to do all state changes using pure functions.
addProductToMap() takes a product and a map and returns the new map
with the product quantity updated.
function addProductToMap(product, map){
const newMap = { ...map };
const quantity = getProductQuantity(product, map) + 1;
newMap[product.id] = { ...product, quantity };
return Object.freeze(newMap);
}
The new product added to the map has the quantity updated. If the product
does not exist it will be added with quantity one, otherwise, its current
quantity will be incremented by one.
Pure functions don’t modify their input values. For this reason, the map is
first cloned. The spread operator is used to create a shallow copy of the
input map, { ...map } .
toCartView() converts a map to a list of products plus the total price. The
total price is computed using the addPrice() reducer function.
function toCartView(map) {
const list = Object.values(map);
return Object.freeze({
list,
total: list.reduce(addPrice, 0)
});
}
At this point, all the logic managing the state is inside the App root
component. All the other components are presentation components taking
data as props.
Final Thoughts
Data can be available in a functional component either from props or from
its internal state.
State transformations can be done with pure functions taking the previous
state as a parameter and returning the new state.
Chapter 09: Custom Store
Next, we are going to explore what it means to extract out the state and the
state management logic in a separate object. We will create a new store
object responsible for managing the shopping cart data.
Store
A store is an object whose main purpose is to store and manage data.
The store emits events every time its state changes. Components can
subscribe to these events and update the user interface.
Event Emitter
The store object emits events. For this task, we will use a micro event
emitter.
npm install micro-emitter --save
//emit event
eventEmitter.emit(CHANGE_EVENT);
The ShoppingCartStore will store and manage the current shopping cart. It
is the single source of truth regarding the shopping cart.
//pure functions
//...
function ShoppingCartStore() {
const eventEmitter = new MicroEmitter();
const CHANGE_EVENT = "change";
function addToCart(product) {
shoppingMap = addProductToMap(product, shoppingMap);
eventEmitter.emit(CHANGE_EVENT);
}
function removeFromCart(product) {
shoppingMap = removeProductFromMap(product, shoppingMap);
eventEmitter.emit(CHANGE_EVENT);
}
function onChange(handler) {
eventEmitter.on(CHANGE_EVENT, handler);
}
function offChange() {
eventEmitter.off(CHANGE_EVENT);
}
function get() {
return toCartView(shoppingMap);
}
return Object.freeze({
addToCart,
removeFromCart,
get,
onChange,
offChange
});
}
Entry Point
The store needs to be created and sent to components.
index.js is the application entry point. This is the place where the store is
created. It is then sent to the App component using props.
import ShoppingCartStore from './ShoppingCartStore';
ReactDOM.render(<App products={products}
shoppingCartStore={shoppingCartStore} />,
document.getElementById('root'));
When the store emits a change event, the component changes the local state
and the UI is re-rendered.
import React, {useState, useEffect} from 'react';
import './App.css';
useEffect(subscribeToStore, []);
function subscribeToStore() {
shoppingCartStore.onChange(reload);
function reload() {
const cart = shoppingCartStore.get();
setCart(cart);
}
return (
<div>
<Header />
<div className="content">
<ProductList
products={products}
onAddClick={shoppingCartStore.addToCart} />
<ShoppingCart
cart={cart}
onRemoveClick={shoppingCartStore.removeFromCart} />
</div>
</div>
);
}
Effect Hook
The effect hook performs side-effects in function components. Any
communication with the outside environment is a side-effect.
The “effect” is a function that runs after React performs the DOM updates.
The effect function has access to the state variables.
The following code runs the effect only once. It subscribes to store events
only once.
useEffect(loadAndSubscribe, []);
function loadAndSubscribe() {
shoppingCartStore.onChange(reload);
}
When effects run more than once, React cleans up the effects from the
previous render before running the next effects.
useEffect(subscribeToStore, []);
function subscribeToStore() {
shoppingCartStore.onChange(reload);
return function cleanup(){
shoppingCartStore.offChange();
};
}
Final Thoughts
Stores offer a way to encapsulate state and share the state management
behavior between components.
The custom store object emits events when its state changes.
Store
With MobX we can create the state in ShoppingCartStore as an observable
map.
import { observable, action } from 'mobx';
//pure functions
//...
function toCartView() {
return toCartViewFromMap(shoppingMap);
}
//actions
const addToCart = action(function(product){
shoppingMap.set(product.id,
incrementProductQuantity(shoppingMap, product));
});
return Object.freeze({
addToCart,
removeFromCart,
toCartView
});
}
Once the state is observable, we can turn components into observers and
react to changes by re-rendering.
In MobX actions are functions that modify the state. Action functions
should be marked using the action() decorator. This will make sure that
intermediate changes are not visible until the action has finished.
Container Components
Presentation components don’t communicate with the outside environment
using a publish-subscribe pattern. The ShoppingCartStore cannot be used
in presentation components.
We want to keep presentation components as pure functions and get all the
benefits of purity. We can use container components to connect stores to
presentation components.
ShoppingCart Container
Once the store is observable we can make the ShoppingCart component an
observer. It means that when the state changes inside the store, the
ShoppingCart component will re-render.
observer() takes a component an creates a new one that will re-render
when state changes. Components become reactive.
import { inject, observer } from 'mobx-react';
Root Component
The App root component becomes very simple.
import React from 'react';
import './App.css';
function App({products}) {
return (
<div>
<Header />
<div className="content">
<ProductList products={products} />
<ShoppingCart />
</div>
</div>
);
}
Entry Point
The index.js is the application single entry point. Here is where the store
is created.
We can send the store down to components using props , but a simpler way
is to use the React Context . React Context can be used to share data that is
considered to be “global”.
const products = [
//...
];
Final Thoughts
MobX allows to defined observable state.
Store
In Redux there is a single store that manages all the application state-tree.
The state inside the store can be changed only by dispatching actions. There
are no state setters on the store object.
Actions
Actions are plain data objects containing all the necessary information to
make an action.
Let’s imagine what are the necessary information for adding a product to
cart. First, the application needs to know that it is an 'ADD_TO_CART' action,
second, it needs to know the product to be added to the cart. Here is how
the 'ADD_TO_CART' action may look like:
{
type: 'ADD_TO_CART',
product
}
The action object needs the type property indicating the action to perform.
type is usually a string.
Action Creators
A common practice is to encapsulate the code that creates the plain action
objects in functions. These functions are pure functions called action
creators.
function addToCart(product) {
return {
type: 'ADD_TO_CART',
product
};
}
function removeFromCart(product) {
return {
type: 'REMOVE_FROM_CART',
product
};
}
Reducers
The Redux store manages state using pure functions called reducers. These
functions take the previous state and an action as parameters and return the
new state.
The state is initialized with an empty object that will be used as a map.
Reducers are used to change the state, not to get the state from the store.
Root Reducer
Redux requires one root reducer. We can create many reducers managing
parts of the root state and then combine them together with the
combineReducers() utility function and create the root reducer.
Here is how the data flow with the Redux store looks like:
Selectors
Selectors are functions that take the state as input and return a part of that
state or compute derived values from it. Selectors are pure functions.
The toCartView() selector extracts the shoppingCart map from the state
and returns a list of all products and the total price.
function toCartView({shoppingCart}) {
const list = Object.values(shoppingCart);
return Object.freeze({
list,
total: list.reduce(addPrice, 0)
});
}
export { toCartView };
Entry Point
We create the store in the application entry point, the index.js file.
We can use the Provider component from the react-redux to send the
store down to components. To send the store to all components we wrap the
root component inside the Provider . The store is then available to
components using the connect() function.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const products = [
//...
];
ReactDOM.render(<Provider store={store}>
<App products={products} /></Provider>,
document.getElementById('root'));
Presentation Components
We will use the same presentation components.
Container Components
The ProductList is a presentation component that requires the list of
products in order to render the user interface.
We need to take the data from the store, process it, and send it to the
ProductList component. This is the role of the container component.
For this, we will use the connect() utility from the react-redux package.
It takes the state from the store, processes it with a selector, and then
sends the result to the presentation component as props. The
mapStateToProps() does that.
It defines the actions to be dispatched when events are triggered by
user interaction. The mapDispatchToProps() does that.
ProductList Container
function mapDispatchToProps(dispatch) {
return {
onAddClick: function(product){
dispatch(addToCart(product));
}
};
}
ShoppingCart Container
function mapStateToProps(state) {
return {
cart: toCartView(state)
};
}
function mapDispatchToProps(dispatch) {
return {
onRemoveClick: function(product){
dispatch(removeFromCart(product));
}
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ShoppingCart);
Data Flow
Let’s look at the data flow for reads and writes.
Final Thoughts
The Redux store manages state using pure functions.
A reducer is a pure function that takes the state and an action and returns
the new state.
A selector is a function that takes the state as input and extracts or computes
new data from it.
Chapter 12: Redux Actions and Immutable.Js
In this chapter, we are going refactor the action creation and handling using
helpers and then introduce immutable data structures.
Action Creators
Action creators tend to become repetitive in nature and can be easily
created with a helper library like the one from Redux Actions .
import { createAction } from 'redux-actions';
You can imagine the addToCart() action creator defined with the helper as
similar to the function below:
function addToCart(payload){
return {
type: 'ADD_TO_CART',
payload
}
}
As you see, all the additional data the action creator receives stays in the
payload property of the action object.
Handling Actions in Reducer
Let’s look again at the reducer function handling several actions.
export default function (shoppingMap = {}, action) {
switch (action.type) {
case 'ADD_TO_CART':
return addToCart(shoppingMap, action.product);
case 'REMOVE_FROM_CART':
return removeFromCart(shoppingMap, action.product);
default:
return shoppingMap;
}
}
Functions should be small and do one thing. The switch statement leads to
large code doing multiple things. I think that using a map is a better option
for handling actions in a reducer.
The handleActions() utility function from the Redux Actions can be used
to create a new function that handles several actions by calling a specific
updater function. It does all this using a map. Below is how it looks like:
import { handleActions } from 'redux-actions';
import actions from '../actions/ShoppingCartActions';
Immutable.js provides immutable data structures like Map and List . Let’s
first install the library.
npm install immutable --save
Till now, we have used the object literal as a map. In order to create new
value when changing the map, we used the spread syntax to make a copy of
the map.
Next, we are going to use the Map data structures from Immutable.js .
set(key, value) returns a new Map containing the new key-value pair.
A sequence returns the next value from the collection each time it is
called. It efficiently chains methods like map() and filter() by not
creating intermediate collections
Final Thoughts
Action creating can be simplified with helpers.
The mapping between actions and small updater functions can be also
simplified with a helper.
Then create a simple JSON file with a list of products in a .json file. Here
is the products.json file:
{
"fruits":[
{
"id":1,
"name":"mango",
"price":10
},
{
"id":2,
"name":"apple",
"price":5
}
]
}
API Utils
It is better to separate responsibilities and encapsulate the network calls in
their own files. Functions doing network calls are impure.
function toJson(response){
return response.json();
}
function fetchProducts(){
return fetch(`${baseUrl}/fruits/`)
.then(toJson)
}
The fetch() built-in function can be used to fetch resources across the
network. fetch() is an asynchronous function that returns a promise
containing the response.
The json() method extracts the JSON object from the response.
Product Store
MobX offers an object-oriented way of working with the state.
The store is responsible for managing state but also to keep it in sync with
the server.
function ProductStore(api){
const products = observable([]);
function resetProducts(newProducts){
products.replace(newProducts);
}
function getProducts(){
return products.toJS();
}
return Object.freeze({
getProducts,
fetchProducts
});
}
ProductsList Container
The container component turns the ProductList component into an
observer. When products change in the store, the ProductList component
re-renders.
import { inject, observer } from 'mobx-react';
Layers
This approach leads to a layered architecture:
The view layer is responsible for displaying data on the page, and for
handling user interactions. The view layer is made up of components.
The business layer is made of stores.
The API access layer contains the objects communicating with the
REST API.
The layered architecture offers a better separation of concerns.
Entry Point
The entry point index.js creates the two stores passing the api object as a
dependency, and then sends all stores to components using the Provider .
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
const stores = {
productStore,
shoppingStore
};
productStore.fetchProducts();
Final Thoughts
It is a good practice to encapsulate network calls in their own files.
MobX takes an object-oriented approach to work with the state. Stores are
responsible for managing the state and keep it in sync with the server.
To make things flexible and easier test we separate use from construction.
For this reason, we can create all objects in the entry point file.
Chapter 14: Fetching with Redux Thunk
So far, all the functions written for the Redux store were pure functions.
There are situations when we need to write impure core like when doing
network calls.
A middleware enables us to write async logic that interacts with the store.
Redux Thunk
Thunks are the recommended middleware for side-effects logic like
network requests.
The Redux Thunk middleware allows you to write action creators that
return a function instead of a plain action object.
Now, we intend to make a network request and get all products from the
REST API and display them on the page.
In order to update the state, we need an action for resetting all products with
the new ones. Here is the action creator for building it:
import { createAction } from 'redux-actions';
Here is how we are going to do it. First, we make the network call to take
the products and then we update the store by dispatching an action with the
new products. We can do this orchestration logic using an asynchronous
action creator.
These asynchronous action creators built with Redux Thunk are functions
that return other functions. The returned functions are called “thunks”.
function fetchProducts() {
return function(dispatch) {
return api.fetchProducts()
.then(actions.resetProducts)
.then(dispatch);
};
}
export default { fetchProducts };
The inner function receives the store methods dispatch and getState as
parameters.
Reducers
I order to update the state we need a new reducer that updates the previous
products with the new products.
import { handleActions } from 'redux-actions';
import { List } from 'immutable';
import actions from '../actions/productsActions';
The list of products is stored as an immutable list using the List data
structure.
Root Reducer
The root reducer should split the state management between the
shoppingCart and products reducers, each with its own state.
Entry Point
In the application entry point, when creating the store we need to enable the
middleware library that intercepts the dispatch and runs the asynchronous
actions.
const store = createStore(rootReducer, applyMiddleware(thunk));
Data Flow
After adding the asynchronous actions, the data flow changes.
Final Thoughts
The Redux Thunk middleware enables two kinds of actions in an
application, plain data actions and function actions. Dispatching a plain data
action changes the store. Dispatching a function action runs the logic inside
the function doing the asynchronous tasks and dispatching the plain
object(s).
observable.subscribe(console.log);
//1
//2
//3 after 1 second
Operators
The operators that can be applied on an observable are inspired by the
functional array methods like filter() , map() , or reduce() .
For example, to transform the emitted values from an observable source, we
can use the map() operator.
Here is an example using the filter() operator to select only the even
numbers from the observable stream of data.
import { of } from 'rxjs';
import { filter } from 'rxjs/operators';
observable.pipe(
filter(isEven)
)
.subscribe(console.log);
//2
//4
//6
function isEven(n){
return n % 2 == 0;
}
API
The easiest way to create an observable is through built-in functions. The
ajax.getJSON() function makes a network call and returns an observable.
This observable emits the response object returned from the request.
import { ajax } from 'rxjs/ajax';
function fetchProducts(){
return ajax.getJSON(`${baseUrl}/fruits/`)
}
Actions
Our aim is to retrieve the products from the REST API and update the store.
For this, we are going to need two actions.
import { createAction } from 'redux-actions';
Epics
An epic is a core primitive of Redux Observable. It is a function that takes
an observable stream of actions and returns a new observable stream of
actions.
The epic for retrieving the products first filters out all the actions received
and handles only the fetchProducts actions. Then for these actions, it
makes an API calls using the api.fetchProducts() . The API call returns
an observable whose data is then transformed into a new observable
emitting the resetProducts action.
import api from '../api/productsAPI';
import actions from '../actions/productsActions';
import { ofType } from 'redux-observable';
import { map, mergeMap } from 'rxjs/operators';
function fetchProducts(action$){
return action$.pipe(
ofType(actions.fetchProducts),
mergeMap(action$ =>
api.fetchProducts().pipe(
map(actions.resetProducts)
)
)
);
}
Entry Point
In the application entry point when creating the store we need to define the
epicMiddleware using the createEpicMiddleware() utility.
The epicMiddleware needs one root epic. The root epic, containing all the
other epics, is created using the combineEpics() utility function.
import { combineEpics, createEpicMiddleware } from 'redux-
observable';
import rootReducer from './reducers';
import actions from './actions/productsActions';
import epics from './async/productsEpics';
epicMiddleware.run(rootEpic);
store.dispatch(actions.fetchProducts());
Final Thoughts
An observable represents a stream or source of data that can arrive over
time.
Form elements like input , textarea and select usually have their own
state that is updated based on user input.
An input element with both the value and the onChange properties
controlled by React is a controlled component. The value property displays
the state. The onChange event receives a handler to modify the state.
input , textarea and select all have the value and the onChange
properties. The checkbox is different as the value is represented by the
checked property.
From Component
We are going to build the ProductSearch form component. It lets the user
write the search criteria and builds the search query object.
import React, { useState } from 'react';
import './ProductSearch.css';
function submitSearch(e){
e.preventDefault();
onSearch({text});
}
return (
<form
className="search-form"
onSubmit={submitSearch}>
<input
value={text}
onChange={e=> setText(e.target.value)}
placeholder="Product"
type="text"
name="text"
/>
<button
type="search"
className = "search-button">
Search
</button>
</form>
);
}
Let’s analyze the code. First, the state for the search textbox input is defined
using the useState() hook.
const [text, setText] = useState('');
The state is then displayed in the textbox using the value property.
<input value={text} />
Input changes are captured with the onChange event. onChange fires on each
keystroke, not only on lost focus. In the event object, the
event.target.value property gives access to the current input value.
<input
value={text}
onChange={e=> setText(e.target.value)} />
Form submission can be handled with the onSubmit event on the form
element.
<form onSubmit={submitSearch}>
The default behavior of a form with a search button is to submit the form
and reload the page. We don’t want to reload the page. For this, we are
going to call the event preventDefault() method in the event handler.
function submitSearch(e){
e.preventDefault();
onSearch({text});
}
submitSearch() builds the search query using the text variable storing the
current input textbox value.
Custom Hook
Custom hooks allow us to extract out component logic into reusable
functions.
A custom hook is a function whose name starts with "use" and that may
call other hooks. Each hook use has its own state.
function useInputState(initialValue){
const [value, setValue] = useState(initialValue);
function setValueFromEvent(e){
setValue(e.target.value);
}
export { useInputState };
//...
<input
value={text}
onChange={setText}
placeholder="Product"
type="text"
name="text" />
//...
}
App
In the App root component, we have access to the query object submitted by
the ProductSearch component.
import React from 'react';
import ProductSearch from './ProductSearch';
//...
function App({products}) {
function filterProducts(query){
console.log(query);
}
//...
<ProductSearch
onSearch={filterProducts} />
//...
}
An input form element with the value and onChange properties controlled
by React is a controlled component.
UI Store
The UIStore will manage the state related to the user interface. It is a
simple store in the sense it just updates the state and triggers reactions.
In our case, the UIStore keeps the query criteria. The query is not
something we want to save on the server, it is just the information we need
for filtering the products.
import { observable, action } from 'mobx';
function UIStore(){
const state = observable({
query : {
text : ''
}
});
function getQuery(){
return state.query;
}
return Object.freeze({
getQuery,
setQuery
});
}
ProductSearch Container
Once the UIStore is created, we need to make it available to the
ProductSearch component. A new container component makes this
connection. It uses uiStore.setQuery() to handle the onSearch event.
import { inject } from 'mobx-react';
ProductList Container
The ProductListContainer has already transformed ProductList into an
observer. We just need to make it filter the products by the query criteria.
This is accomplished by the filterProducts() pure function.
import { inject, observer } from 'mobx-react';
function isInQuery(query){
return function(product){
return product.name.includes(query.text);
};
}
Entry Point
In index.js all stores, including uiStore , are created and then passed to
components.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
const stores = {
productStore,
shoppingStore,
uiStore
};
ReactDOM.render(<Provider {...stores}>
<App />
</Provider>,
document.getElementById('root'));
productStore.fetchProducts();
Final Thoughts
Stores keep data from the backend and data related to the user interface.
The UI Store is a simple store. When state changes in the UI Store, the
components using it will re-render.
Chapter 18: UI State in Redux Store
Let’s implement the search product functionality with Redux.
Searching by a query requires to actually store the query itself. The query
in this case, is UI state.
Actions
Let’s start by defining the action we need for storing the query . Here is the
action creator for it:
import { createAction } from 'redux-actions';
Query Reducer
To store the query we need to define a new reducer managing this state.
import { handleActions } from 'redux-actions';
import actions from '../actions/queryActions';
ProductSearch Container
The ProductSearch component doesn’t change, it remains the same. What
does change is the way it is connected to the store.
We will create a new container component that updates the store on the
onSearch event.
function mapStateToProps(state) {
return {
query: state.search
};
}
function mapDispatchToProps(dispatch) {
return {
onSearch: function(query){
dispatch(actions.setQuery(query));
}
};
}
Selector
The filterProducts() selector extracts the products and the current
search query from the state and returns a new filtered list of products.
function filterProducts({products, query}){
return products.filter(isInQuery(query));
}
function isInQuery(query){
return function(product){
return product.name.includes(query.text);
};
}
ProductList Container
The ProductList container reads the filtered list of products from the store
using the filterProducts() selector.
import { connect } from 'react-redux';
import actions from '../actions/shoppingCartActions';
import selectors from '../selectors/productsSelectors';
function mapStateToProps(state) {
return {
products: selectors.filterProducts(state)
};
}
function mapDispatchToProps(dispatch) {
return {
onAddClick: function(product){
dispatch(actions.addToCart(product));
}
};
}
Final Thoughts
UI state can be stored and changed in the Redux store the same way other
data is stored and changed.
Chapter 19: Testing
Unit tests can identify bugs and confirm that functions work as expected.
We are going to make a few tests using the Jest testing framework. Jest is
already installed in applications created with Create React App . Tests can
be defined in files named with the .test.js suffix.
//act
const resetProductsAction =
actions.resetProducts(newProducts);
const result = productsReducer(products, resetProductsAction);
//assert
expect(Array.from(result)).toEqual(newProducts);
});
We assert our expectations using the expect() function. expect() returns
an expectation object.
The toEqual() method can be called on the expectation object to test the
value for equality. toEqual() recursively checks every field of an object or
array.
The following tests verify that the shoppingCart reducer can add products
to cart, increment the quantity of an existing product, and remove a product
from the cart.
import { Map } from 'immutable';
import actions from '../actions/shoppingCartActions';
import selectors from '../selectors/shoppingCartSelectors';
import shoppingCartReducer from './shoppingCart';
//act
const addToCartAction =
actions.addToCart({id:1, title: 'apple', price: 10});
const shoppingCart =
shoppingCartReducer(cartMap, addToCartAction);
//assert
const cart = selectors.toCartView({shoppingCart});
expect(cart.list.count()).toEqual(1);
});
//act
const addToCartAction =
actions.addToCart({id:1, title: 'apple', price: 10});
const shoppingCart =
shoppingCartReducer(cartMap, addToCartAction);
//assert
const cart = selectors.toCartView({shoppingCart});
expect(cart.list.count()).toEqual(2);
expect(cart.list.first().quantity).toEqual(2);
});
//act
const removeFromCartAction =
actions.removeFromCart({id:1, title: 'apple', price: 10});
const shoppingCart =
shoppingCartReducer(cartMap, removeFromCartAction);
//assert
const cart = selectors.toCartView({shoppingCart});
expect(cart.list.count()).toEqual(1);
expect(cart.list.first().id).toEqual(2);
});
We are using immutable data structures to store data in the store. The
cart.list , in this case, is a sequence .
The query UI reducer should be able to reset the query when the setQuery
action is dispatched.
import actions from '../actions/queryActions';
import queryReducer from './query';
//act
const queryAction = actions.setQuery(newQuery);
const result = queryReducer(query, queryAction);
//assert
expect(result).toEqual(newQuery);
});
Testing Selectors
The following test verifies that the toCartView() selector is able to
transform the map of products into a cart object with all products and the
total price.
import { Map } from 'immutable';
import selectors from './shoppingCartSelectors';
//act
const cart = selectors.toCartView({shoppingCart});
//assert
expect(cart.total).toEqual(15);
});
Remember that we are using the Map immutable data structure to store the
quantity in the cart for each product. The product id is the key in the map
and the product itself with its quantity is the value in the map.
Next, we are going to check that the filterProducts() selector can filter
the products by the query criteria.
import { List } from 'immutable';
import selectors from './productsSelectors';
//act
const result = selectors.filterProducts({products, query});
//assert
expect(result).toEqual(List([
{id:1, name: 'apple', price: 10}
]));
});
Products are kept in the store using the List immutable data structure.
//act
store.addToCart({id:1, title: 'apple', price: 10});
store.addToCart({id:2, title: 'mango', price: 5});
//assert
const cart = store.toCartView();
expect(cart.list.length).toEqual(2);
});
//act
store.addToCart({id:1, title: 'apple', price: 10});
//assert
const cart = store.toCartView();
expect(cart.list.length).toEqual(2);
expect(cart.list[0].quantity).toEqual(2);
});
Testing a store with a dependency requires to create a fake object and pass it
as the dependency.
import ProductStore from './ProductStore';
//act
return store.fetchProducts(newProducts)
.then(function assert(){
//assert
const products = store.getProducts();
expect(products.length).toEqual(2);
});
});
productStore.fetchProducts();
Final Thoughts
Selectors and reducers are pure functions and we can create tests verifying
the expected output for specific input.
A simple test rendering the root component can check if the application can
start without errors.
Chapter 20: Redux Folder Structure
In this chapter, we are going to analyze and restructure the current folder
organization. This will be a good way to understand the pros and cons of
different options.
Next, inside the folder for a specific type, we will find the files for specific
features. Here are the files in the "actions" folder:
|--productsActions.js
|--queryActions.js
|--shoppingCartActions.js
Whenever we need to add a new feature, we add a new directory. This fits
better in applications with several features. One possible weakness of this
structure is the lack of certainty about the feature to which a file is linked.
Until now we have separated reducers, selectors, actions in their own file,
but we can take a more evolutionary approach. That means we start out
with a single file containing all actions, the reducer, and all sectors for a
feature, and then, when it grows, we extract them out in their own files.
The same approach can be used for presentation and container components.
We can put the code in the same file and then extract out the container logic
in its own file when it grows.
We can follow the same idea and keep the state management in one reducer
file at start and then when it grows, we extract the code out in several files
under the "reducers" sub-folder of the feature.
Final Thoughts
Type-first and feature-first are two options available for organizing files
into directories.
We can take a more evolutionary approach and extract out the code from a
file into several others when it grows.
Chapter 21: Router
All the functionality implemented until now happened on one page, the
products page. In this chapter, we are going to introduce a new page, the
product details page.
Product Component
The ProductDetails functional component creates the visual interface
displaying all the product details. In addition to the name and price , our
products may have the nutrients property.
function mapDispatch(dispatch) {
return {
onAddClick(product){
dispatch(actions.addToCart(product));
}
};
}
State
The Redux flow is a data-driven one. In order to display the current
product, first, we need this information in the store.
Actions
To save the current product in the store, we need a new action.
import { createAction } from 'redux-actions';
Reducer
A new reducer is required to manage the current product state. When
setCurrentProduct is dispatched, it will update the current product with
the new one.
import { handleActions } from 'redux-actions';
import actions from '../actions'
API Utils
A new API utility function is needed to fetch all product details by id.
const baseUrl = 'http://localhost:3000';
function toJson(response){
return response.json();
}
function fetchProduct(id){
return fetch(`${baseUrl}/fruits/${id}`)
.then(toJson)
}
Thunks
The fetchProduct thunk uses the api.fetchProduct() utility to get the
product details from the REST API, and then dispatches the
setCurrentProduct action containing the product information.
function fetchProduct(id) {
return function(dispatch) {
return api.fetchProduct(id)
.then(actions.setCurrentProduct)
.then(dispatch);
};
}
Root Reducer
The root reducer must be updated with the new reducer managing the
current product.
import { combineReducers } from 'redux';
import shoppingCart from './shopping-cart/reducer';
import products from './products/reducers/productsReducer';
import query from './products/reducers/queryReducer';
import currentProduct from
'./products/reducers/currentProductReducer';
Router
Navigating between several pages requires routing. We are going to use the
React Router library to define the routes to these pages and create the links
pointing to them.
npm install --save react-router-dom
Pages
We should at this point make a difference between pages and other reusable
components. The page until now was the root App functional component.
Products Page
We are going to create the products page from the previous App component
and do a few changes.
We are not loading the products when the application starts but when the
products page is loaded. For this, we need to dispatch the fetchProducts
thunk when the page is loaded, using the useEffect() hook.
import React, {useEffect} from 'react';
function Products({onLoad}) {
useEffect(() => {
onLoad();
},[]);
return (
<div className="content">
<div>
<ProductSearch />
<ProductList />
</div>
<ShoppingCart />
</div>
);
}
function mapDispatch(dispatch) {
return {
onLoad(){
dispatch(thunks.fetchProducts());
}
};
}
import './App.css';
useEffect(() => {
onLoad(id);
},[id]);
return (
<div className="content">
<div>
{ currentProduct &&
<ProductDetails product={currentProduct} /> }
</div>
<ShoppingCart />
</div>
)
}
The container component reads the current id from the route using the
match.params property and extracts the currentProduct from the store. It
dispatches the fetchProduct thunk on the onLoad event.
import { connect } from 'react-redux';
import thunks from './products/thunks'
import Product from "./Product";
return {
id,
currentProduct
}
}
function mapDispatch(dispatch) {
return {
onLoad(id){
dispatch(thunks.fetchProduct(id));
}
};
}
Header
Once we navigate to the details page, we need a way to get back. We can
add a link the the Hearder component pointing to the products page.
import './Header.css'
function Header() {
return (
<header>
<h1>A Shopping Cart</h1>
<nav className="content">
<Link to="/">Products</Link>
</nav>
</header>
);
}
export default Header;
App Router
The AppRouter component defines all the routes using the Route
component.
The path attribute declares the path used in the URL and the component
attribute defines the component to be rendered when the route matches the
URL.
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-
dom';
function AppRouter(){
return (
<Router>
<Header />
<Route
exact path="/"
component={Products} />
<Route
path="/products/:id/"
component={Product} />
</Router>
);
}
export default AppRouter;
Entry Point
In the index.js file, we are going to use the AppRouter as the root
component.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
Final Thoughts
React Route enables rounding in an application.
The component with all the routes becomes the root component. The
<Link> components define navigation to routes defined using the <Route>
component.
In this chapter, we are going to take a look back and see how the functional
programming principles were used in the application.
Functional Array
filter() selects values from a list using a predicate function.
The basic array methods were used in all data transformations, but mostly
we have seen them used in reducers and selectors.
Immutability
An immutable value is a value that, once created, cannot be changed.
The transfer objects that move around the application from one component
to another should be immutable.
First, we should aim to create components as pure functions. That’s it, they
take data as props and return the markup defining the user interface.
Higher-order functions
A higher-order function is a function that takes another function as
argument, returns a function or does both.
function fetchProducts() {
return function(dispatch) {
return api.fetchProducts()
.then(actions.resetProducts)
.then(dispatch);
};
}
export default { fetchProducts };
Higher-order components
A higher-order component is a function that takes a component as input
and returns a new component.
Reading data from the store and dispatching events are side-effects. We can
keep components pure by encapsulating side effects in higher-order
components, HoC in short.
function isInQuery(query){
return function(product){
return product.name.includes(query.text);
};
}
Promises
A promise is an object that represents a possible future result of an
asynchronous operation.
API Util functions encapsulate the network calls that return promises.
Asynchronous action creators work with promises.
Function Composition
Function composition is a technique of passing the output of a function as
the input for another function.
Closures
A closure is a function that has access to the variables from its outer
function, even after the outer function has executed.
Closure can encapsulate state. Multiple closures sharing the same private
state can create encapsulated objects.
import { observable, action } from 'mobx';
//pure functions
//...
function ShoppingCartStore(){
const shoppingMap = observable.map();
function toCartView() {
return toCartViewFromMap(shoppingMap);
}
//actions
const addToCart = action(function(product){
shoppingMap.set(product.id,
incrementProductQuantity(shoppingMap, product));
});
return Object.freeze({
addToCart,
removeFromCart,
toCartView
});
}
Final Thoughts
React makes it easy to build components with functions. Functional
components are cleaner and easier to read. They are not using the this
pseudo-parameter.
Multiple closures sharing the same private state can create flexible and
encapsulated objects.
Chapter 23: Architecture
In this chapter, we are going to review and analyze the architectural
decisions taken so far.
Props
There are two kinds of values passed as props, plain data objects, and
function callbacks.
State
Reach Hooks enables functional components with local state, using the
useState() hook. We explore the local state when building the App and
ProductSearch components.
State can be external. It can be stored in a single store, like the Redux store,
or in multiple stores, like the ones created with MobX.
UI state, or view state, is used in the user interface. It is not taken or stored
on the backend.
The list of products in our case is the domain state. The shopping cart is
domain state as it needs to be stored on the backend. The query search
object is UI state.
Presentation Components
Presentation components take only plain data objects and function callbacks
as props.
Presentation components are responsible for rendering the UI. Here are
things presentation components don’t do:
Container Components
Container components are connected to the external environment and can
have side-effects.
Container components can read data from stores and dispatch actions.
UI Decomposition
UI as Function of State
In a sense, React allows treating the UI as a function of state. There is an
equation describing this relation:
UI = f(state)
Note that this equation takes into consideration only the data props. The UI
is not only about creating a visual representation of data but also about
handling user interactions. The user interactions can be expressed using the
concept of actions. Actions change state.
MobX Architecture
With MobX we tend to split the application into three layers:
Redux Architecture
In a Redux Architecture we split the application into the following parts :
We express the UI using presentation components and aim for making them
pure functions.
Container components are used to make the connection between the single
store and presentation components. Container components read data from
the store, use selectors to transform it, and send it to presentation
components. Container components decide what actions to dispatch on
events in the presentation components. When state changes inside the store
the presentation components using that data will re-render.
The API Util objects make the network calls.
Final Thoughts
The UI can be expressed as a tree of presentation components, where each
component is a function.
The application state can be split and managed in small parts. Container
components connect the state to presentation components.
Redux takes a functional way of managing state, while MobX takes a more
object-oriented approach. I favor Redux for working with the application
state in a functional style.
What’s next?
You can consider reading the Functional Architecture with React and Redux
book, and put in practice what you learned by building several applications
with an incremental level of complexity.
The functional architecture implies getting the initial state, showing it to the
user using the view functions, listening for actions, updating the state based
on those actions, and rendering the updated state back to the user again.
The update functions are the reducers, the view functions are the functional
components.