React with Redux Basics

Updated: 03 September 2023

Based on this video series

GitHub Repo

VSCode Extensions

  • ES7 React/Redux/GraphQL/React-Native snippets
  • Sublime Babel
  • React Chrome Dev Tools

Intro

React

React is a JS library created by Facebook and is used to create dynamic web apps

Redux

Redux is a layer on top of React that helps to manage the state and data of an app

How React Works

React is made up of components which are used for different parts of the application. React will take these components and render them into the DOM

React uses a Virtual DOM and renders the content into the Actual DOM based on that

When the state is updated React creates a new VD and compares it to the AD and renders based on the differences between the two

Components are made of JSX templates which can contain the UI state as well as functionality

React in the Browser

Set Up React with CDN

React can be included via CDN or with the Create React App

The CDN is good if we do not want to use React for an entire application but just a few specific places

We simply include the React scripts for react and the Babel script to allow us to use React in the browser as follows:

1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
<meta charset="UTF-8" />
5
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7
<script
8
crossorigin
9
src="https://unpkg.com/react@16/umd/react.production.min.js"
10
></script>
11
<script
12
crossorigin
13
src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"
14
></script>
15
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
16
<title>Document</title>
17
</head>
18
<body>
19
<div id="app"></div>
20
<script></script>
21
</body>
22
</html>

React Components

React components will take over a specific element in the DOM, we can create them within a script tag , a component is defined by a class which will extend React.Component

Any class based component must define a render method which will return the tempalate for an object in JSX

1
class App extends React.Component {
2
render() {
3
return (
4
<div>
5
<h1>Hello World!</h1>
6
</div>
7
)
8
}
9
}

JSX needs to only return a single root element. We also cannot use the word class as it is a reserver word in JS and hence we need to use className instead

In order to render a component we need to tell React to render our component, this is from the readt-dom script

the ReactDOM.render function takes a component and the DOM element we want to render the component into as follows

1
ReactDOM.render(<App />, document.getElementById('app'))

Within JSX we are also able to include Javascript dynamically within curly braces for example:

1
class App extends React.Component {
2
render() {
3
return (
4
<div>
5
<h1>Hello World!</h1>
6
<p>{Math.random() * 10}</p>
7
</div>
8
)
9
}
10
}

Component State

This describes the state of the Data and the UI in the application. Component state is simply a JS object

We make use of the state of the component to dynamically update the visual representation of the component

We can define and create component state in a few ways. The easiest is to define a state object which is a Javascript object that will contain the state data

State can be referenced as dynamic content inside of template

1
class App extends React.Component {
2
state = {
3
name: 'John',
4
age: 30,
5
}
6
7
render() {
8
return (
9
<div>
10
<h1>Hello World!</h1>
11
<p>
12
My name is {this.state.name}, I am {this.state.age}
13
</p>
14
</div>
15
)
16
}
17
}

State can also be defined in a constructor

Click Events

We can attach event handlers to an element based on the event name on the JSX and link it to a function in the component to handle the function

Handlers are simply defined as functions in the class definition for an object

1
class App extends React.Component {
2
3
state = {
4
name: 'John',
5
age: 30
6
}
7
8
handleClick => (e) => {
9
const event = Object.assign({}, e)
10
console.log(event)
11
}
12
13
handleMouseOver(e){
14
console.log(this)
15
}
16
17
handleCopy(e){
18
console.log("User copied the text")
19
}
20
21
render(){
22
return (
23
<div>
24
<h1>Hello World!</h1>
25
<p>My name is { this.state.name }, I am { this.state.age }</p>
26
<button onClick={ this.handleClick }>Click Me</button>
27
<button onMouseOver={ this.handleMouseOver }>Hover Me</button>
28
<p onCopy={ this.handleCopy }>What we think, what we become</p>
29
</div>
30
)
31
}
32
}

State and the THIS

When accessing state inside of functions we cannot necessarily access the context this object which is determined by how and where the function is called. For example in the above code when we hover we will see the this as undefined

In the case of event handlers, we need to manually bind the this to our functions. Note that for the render method, React will automatically bind the context

There are a few different ways to bind context, using an Arrow function is the easiest way as they pass through the parent context as defined by ES6

We can do this for the handleMouseOver function as follows

1
handleMouseOver = (e) => {
2
console.log(this)
3
}

Update State

In order to update state we use the this.setState function, not by manually assigning the new value

