React with Redux Basics
Updated: 03 September 2023
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 <script8 crossorigin9 src="https://unpkg.com/react@16/umd/react.production.min.js"10 ></script>11 <script12 crossorigin13 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
1class 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
1ReactDOM.render(<App />, document.getElementById('app'))Within JSX we are also able to include Javascript dynamically within curly braces for example:
1class 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
1class 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
1class App extends React.Component {2
3 state = {4 name: 'John',5 age: 306 }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
1handleMouseOver = (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
1handleClick = (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
1class 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
1handleSubmit = (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
1class 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
29ReactDOM.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,nameis defined and not null. If you’re assigningstatein theconstructornote 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
We can create a new app using npx which is part of npm and the following command
1npx create-react-app netninja-appWe can then run the app as follows
1cd netninja-app2yarn startSingle 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
1import React, { Component } from 'react'2
3class 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
14export default AppTake 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
ReactLearningrepo
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
1const { name, age, belt } = this.propsWe 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
1class Ninjas extends Component {2 render() {3 const { ninjas } = this.props4 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
1import React from 'react'2
3const 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
14export default NinjasConditional 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
1const 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 null13 }14 })15 return <div className="ninja-list">{ninjaList}</div>16}Terenary
1const 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 ) : null10 })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
1import React, { Component } from 'react'2
3class 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
38export default AddNinjaWe 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
1addNinja = (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
1handleSubmit = (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:
1const 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 <button9 onClick={() => {10 deleteNinja(ninja.id)11 }}12 >13 Delete14 </button>15 </div>16 ) : null17 })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
1deleteNinja = (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
- CSS file for each component
- Index.css
- 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<link2 rel="stylesheet"3 href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"4/>App.js
1import React, { Component } from 'react'2import Todos from './Todos'3import AddTodo from './AddForm'4
5class 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.length20 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
37export default AppTodos.js
1import React from 'react'2
3const Todos = ({ todos, deleteTodo }) => {4 const todoList =5 todos.length > 0 ? (6 todos.map((todo) => (7 <div8 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
22export default TodosAddForm.js
1import React, { Component } from 'react'2
3class 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 <input28 type="text"29 onChange={this.handleChange}30 value={this.state.content}31 />32 </form>33 </div>34 )35 }36}37
38export default AddTodoReact 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
1npx create-react-app poketimesAdd CSS
Add the materialize.css stylesheet to the index.html
1<link2 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
1import React from 'react'2
3const About = () => (4 <div className="container">5 <h4 className="center">About</h4>6 <p>7 Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quos et8 asperiores libero tempora commodi, non accusantium sit ea eum? Beatae9 ipsum quod amet soluta aliquid quae nobis fugiat incidunt obcaecati.10 </p>11 </div>12)13
14export default Aboutcomponents/Contact.js
1import React from 'react'2
3const Contact = () => (4 <div className="container">5 <h4 className="center">Contact</h4>6 <p>7 Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quos et8 asperiores libero tempora commodi, non accusantium sit ea eum? Beatae9 ipsum quod amet soluta aliquid quae nobis fugiat incidunt obcaecati.10 </p>11 </div>12)13
14export default Contactcomponents/Home.js
1import React from 'react'2
3const Home = () => (4 <div className="container home">5 <h4 className="center">Home</h4>6 <p>7 Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quos et8 asperiores libero tempora commodi, non accusantium sit ea eum? Beatae9 ipsum quod amet soluta aliquid quae nobis fugiat incidunt obcaecati.10 </p>11 </div>12)13
14export default HomeCreate Navbar
Now add a navbar which will just link to the different pages we just created
components/Navbar.js
1import React from 'react'2
3const Navbar = () => {4 return (5 <nav className="nav-wrapper red darken-3">6 <div className="container">7 <a href="/" className="brand-logo">8 Poketimes9 </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
26export default NavbarAdd the Navbar to the App template as follows
1import React, { Component } from 'react'2import Navbar from './components/Navbar'3
4class App extends Component {5 render() {6 return (7 <div className="App">8 <Navbar />9 </div>10 )11 }12}13
14export default AppAdd 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
1yarn add react-router-domThen import the BrowserRouter and and Route include the applcation inside the BrowserRouter Component and the respective Route content in the Route
App.js
1import React, { Component } from 'react'2import { BrowserRouter, Route } from 'react-router-dom'3import Navbar from './components/Navbar'4import Home from './components/Home'5import About from './components/About'6import Contact from './components/Contact'7
8class 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
23export default AppMake 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
1import React from 'react'2import { Link, NavLink } from 'react-router-dom'3
4const Navbar = () => {5 return (6 <nav className="nav-wrapper red darken-3">7 <div className="container">8 <a href="/" className="brand-logo">9 Poketimes10 </a>11 <ul className="right">12 <li>13 <Link exact to="/">14 Home15 </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
29export default NavbarWe 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
1import React from 'react'2import { Link, NavLink } from 'react-router-dom'3
4const Navbar = () => {5 return (6 <nav className="nav-wrapper red darken-3">7 <div className="container">8 <a href="/" className="brand-logo">9 Poketimes10 </a>11 <ul className="right">12 <li>13 <NavLink exact to="/">14 Home15 </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
29export default NavbarRedirect 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
1import {withRouter} from 'react-router-dom'2...3export default withRouter(Navbar)To create an automatic redirect to the About page from the Navbar we can update the Navbar.js with the following
1import React from 'react'2import { NavLink, withRouter } from 'react-router-dom'3
4const Navbar = (props) => {5 setTimeout(() => {6 props.history.push('/about')7 }, 2000)8 return <nav className="nav-wrapper red darken-3">...</nav>9}10
11export default withRouter(Navbar)Be sure to remove the setTimeout after you have tested the rerouting as we do not need this
1import React from 'react'2import { NavLink, withRouter } from 'react-router-dom'3
4const Navbar = (props) => {5 return <nav className="nav-wrapper red darken-3">...</nav>6}7
8export 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
1import React from 'react'2
3const 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
15export default RainbowAxios
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
1yarn add axiosWe typically get data and render it with the componentDidMount lifecycle hook on the Home component by updating the code as follows as follows
1import React, { Component } from 'react'2import axios from 'axios'3
4class 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.state20
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
43export default HomeRoute 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
1import React, { Component } from 'react'2import { BrowserRouter, Route, Switch } from 'react-router-dom'3import Navbar from './components/Navbar'4import Home from './components/Home'5import About from './components/About'6import Contact from './components/Contact'7import Post from './components/Post'8
9class 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
27export default AppNote 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
1import React, { Component } from 'react'2import axios from 'axios'3
4class Post extends Component {5 state = {6 post: null,7 }8
9 componentDidMount() {10 const id = this.props.match.params.post_id11 axios12 .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
32export default PostRender 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
1import Pokeball from '../pokeball.png'2...3posts.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
- A component dispatches an action that describes the change that we want to make, with an optional payload
- The Reducer updates the state of the central state
- 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
1const { createStore } = Redux2
3const initState = {4 todos: [],5 posts: [],6}7
8function myreducer(state = initState, action) {9 console.log(action, state)10}11
12const store = createStore(myreducer)Dispatch Action
When editing data we create an action which will be dispatched, this is an object with type and props
1const todoAction = {2 type: 'ADD_TODO',3 todo: 'buy milk',4}5
6store.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
1function 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
1store.subscribe(() => {2 console.log('state updated')3 store.getState()4})The final state of our JS will be as follows
1const { createStore } = Redux;2
3const initState = {4 todos: [],5 posts: []6}7
8function myreducer(state = initState, action){9 if (action.type == 'ADD_TODO){10 return {11 ...state,12 todos: [...state.todos, action.todo]13 }14 }15}16
17store.subscribe(() => {18 console.log('state updated')19 store.getState()20})21
22const store = createStore(myreducer)23
24const todoAction = {25 type: 'ADD_TODO',26 todo: 'buy milk'27}28
29store.dispatch(todoAction)Install Redux
We need to add redux and react-redux to the project with the following code
1yarn add redux react-reduxInitialize Reducer
Create a src/reducers/rootReducer.js with a simple reducer defined and some simple state just for initialization
1const 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
21const rootReducer = (state = initState, action) => {22 return state23}24
25export default rootReducerCreate Store
Thereafter we will create a store, we generally do this in the index.js file, in our case updating it with the following
1import { createStore } from 'redux'2import { Provider } from 'react-redux'3import rootReducer from './reducers/rootReducer';4
5const store = createStore(rootReducer)6
7ReactDOM.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
1const initState = {2 posts: [],3}4
5const rootReducer = (state = initState, action) => {6 return state7}8
9export default rootReducerHome.js
1import React, { Component } from 'react'2import { Link } from 'react-router-dom'3import Pokeball from '../pokeball.png'4import { connect } from 'react-redux'5
6class Home extends Component {7 render() {8 const { posts } = this.props9
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
35const mapStateToProps = (state) => {36 return {37 posts: state.posts,38 }39}40
41export 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
1import React, { Component } from 'react'2import { connect } from 'react-redux'3
4class 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
19const mapStateToProps = (state, ownProps) => {20 const id = ownProps.match.params.post_id21 return {22 post: state.posts.find((post) => post.id == id),23 }24}25
26export 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
1import React, { Component } from 'react'2import { connect } from 'react-redux'3
4class 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 Post16 </button>17 ...18 ) : (19 <div className="center">Loading post...</div>20 )21
22 return <div className="container">{post}</div>23 }24}25
26const mapStateToProps = (state, ownProps) => {27 const id = ownProps.match.params.post_id28 return {29 post: state.posts.find(post => post.id === +id)30 }31}32
33const mapDispatchToProps = dispatch => {34 return {35 deletePost: id => dispatch({type: 'DELETE_POST', id: id})36 }37}38
39export default connect(mapStateToProps, mapDispatchToProps)(Post)As well as providing the delete functionality on the rootReducer as follows
1const 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 state11}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
1export 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
1const mapDispatchToProps = (dispatch) => {2 return {3 deletePost: (id) => dispatch(deletePost(id)),4 }5}