We can update the state by doing something simple like increasing the age when someone clicks on the Click Me button

1
handleClick = (e) => {
2
this.setState({ age: this.state.age + 1 })
3
}

Forms

We can redefine the content of our App class to contain a simple form by replacing it with the following

1
class App extends React.Component {
2
state = {
3
name: 'John',
4
age: 30,
5
}
6
7
render() {
8
return (
9
<div className="app-contnet">
10
<h1>My name is {this.state.name}</h1>
11
<form>
12
<input type="text" />
13
<button>Submit</button>
14
</form>
15
</div>
16
)
17
}
18
}

We can then make use of event binding to enable stuff to happen when we interact with the form, for the onSubmit event handler in the form we capture the enter or submit click for the form

When a form is submitted by default the event is to submit and refresh the page, but we do not want that, so in the handleSubmit function we need to first prevent the default action and then add our own functionality

1
handleSubmit = (e) => {
2
e.preventDefault()
3
console.log('Form submitted by', this.state.name)
4
}

We then bind this to the form onSubmit event and can be seen below

1
class App extends React.Component {
2
state = {
3
name: 'John',
4
age: 30,
5
}
6
7
handleChange = (e) => {
8
this.setState({ name: e.target.value })
9
}
10
11
handleSubmit = (e) => {
12
e.preventDefault()
13
console.log('Form submitted by', this.state.name)
14
}
15
16
render() {
17
return (
18
<div className="app-contnet">
19
<h1>My name is {this.state.name}</h1>
20
<form onSubmit={this.handleSubmit}>
21
<input type="text" onChange={this.handleChange} />
22
<button>Submit</button>
23
</form>
24
</div>
25
)
26
}
27
}
28
29
ReactDOM.render(<App />, document.getElementById('app'))

It is important to note that React makes use of two input types: ‘controlled’ and ‘uncontrolled’. In order to make an input ‘controlled’ you need to ensure that the value is initialized - e.g. if value={name} you need to be sure that upon initialization, name is defined and not null. If you’re assigning state in the constructor note that you will be overwriting state that is defined at a class level

React CLI

Creating a React App

The Create React App is a CLI that allows us to use a development server, ES6 features, modularise code, and use build tools to optimize code for deployment

Create React App CLI

We can create a new app using npx which is part of npm and the following command

Terminal window
1
npx create-react-app netninja-app

We can then run the app as follows

Terminal window
1
cd netninja-app
2
yarn start

Single Page Apps

Single page apps provide the user with a single page with which the user’s inital request retrieves the index.html and thereafter the React Javascript intercepts the user’s interactions with the application instead of the server

The public/index.html file is created with a root into which the application components can be injected. create-react-app creates an App class that will be rendered into the DOM. The src/index.js Exports the application and renders it in the HTML

Additionally there is a CSS and JS file for each component as well as a test file

Root Component

A root component is the app that is rendered initially into which other components are rendered. In this case our App component is the Root component

We’ll create some new components in the src file to build the other functionality into the app

Let’s clean up the src/App.js file to be as follows

1
import React, { Component } from 'react'
2
3
class App extends Component {
4
render() {
5
return (
6
<div className="App">
7
<h1>React App</h1>
8
<p>Hello World!</p>
9
</div>
10
)
11
}
12
}
13
14
export default App

Take note of the imports at the top and the export at the bottom of the above codeblock

From this point the code will be included in my ReactLearning repo

Props

Props allow us to pass data into components therefore making them more general and thereby more reusable

We pass this in within the template, this can be done as follows

1
<Ninjas name="Ryu" age="25" belt="Black" />

And can then be accessed in a class based component with the this.props object

In order to split up the object, we can de-structure the props object as follows

1
const { name, age, belt } = this.props

We can also, instead of passing a single value in - pass in a list of objects and cycle through those within a component

Lists

We can iterate and render a list of objects, but we must be sure to include a unique key so that react can efficiently manage the DOM with lists of elements. This can be done with the following

1
class Ninjas extends Component {
2
render() {
3
const { ninjas } = this.props
4
const ninjaList = ninjas.map((ninja) => (
5
<div className="ninja" key={ninja.id}>
6
<div>Name: {ninja.name}</div>
7
<div>Age: {ninja.age}</div>
8
<div>Belt: {ninja.belt}</div>
9
</div>
10
))
11
return <div className="ninja-list">{ninjaList}</div>
12
}
13
}

Component Types

We have multiple types of components such as Container components which are stateful and UI components which are stateless. These are also known as class-based and function-based components respectively

UI Components

We can redefine the Ninjas component from above to be a Functional Component which also makes use of destructuring in the function input with the following code

1
import React from 'react'
2
3
const Ninjas = ({ ninjas }) => {
4
const ninjaList = ninjas.map((ninja) => (
5
<div className="ninja" key={ninja.id}>
6
<div>Name: {ninja.name}</div>
7
<div>Age: {ninja.age}</div>
8
<div>Belt: {ninja.belt}</div>
9
</div>
10
))
11
return <div className="ninja-list">{ninjaList}</div>
12
}
13
14
export default Ninjas

Conditional Output

In order to display conditional output we can simply use an if-else or terenary operator in order to create the desired output, we can see this being done when we do the array mapping over the Ninja list below

If-Else

1
const Ninjas = ({ ninjas }) => {
2
const ninjaList = ninjas.map((ninja) => {
3
if (ninja.age > 31) {
4
return (
5
<div className="ninja" key={ninja.id}>
6
<div>Name: {ninja.name}</div>
7
<div>Age: {ninja.age}</div>
8
<div>Belt: {ninja.belt}</div>
9
</div>
10
)
11
} else {
12
return null
13
}
14
})
15
return <div className="ninja-list">{ninjaList}</div>
16
}

Terenary

1
const Ninjas = ({ ninjas }) => {
2
const ninjaList = ninjas.map((ninja) => {
3
return ninja.age > 31 ? (
4
<div className="ninja" key={ninja.id}>
5
<div>Name: {ninja.name}</div>
6
<div>Age: {ninja.age}</div>
7
<div>Belt: {ninja.belt}</div>
8
</div>
9
) : null
10
})
11
return <div className="ninja-list">{ninjaList}</div>
12
}

Forms

We’ll create a new form for adding Ninjas to the list, then update the state inside of the Ninjas component with the new Ninja

Crete a new file for the form called AddNinja.js, because we need to control the state of the form, it will need to be class-based

The onChange function will update the state each time we update the form, in order to do this we first need to define a state object and a handleChange event in which we can take the correct state property

1
import React, { Component } from 'react'
2
3
class AddNinja extends Component {
4
state = {
5
name: null,
6
age: null,
7
belt: null,
8
}
9
10
handleChange = (e) => {
11
this.setState({
12
[e.target.id]: e.target.value,
13
})
14
}
15
16
handleSubmit = (e) => {
17
e.preventDefault()
18
console.log(this.state)
19
}
20
21
render() {
22
return (
23
<div>
24
<form onSubmit={this.handleSubmit}>
25
<label htmlFor="name">Name:</label>
26
<input type="text" id="name" onChange={this.handleChange} />
27
<label htmlFor="Age">Age:</label>
28
<input type="text" id="age" onChange={this.handleChange} />
29
<label htmlFor="belt">Belt:</label>
30
<input type="text" id="belt" onChange={this.handleChange} />
31
<button>Submit</button>
32
</form>
33
</div>
34
)
35
}
36
}
37
38
export default AddNinja

We will then need to add this to the App by including it in the app.js Render()

We can now update the state of the form, but we are still not adding the new ninja to the App State

In the App class, create a new function to handle the addNinja event which we will then be able to pass into the child component and then call that function from the child in which we will

1
addNinja = (ninja) => {
2
ninja.id = Math.random()
3
let ninjas = [...this.state.ninjas, ninja]
4
this.setState({
5
ninjas: ninjas,
6
})
7
}

Pass the addNinja function as a prop to the AddNinja component as follows

1
<AddNinja addNinja={this.addNinja} />

And we will update the handleSubmit function in the AddNinja class to be

1
handleSubmit = (e) => {
2
e.preventDefault()
3
this.props.addNinja(this.state)
4
}

Next up we will add the functionality to delete a ninja by creating a function in the parent called deleteNinja and adding a delete button on the child from which we will call the delete function

We will wrap the function on the child in an anonymous function in order to prevent it from being called as soon as the button is instantiated. adding this button to the Ninjas component should result in the following:

1
const Ninjas = ({ ninjas, deleteNinja }) => {
2
const ninjaList = ninjas.map((ninja) => {
3
return ninja.age > 31 ? (
4
<div className="ninja" key={ninja.id}>
5
<div>Name: {ninja.name}</div>
6
<div>Age: {ninja.age}</div>
7
<div>Belt: {ninja.belt}</div>
8
<button
9
onClick={() => {
10
deleteNinja(ninja.id)
11
}}
12
>
13
Delete
14
</button>
15
</div>
16
) : null
17
})
18
return <div className="ninja-list">{ninjaList}</div>
19
}

Next, from the App component, define the deleteNinja function and pass it in as a prop to the Ninjas component

In the deleteNinja function, we will use a non-destructiver filter function to remove the ninja with the selected ID

1
deleteNinja = (id) => {
2
let ninjas = this.state.ninjas.filter((ninja) => ninja.id !== id)
3
this.setState({
4
ninjas: ninjas,
5
})
6
}

CSS

There are a few different ways to include CSS in react

  1. CSS file for each component
  2. Index.css
  3. CSS modules

The React build tool will add vendor prefixes automatically for us

To include CSS in a component we simply import the CSS file

With every method other than CSS modules, the styles will be applied everywhere

Lifecycle Methods

React Components have a bunch of different lifecycle methods, the only one that needs to be provided by us is the render method, I have a list and explanation of these here

ToDo App

For a somewhat nicer version of this take a look at the ToDo app which makes use of the same concepts here but additionally uses materialize.css to make it a bit nicer

index.html

1
<link
2
rel="stylesheet"
3
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
4
/>

App.js

1
import React, { Component } from 'react'
2
import Todos from './Todos'
3
import AddTodo from './AddForm'
4
5
class App extends Component {
6
state = {
7
todos: [
8
{ id: 0, content: 'buy some milk' },
9
{ id: 1, content: 'play mario kart' },
10
],
11
}
12
13
deleteTodo = (id) => {
14
const todos = this.state.todos.filter((todo) => todo.id !== id)
15
this.setState({ todos: todos })
16
}
17
18
addTodo = (todo) => {
19
todo.id = this.state.todos.length
20
const todos = [...this.state.todos, todo]
21
this.setState({
22
todos: todos,
23
})
24
}
25
26
render() {
27
return (
28
<div className="todo-app container">
29
<h1 className="center blue-text">Todo's</h1>
30
<Todos todos={this.state.todos} deleteTodo={this.deleteTodo} />
31
<AddTodo addTodo={this.addTodo} />
32
</div>
33
)
34
}
35
}
36
37
export default App

Todos.js

1
import React from 'react'
2
3
const Todos = ({ todos, deleteTodo }) => {
4
const todoList =
5
todos.length > 0 ? (
6
todos.map((todo) => (
7
<div
8
className="collection-item"
9
key={todo.id}
10
onClick={() => deleteTodo(todo.id)}
11
>
12
<span>{todo.content}</span>
13
</div>
14
))
15
) : (
16
<p className="center">You have no todos</p>
17
)
18
19
return <div className="todos collection">{todoList}</div>
20
}
21
22
export default Todos

AddForm.js

1
import React, { Component } from 'react'
2
3
class AddTodo extends Component {
4
state = {
5
content: '',
6
}
7
8
handleChange = (e) => {
9
this.setState({
10
content: e.target.value,
11
})
12
}
13
14
handleSubmit = (e) => {
15
e.preventDefault()
16
this.props.addTodo(this.state)
17
this.setState({
18
content: '',
19
})
20
}
21
22
render() {
23
return (
24
<div>
25
<form onSubmit={this.handleSubmit}>
26
<label>Add new todo:</label>
27
<input
28
type="text"
29
onChange={this.handleChange}
30
value={this.state.content}
31
/>
32
</form>
33
</div>
34
)
35
}
36
}
37
38
export default AddTodo

React Router

The React Router will essentially disply specific components in response to specific route requests, the React Router will intercept requests

For this application we will be using create-react-app once again with materialize.css

Intialize App

Terminal window
1
npx create-react-app poketimes

Add CSS

Add the materialize.css stylesheet to the index.html

1
<link
2
rel="stylesheet"
3
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
4
/>

Create Pages

Before we can route to pagess it would be useful to first have some. Create the following files in a src/components directory with their respective contents

components/About.js

1
import React from 'react'
2
3
const About = () => (
4
<div className="container">
5
<h4 className="center">About</h4>
6
<p>
7
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quos et
8
asperiores libero tempora commodi, non accusantium sit ea eum? Beatae
9
ipsum quod amet soluta aliquid quae nobis fugiat incidunt obcaecati.
10
</p>
11
</div>
12
)
13
14
export default About

components/Contact.js

1
import React from 'react'
2
3
const Contact = () => (
4
<div className="container">
5
<h4 className="center">Contact</h4>
6
<p>
7
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quos et
8
asperiores libero tempora commodi, non accusantium sit ea eum? Beatae
9
ipsum quod amet soluta aliquid quae nobis fugiat incidunt obcaecati.
10
</p>
11
</div>
12
)
13
14
export default Contact

components/Home.js

1
import React from 'react'
2
3
const Home = () => (
4
<div className="container home">
5
<h4 className="center">Home</h4>
6
<p>
7
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quos et
8
asperiores libero tempora commodi, non accusantium sit ea eum? Beatae
9
ipsum quod amet soluta aliquid quae nobis fugiat incidunt obcaecati.
10
</p>
11
</div>
12
)
13
14
export default Home

Create Navbar

Now add a navbar which will just link to the different pages we just created

components/Navbar.js

1
import React from 'react'
2
3
const Navbar = () => {
4
return (
5
<nav className="nav-wrapper red darken-3">
6
<div className="container">
7
<a href="/" className="brand-logo">
8
Poketimes
9
</a>
10
<ul className="right">
11
<li>
12
<a href="/">Home</a>
13
</li>
14
<li>
15
<a href="/about">About</a>
16
</li>
17
<li>
18
<a href="/contact">Contact</a>
19
</li>
20
</ul>
21
</div>
22
</nav>
23
)
24
}
25
26
export default Navbar

Add the Navbar to the App template as follows

1
import React, { Component } from 'react'
2
import Navbar from './components/Navbar'
3
4
class App extends Component {
5
render() {
6
return (
7
<div className="App">
8
<Navbar />
9
</div>
10
)
11
}
12
}
13
14
export default App

Add React Router Package

Add the React Router to the application by installing the package and then including it in the application by doing the following

Terminal window
1
yarn add react-router-dom

Then import the BrowserRouter and and Route include the applcation inside the BrowserRouter Component and the respective Route content in the Route

App.js

1
import React, { Component } from 'react'
2
import { BrowserRouter, Route } from 'react-router-dom'
3
import Navbar from './components/Navbar'
4
import Home from './components/Home'
5
import About from './components/About'
6
import Contact from './components/Contact'
7
8
class App extends Component {
9
render() {
10
return (
11
<BrowserRouter>
12
<div className="App">
13
<Navbar />
14
<Route exact path="/" component={Home} />
15
<Route path="/contact" component={Contact} />
16
<Route path="/about" component={About} />
17
</div>
18
</BrowserRouter>
19
)
20
}
21
}
22
23
export default App

Make note of the exact on the Home route, this insures that the About and Contact pages are not viewed as subsets of the Home route.

Also we will notice that when navigating the page is reloaded, this is because we are using href in our Navbar anchor tags which results in the browser trying to make a server request. Instead we will make use of the Link from the react-router-dom as follows

1
import React from 'react'
2
import { Link, NavLink } from 'react-router-dom'
3
4
const Navbar = () => {
5
return (
6
<nav className="nav-wrapper red darken-3">
7
<div className="container">
8
<a href="/" className="brand-logo">
9
Poketimes
10
</a>
11
<ul className="right">
12
<li>
13
<Link exact to="/">
14
Home
15
</Link>
16
</li>
17
<li>
18
<Link to="/about">About</Link>
19
</li>
20
<li>
21
<Link to="/contact">Contact</Link>
22
</li>
23
</ul>
24
</div>
25
</nav>
26
)
27
}
28
29
export default Navbar

We can also use NavLink insead, this will but this will render the active class when we are on a specific route - this is pretty much the same as when using Link

1
import React from 'react'
2
import { Link, NavLink } from 'react-router-dom'
3
4
const Navbar = () => {
5
return (
6
<nav className="nav-wrapper red darken-3">
7
<div className="container">
8
<a href="/" className="brand-logo">
9
Poketimes
10
</a>
11
<ul className="right">
12
<li>
13
<NavLink exact to="/">
14
Home
15
</NavLink>
16
</li>
17
<li>
18
<NavLink to="/about">About</NavLink>
19
</li>
20
<li>
21
<NavLink to="/contact">Contact</NavLink>
22
</li>
23
</ul>
24
</div>
25
</nav>
26
)
27
}
28
29
export default Navbar

Redirect Users

The React Router will pass Router Information into the props of our component when it renders the component - this will give us route parameters that we can work with among other things

The props object as an example, scan be seen below

1
{
2
match: { path: "/contact", url: "/contact", isExact: true, params: {} },
3
location: { pathname: "/contact", search: "", hash: "", key: "gb517b" },
4
history: {
5
length: 50,
6
action: "POP",
7
location: { pathname: "/contact", search: "", hash: "", key: "gb517b" }
8
}
9
}

Along with the above, the props also contain a history object which has a push function with which we can add programmatic redirects

However, we will note that the Navbar component does not automatically get the Router information injected into the props as it is not associated with a route - however we can wrap it with a higher order component with the following code

Navbar.js

1
import {withRouter} from 'react-router-dom'
2
...
3
export default withRouter(Navbar)

To create an automatic redirect to the About page from the Navbar we can update the Navbar.js with the following

1
import React from 'react'
2
import { NavLink, withRouter } from 'react-router-dom'
3
4
const Navbar = (props) => {
5
setTimeout(() => {
6
props.history.push('/about')
7
}, 2000)
8
return <nav className="nav-wrapper red darken-3">...</nav>
9
}
10
11
export default withRouter(Navbar)

Be sure to remove the setTimeout after you have tested the rerouting as we do not need this

1
import React from 'react'
2
import { NavLink, withRouter } from 'react-router-dom'
3
4
const Navbar = (props) => {
5
return <nav className="nav-wrapper red darken-3">...</nav>
6
}
7
8
export default withRouter(Navbar)

Higher Order Components

HOCs are functions that add functionality to components, such as the withRouter function

Create a new file hoc/Rainbow.js that will randomize the colour of the text in a component

The HOC should return a function which returns the original component, we can do this as follows

1
import React from 'react'
2
3
const Rainbow = (WrappedComponent) => {
4
const colours = ['red', 'pink', 'orange', 'blue', 'green', 'yellow']
5
const randomColour = colours[Math.floor(Math.random() * 5)]
6
const className = randomColour + '-text'
7
8
return (props) => (
9
<div className={className}>
10
<WrappedComponent {...props} />
11
</div>
12
)
13
}
14
15
export default Rainbow

Axios

We can get some dummy data for our application from the jsonplaceholder site, we can also create a fake JSON Server here

Axios is an HTTP library which can be installed as follows

Terminal window
1
yarn add axios

We typically get data and render it with the componentDidMount lifecycle hook on the Home component by updating the code as follows as follows

1
import React, { Component } from 'react'
2
import axios from 'axios'
3
4
class Home extends Component {
5
state = {
6
posts: [],
7
}
8
9
componentDidMount() {
10
axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
11
console.log(res)
12
this.setState({
13
posts: res.data.slice(0, 10),
14
})
15
})
16
}
17
18
render = () => {
19
const { posts } = this.state
20
21
const postList = posts.length ? (
22
posts.map((post) => (
23
<div className="post card" key={post.id}>
24
<div className="card-content">
25
<span className="card-title">{post.title}</span>
26
<p>{post.body}</p>
27
</div>
28
</div>
29
))
30
) : (
31
<div className="center">No posts yet</div>
32
)
33
34
return (
35
<div className="container home">
36
<h4 className="center">Home</h4>
37
{postList}
38
</div>
39
)
40
}
41
}
42
43
export default Home

Route Parameters

We can set up sub-routes based on components, we can create a component to render a single post with the following, taking careful note of the sub-component routing in the Home.js for the Post component file and the usage of the params in the Post.js file

Home.js

1
import React, { Component } from 'react'
2
import { BrowserRouter, Route, Switch } from 'react-router-dom'
3
import Navbar from './components/Navbar'
4
import Home from './components/Home'
5
import About from './components/About'
6
import Contact from './components/Contact'
7
import Post from './components/Post'
8
9
class App extends Component {
10
render() {
11
return (
12
<BrowserRouter>
13
<div className="App">
14
<Navbar />
15
<Switch>
16
<Route exact path="/" component={Home} />
17
<Route path="/contact" component={Contact} />
18
<Route path="/about" component={About} />
19
<Route path="/:post_id" component={Post} />
20
</Switch>
21
</div>
22
</BrowserRouter>
23
)
24
}
25
}
26
27
export default App

Note that the usage of the Switch above is to take care of the fact that /contact and /about can be viewed as /:post_id where the post_id is contact and about respectively - the Switch component will ensure that only one route at a time can be matched

Next we render the Post on the /:post_id page with the following

1
import React, { Component } from 'react'
2
import axios from 'axios'
3
4
class Post extends Component {
5
state = {
6
post: null,
7
}
8
9
componentDidMount() {
10
const id = this.props.match.params.post_id
11
axios
12
.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
13
.then((res) => {
14
this.setState({ post: res.data })
15
})
16
}
17
18
render() {
19
const post = this.state.post ? (
20
<div className="post">
21
<h4 className="center">{this.state.post.title}</h4>
22
<p>{this.state.post.body}</p>
23
</div>
24
) : (
25
<div className="center">Loading post...</div>
26
)
27
28
return <div className="container">{post}</div>
29
}
30
}
31
32
export default Post

Render Images

We can render images by importing them into the component in which we waant to use them and reference the import without needing the source to the actual file

Home.js

1
import Pokeball from '../pokeball.png'
2
...
3
posts.map(post => (
4
<div className="post card" key={post.id}>
5
<img src={Pokeball} alt="A pokeball"/>
6
<div className="card-content">
7
<Link to={`/${post.id}`}><span className="card-title">{post.title}</span></Link>
8
<p>{post.body}</p>
9
</div>
10
</div>
11
))

Redux

Redux is a library that is essentially a centralized state store which makes it easier for us to manage state on different components that use the same information

  1. A component dispatches an action that describes the change that we want to make, with an optional payload
  2. The Reducer updates the state of the central state
  3. Component subscribes to changes and receives them as props

Using Redux in HTML

We can import Redux, and Babel into the HTML document, and start working with the basics within that app

Create a Store

When creating a store we need to intialize state and give the store a reducer, this can be seen below

1
const { createStore } = Redux
2
3
const initState = {
4
todos: [],
5
posts: [],
6
}
7
8
function myreducer(state = initState, action) {
9
console.log(action, state)
10
}
11
12
const store = createStore(myreducer)

Dispatch Action

When editing data we create an action which will be dispatched, this is an object with type and props

1
const todoAction = {
2
type: 'ADD_TODO',
3
todo: 'buy milk',
4
}
5
6
store.dispatch(todoAction)

We would siomply use the dispatch from a component to update state

Update State

In order to interact with our state we will make use of the Reducer and manipulate the state based on action.type. The reducer needs to then return the state update

1
function myreducer(state = initState, action){
2
if (action.type == 'ADD_TODO){
3
return {
4
...state,
5
todos: [...state.todos, action.todo]
6
}
7
}
8
}

Note that the above will overwrite the state, hence the ...state portion

Subscribe to Changes

In order to subscribe, we need to create a listener for store changes and react to those changes

1
store.subscribe(() => {
2
console.log('state updated')
3
store.getState()
4
})

The final state of our JS will be as follows

1
const { createStore } = Redux;
2
3
const initState = {
4
todos: [],
5
posts: []
6
}
7
8
function myreducer(state = initState, action){
9
if (action.type == 'ADD_TODO){
10
return {
11
...state,
12
todos: [...state.todos, action.todo]
13
}
14
}
15
}
16
17
store.subscribe(() => {
18
console.log('state updated')
19
store.getState()
20
})
21
22
const store = createStore(myreducer)
23
24
const todoAction = {
25
type: 'ADD_TODO',
26
todo: 'buy milk'
27
}
28
29
store.dispatch(todoAction)

Install Redux

We need to add redux and react-redux to the project with the following code

Terminal window
1
yarn add redux react-redux

Initialize Reducer

Create a src/reducers/rootReducer.js with a simple reducer defined and some simple state just for initialization

1
const initState = {
2
posts: [
3
{
4
id: 1,
5
title: 'Hello World 1',
6
body: 'das as das d asd asd sd asd asd sd sd',
7
},
8
{
9
id: 2,
10
title: 'Hello World 2',
11
body: 'das as das d asd asd sd asd asd sd sd',
12
},
13
{
14
id: 3,
15
title: 'Hello World 3',
16
body: 'das as das d asd asd sd asd asd sd sd',
17
},
18
],
19
}
20
21
const rootReducer = (state = initState, action) => {
22
return state
23
}
24
25
export default rootReducer

Create Store

Thereafter we will create a store, we generally do this in the index.js file, in our case updating it with the following

1
import { createStore } from 'redux'
2
import { Provider } from 'react-redux'
3
import rootReducer from './reducers/rootReducer';
4
5
const store = createStore(rootReducer)
6
7
ReactDOM.render(<Provider store={store}><App /></Provider>,

Update Data

In Order to update our components, we will make use of an HOC, in this case using connect from react-redux, we need to update the Home.js file as well as the rootReducer.js

Note that the connect function returns an HOC which will in turn take in our component. The connect function requires an input function which will map our component props to our redux state

rootReducer.js

1
const initState = {
2
posts: [],
3
}
4
5
const rootReducer = (state = initState, action) => {
6
return state
7
}
8
9
export default rootReducer

Home.js

1
import React, { Component } from 'react'
2
import { Link } from 'react-router-dom'
3
import Pokeball from '../pokeball.png'
4
import { connect } from 'react-redux'
5
6
class Home extends Component {
7
render() {
8
const { posts } = this.props
9
10
const postList = posts.length ? (
11
posts.map((post) => (
12
<div className="post card" key={post.id}>
13
<img src={Pokeball} alt="A pokeball" />
14
<div className="card-content">
15
<Link to={`/${post.id}`}>
16
<span className="card-title">{post.title}</span>
17
</Link>
18
<p>{post.body}</p>
19
</div>
20
</div>
21
))
22
) : (
23
<div className="center">No posts yet</div>
24
)
25
26
return (
27
<div className="container home">
28
<h4 className="center">Home</h4>
29
{postList}
30
</div>
31
)
32
}
33
}
34
35
const mapStateToProps = (state) => {
36
return {
37
posts: state.posts,
38
}
39
}
40
41
export default connect(mapStateToProps)(Home)

Render Post from Redux

Now we need to connect the Post component to Redux, this can essentially be done in the same way as the Home component above

1
import React, { Component } from 'react'
2
import { connect } from 'react-redux'
3
4
class Post extends Component {
5
render() {
6
const post = this.props.post ? (
7
<div className="post">
8
<h4 className="center">{this.props.post.title}</h4>
9
<p>{this.props.post.body}</p>
10
</div>
11
) : (
12
<div className="center">Loading post...</div>
13
)
14
15
return <div className="container">{post}</div>
16
}
17
}
18
19
const mapStateToProps = (state, ownProps) => {
20
const id = ownProps.match.params.post_id
21
return {
22
post: state.posts.find((post) => post.id == id),
23
}
24
}
25
26
export default connect(mapStateToProps)(Post)

Update State

To update state we need to dispatch an action, and when that has changed the relevant components will be updated

We need to define a mapDispatchToProps function on the component, this can be done on the Post component as follows

1
import React, { Component } from 'react'
2
import { connect } from 'react-redux'
3
4
class Post extends Component {
5
6
handleClick = () => {
7
this.props.deletePost(this.props.post.id)
8
this.props.history.push('/')
9
}
10
11
render() {
12
const post = this.props.post ? (
13
...
14
<button className="btn grey" onClick={this.handleClick}>
15
Delete Post
16
</button>
17
...
18
) : (
19
<div className="center">Loading post...</div>
20
)
21
22
return <div className="container">{post}</div>
23
}
24
}
25
26
const mapStateToProps = (state, ownProps) => {
27
const id = ownProps.match.params.post_id
28
return {
29
post: state.posts.find(post => post.id === +id)
30
}
31
}
32
33
const mapDispatchToProps = dispatch => {
34
return {
35
deletePost: id => dispatch({type: 'DELETE_POST', id: id})
36
}
37
}
38
39
export default connect(mapStateToProps, mapDispatchToProps)(Post)

As well as providing the delete functionality on the rootReducer as follows

1
const rootReducer = (state = initState, action) => {
2
console.log(action)
3
if (action.type === 'DELETE_POST') {
4
const newPosts = state.posts.filter((post) => post.id !== action.id)
5
return {
6
...state,
7
posts: newPosts,
8
}
9
}
10
return state
11
}

Action Creators

Action creators are functions we can use to more easily dispatch actions by simply calling a function

Create a file src/actions/postActions.js with a single function defined

1
export const deletePost = (id) => {
2
return {
3
type: 'DELETE_POST',
4
id: id,
5
}
6
}

And then simply update the Posts/mapDispatchToProp function to be as follows

1
const mapDispatchToProps = (dispatch) => {
2
return {
3
deletePost: (id) => dispatch(deletePost(id)),
4
}
5
}