Learn Redux and Redux with React in a simple way

You don't have to go anywhere or spend your time learning Redux on the internet or from books here I include everything you need to learn Redux and then I also include three methods to connect Redux to React all in one place.

In a Redux application

The state is usually represented as a JavaScript object. Each property or attribute or key of

the object describes a substate of the application

A simple blog application state or object could consist of an array of posts (which

are written by the user and contain some text):

{

posts: [

{ user: 'dan', text: 'Hello World!' },

{ user: 'des', text: 'Welcome to the blog' }

]

}

Imagine that we want to add a category string to posts later—we can simply add this

property or key to the objects in the posts array:

{

posts: [

{ user: 'dan', category: 'hello', text: 'Hello World!' },

{ user: 'des', category: 'welcome', text: 'Welcome to the blog' }

]

}

let's say we want to implement filtering posts by category; we could extend our

state object with a filter property that stores the category as a string:

{

posts: [

{ user: 'dan', category: 'hello', text: 'Hello World!' },

{ user: 'des', category: 'welcome', text: 'Welcome to the blog' }

],

filter: 'hello'

}

We can reconstruct the whole application state from this object. Being able to do this

is one of the things that makes Redux so awesome.

We also need a way to change the state.

In Redux, we never modify the state directly.

Only actions can change the state

Redux actions are simply JavaScript objects, with a type property that specifies the name of the action.

We want to create a new post in our blog, we could use an action like this:

{ type: 'CREATE_POST', user: 'dan', text: 'New post' }

We could define another action for setting the filter:

{ type: 'SET_FILTER', filter: 'hello' }


These action objects can be passed to Redux, resulting in a new state being

calculated from the current state and the action. This process is called dispatching

an action.


The state is updated through special functions called reducers. Reducers contain the state changing logic of our application.

newState = reducer(state, action)

A reducer function takes the current state object and an action object as arguments.

The reducer parses the action object, specifically, the action.type. Depending on the

action type, the reducer function either returns a new state, or it simply returns the

current state (if the action type is not handled in this reducer).


A reducer function takes the current state and an action argument. For the state argument, we set a default value, which is what the initial state is going to be just like use a state having a first initial value


In our example application, the initial state is an empty array of posts, so we can define the reducer function, as follows:

function postsReducer (state = [], action) {

The most common way to handle

actions in Redux is using a switch statement on action.type. That way, we can have

separate cases for all the different action types that the reducer function is going to take care of:

switch (action.type) {

We handle the CREATE_POST action we defined earlier using

Array.concat to add the new post object to the state (an array of posts):

case 'CREATE_POST':

return state.concat([{ user: action.user, text: action.text }])

For all other action types, we simply return the current state:

default:

return state

}

If you do not return the current state for unhandled action types, your state will become undefined

To access the application state, Redux provides a .getState() function on the store object. You can view the full state, as follows:

console.log(store.getState())

The output of the preceding code will be the application state. In our example

application, the output would be the post array we defined earlier:

[

{ user: 'dan', text: 'Hello World!' },

{ user: 'des', text: 'Welcome to the blog' }

]


Given the same input, pure functions always return the same output. Because reducer

functions are pure, given the same state and action, they are always going to return the same new state

The following code defines an impure function, because subsequent calls with the

same input result in different output:

var i = 0

function impureCount () {

i += 1

return i

}

console.log(impureCount()) // prints 1

console.log(impureCount()) // prints 2

As you can see, we are accessing a variable outside of the function which is what makes the function impure.

We could make the function pure by specifying i as an argument:

function pureCount (i) {

return i + 1

}

console.log(pureCount(0)) // prints 1

console.log(pureCount(1)) // prints 2


Reducers in Redux are always pure functions. They take the previous state and an

action as arguments and return a new state object. The new part is important here. We

never modify the passed state directly, because that would make the function impure.

We always need to create a new state object based on the old state.


In our reducer function, we used Array.concat to create a new array from the old state

array, adding the new post at the end:

function postsReducer (state = [], action) {

switch (action.type) {

case 'CREATE_POST':

return state.concat([{ user: action.user, text: action.text }])

default:

return state

}

}


You might think that such a reducer function will become very complicated, as it

deals with the whole application state. Usually, you start out with a single simple

reducer. As your application grows, you can split it up into multiple smaller

reducers, each reducer dealing with a specific part of the application state. Because

reducers are just JavaScript function

Instead of directly modifying the previous state, a pure reducer function copies data from the

previous state to create a new state object

Let's define a simple action, in the src/index.js file:

const createPost = { type: 'CREATE_POST', user: 'dan', text: 'New post' }

console.log(createPost)

In Redux projects, we usually store the type property of the action in a constant, as follows:

const CREATE_POST = 'CREATE_POST'

const createPost = { type: CREATE_POST, user: 'dan', text: 'New post' }

console.log(createPost)

We can put the action types in a separate actionTypes.js file and export them

1. Create a new actionTypes.js file and define and export the action type there:

export const CREATE_POST = 'CREATE_POST'

2. Then, we can import the action types that we need in other files. Replace the

src/index.js file with the following code:

import { CREATE_POST } from './actionTypes'


Action creators are functions that return actions. They can be thought of as an action factory.

We tell the function what we want to do, and with which data We could replace our

createPost action with a dynamic createPost(user, text) action creator:

function createPost (user, text) {

return {

type: CREATE_POST,

user: user,

text: text

}

}

console.log(createPost('dan', 'New post'))

We can reuse this function to create similar actions

We will learn how to wrap action creators with the Redux dispatch() function. 

We could write the createPost(user, text) function in arrow function

syntax, as follows:

const createPost = (user, text) => {

return {

type: CREATE_POST,

user: user,

text: text

}

}

Additionally, ES2015 allows us to shorten property definitions where the object key

and constant name are the same (for example, user: user):

const createPost = (user, text) => {

return { type: CREATE_POST, user, text }

}


And we could return the object directly, by wrapping it with ( ) brackets:

const createPost = (user, text) => ({ type: CREATE_POST, user, text })


you can import/export action creators the same way as

action types. Perform the following steps:

1. Create a new actions.js file. First, we import the CREATE_POST action type:

import { CREATE_POST } from './actionTypes'

2. Then we define and export the createPost action creator: returning js object 

export const createPost = (user, text) => {

return { type: CREATE_POST, user, text }

}

3. Finally, we can import and use the createPost action creator in the src/index.js

file. Replace the whole file with the following code:

import { createPost } from './actions'

console.log(createPost('dan', 'New post'))


{

posts: [

{ user: 'dan', category: 'hello', text: 'Hello World!' },

{ user: 'des', category: 'welcome', text: 'Welcome to the blog' }

],

filter: 'hello'

}

As you can see, we have two separate sub-states in the main state object: posts and

filter. This is a good hint for making two reducers: a postsReducer to handle the

posts sub-state, and a filterReducer to handle the filter sub-state


There are no generic rules on how to split up reducers and your application state. It

depends on your projects' requirements and the existing structure.


Edit src/actionTypes.js and define and export the following action types:

export const EDIT_POST = 'EDIT_POST'

export const SET_FILTER = 'SET_FILTER'


ACTION CREATOR FILE

import { CREATE_POST, EDIT_POST, SET_FILTER } from './actionTypes'

export const createPost = (user, text) => {

return { type: CREATE_POST, user, text }

}

export const editPost = (id, text) => {

return { type: EDIT_POST, id, text }

}

export const setFilter = (filter) => {

return { type: SET_FILTER, filter }

}

Then import the additional action creators we defined in the src/index.js file:

import { createPost, editPost, setFilter } from './actions'


We will put our reducers into a separate file:

1. Create a new reducers.js file and import the action types we defined earlier:

import { CREATE_POST, EDIT_POST, SET_FILTER } from './actionTypes'

2. Then, define and export the reducer function:

export function postsReducer (state = [], action) {


We will learn how to split this reducers.js file into multiple files

and make a reducers/ folder

In the Redux world, it is best practice to use switch statements to handle multiple

action types. Feel free to use if/else statements instead, especially when a reducer

handles only a single action type:

case CREATE_POST: {

return ...

}

case EDIT_POST: {

return ...

}

Note how we don't use a string ('CREATE_POST'), but the action

type/constant (CREATE_POST).

3. Defining the default branch is very important: if you do not return the current

state for unhandled action types, your state will become undefined:

default:

return state

}

}


Using destructuring and the rest operator to parse the action

We can manually pull out properties from the action object, as follows:

case CREATE_POST: {

const type = action.type

...

}

Instead of that, we can use destructuring, which would make the code look like this:

case CREATE_POST: {

const { type } = action

...

}


const { type, user = 'anon' } = action

// instead of

const type = action.type

const user = action.user || 'anon'


It is also possible to rename properties, as follows:

const { type: actionType } = action

// instead of

const actionType = action.type

The preceding code will store action.type in the actionType constant.


You can collect the rest of the properties

into another object. This can be done with the rest operator. It works as follows:

const { type, ...post } = action


In this way, we separate type and post 

We can use the rest operator instead of manually collecting the rest of the properties:

const type = action.type

const post = {

user: action.user,

category: action.category,

text: action.text

}


For example, if we want to add a title to posts later, we could

simply add a title property to the action and it would already be in the post object

when using the rest operator we can use that knowledge to parse the CREATE_POST action type:

1. First, we will pull out the data that we need from the action. In this case, we

want to store everything—except for the type property—in a post object:

case CREATE_POST: {

const { type, ...post } = action

2. Then, we will insert this post object into our array and return the new state.

We use something similar to the rest operator—the spread

operator, which is basically the reverse of the rest operator. Instead of

collecting all properties of an object/elements of an array, it spreads them out:

return [ ...state, post ]

}


Using the spread operator achieves the same thing as using Array.prototype.concat(),

which merges two arrays and returns a new one:

like

return state.concat([ post ])

we will handle the EDIT_POST action type:

1. We start by parsing the action object:

case EDIT_POST: {

const { type, id, ...newPost } = action

This time, we will pull out the type and id properties, because we will

need the id later, and it should not be part of the post object.

We use the Array.prototype.map() function to return a new state array. If

you do not know this function, it has the following signature:

const newArr = arr.map(

(elem, index) => { ... }

)


The map function executes the callback function on each element and creates a new array

from the return values of that callback function. Basically, map lets us apply a function to all elements in an array.

perform some action defined in function on each element of the array and return an updated array

If we wanted to increase all numbers in an array by one, we

could use the map function, as follows:

const numbers = [1, 2, 3]

const newNumbers = numbers.map(

number => number + 1

)

console.log(newNumbers) // outputs: [2, 3, 4]


We can use map to edit a single post in the array, by going through all posts and

returning the original post object, except for the one element that matches the

index:

return state.map((oldPost, index) =>

3. First, we will make sure that the id value equals the index of the array because

we want to edit only one of the posts:

action.id === index which matches with action.id

Now, we can overwrite the oldPost properties with the newPost properties using

the spread operator as follows:

? { ...oldPost, ...newPost }

: oldPost

)

}

When the index value matches the id specified in the action, the post object will be

overwritten with all other properties from the action. When the index value doesn't

match, we simply return the oldPost.

we have not defined index in state but map function giving us index 


Imagine that we have the following posts:

posts: [

{ user: 'dan', category: 'hello', text: 'Hello World!' },

{ user: 'des', category: 'welcome', text: 'Welcome to the blog' }

]

We want to edit the first post, so we dispatch the following action:

{ type: 'EDIT_POST', id: 0, text: 'Hi World!' }


Our reducer would match the post with the 0 index in the array, which is the first

one. Then, we have the following values:

const oldPost = { user: 'dan', category: 'hello', text: 'Hello World!' }

const newPost = { text: 'Hi World!' }

We use the spread operator to combine the objects:

{ ...oldPost, ...newPost }


This means that it will first spread out all properties from the oldPost object:

{ user: 'dan', category: 'hello', text: 'Hello World!', ...newPost }

Now, all properties from the newPost object will get spread out, overwriting some of

the oldPost properties, and resulting in a new object:

{ user: 'dan', category: 'hello', text: 'Hi World!' }


EXAMPLE

Now that we have written our reducer function, we can test it out by passing data

directly to it. First, we will define the initial state, an empty array of posts:

const initialState = []

Now, we will need to define the action. To do this, we use the previously defined

createPost action creator:

const action = createPost('dan', 'New post')


Finally, we execute the reducer and log the result:

const newState = postsReducer(initialState, action)

console.log(newState)

The preceding code should have the following output:

[

{

"user": "dan",

"text": "New post"

}


Writing the filter reducerThe filterReducer only handles one action type, so an if/else statement can be used.

Define the reducer function in the src/reducers.js file:

function filterReducer (state = 'all', action) {

if (action.type === SET_FILTER) {

return action.filter

} else {

return state

}

}

This reducer function is pretty simple—it sets the filter state to whatever is entered in action.filter.

Here not replacing but returning the same filter that we pass through action just used that trick

Now, all that is left to do is combining these two reducers together to handle the

whole state and all actions of the application:

function appReducer (state = {}, action) {

return {

posts: postsReducer(state.posts, action),

filter: filterReducer(state.filter, action),

}

}


Redux provides a combineReducers helper function, which creates a function that

essentially does the same thing as the appReducer function we defined earlier:

1. Import the combineReducers helper function at the beginning of the src/reducers.js

file:

import { combineReducers } from 'redux'

2. Then call combineReducers with our reducer functions at the end of the

src/reducers.js file. It returns an appReducer function:

const appReducer = combineReducers({

posts: postsReducer,

filter: filterReducer,

})

export default appReducer


Now, you can import the reducer in the src/index.js file, as follows:

import appReducer from './reducers'

filterReducer function that takes care of handling the filter part of our state object. By default, we want to show all posts, so we set the default state to 'all'.

After combining our reducers, we can test them out by calling the appReducer:

1. First, we will initialize the reducers by passing an undefined state:

let state = appReducer(undefined, { type: 'INIT_ACTION' })

console.log('initial state:', state)

The resulting initial state looks as follows:

{

"posts": [],

"filter": "all"

}


Then, we will dispatch some actions by manually passing the current state and

the action object to the appReducer function. We will start with a createPost

action:

state = appReducer(state, createPost('dan', 'test'))

console.log('state after createPost:', state)

The state now looks like this:

{

"posts": [

{

"user": "dan",

"text": "test"

}

],

"filter": "all"

}


Then, we will edit the text of that post:

state = appReducer(state, editPost(0, 'edited post'))

console.log('state after editPost:', state)

This results in only the first post (index 0) getting changed:

{

"posts": [

{

"user": "dan",

"text": "edited post"

}


],

"filter": "all"

}


Finally, we will set the filter, which does not affect the posts array at all:

state = appReducer(state, setFilter('none'))

console.log('state after setFilter:', state)

The final state is as follows:

{

"posts": [

{

"user": "dan",

"text": "edited post"

}

],

"filter": "none"

}


Redux brings actions and reducers together.

It provides a store, which contains the application state and provides some functions to do the following:

Access the application state: store.getState()

Dispatch actions: store.dispatch(action)

Register listeners: store.subscribe(listener)


We will now cover how to use Redux to create a store from our appReducer, how to

listen to changes in the store, and how to dispatch actions to the store:

import { createStore } from 'redux'

it takes the first arguments

the main/root reducer, in our case, appReducer) as the first argument.

An initial state as the second argument--if no initial state is

specified, the store will be initialized by passing undefined to the

reducer

The createStore function returns the Redux store, which provides some

functions to access the state (store.getState()), listen to state changes

(store.subscribe(listener)), and dispatch actions (store.dispatch(action)):

let store = createStore(reducer, initialState)


We use the createStore function to create the store from our previously created

appReducer. Instead of manually calling the reducer, we replace the rest of the

code in src/index.js with the following:

let store = createStore(appReducer)


console.log(store.getState())

{

"posts": [],

"filter": "all"

}

const unsubscribe = store.subscribe(() => {

console.log('state changed:', store.getState())

})


You might have noted that this code does not produce any output yet. The

initialization process of the Redux store does not trigger a call to our subscribed

function. We first need to dispatch an action to see the state change being logged.


Now, there is only one thing left to do—dispatching actions to the store. You can

simply pass action objects to the store.dispatch() function:


store.dispatch({ type: 'CREATE_POST', user: 'dan', text: 'hello world' })

However, it is best practice to use action creators instead, as follows:

store.dispatch(createPost('dan', 'hello world'))

Dispatching an action will result in the state being changed (by executing the

reducer), and thus, the subscribed function being called and give change state as an output

then at the last step

unsubscribe()


Connect redux with react

Put the following code in the src/components/Post.jsx file

import React from 'react'

const Post = ({ user, text }) =>

<span><b>{user}</b> - {text}</span>

export default Post


In the src/index.js

import React from 'react'

import ReactDOM from 'react-dom'

import Post from './components/Post.jsx'

ReactDOM.render(

<Post user="dan" text="hello world!" />,

document.getElementById('root')

)

Create a new src/components/PostList.jsx file if we have multiple post then

The PostList component will take a posts array as a property and render multiple

Post components. We will use the map() function like

Here we will need more li tag to display more post when we will pass multiple post list from index.js

import React from 'react'

import Post from './Post.jsx'

const PostList = ({ posts }) =>

<ul>

{posts.map(

(post, i) =>

<li key={i.toString()}>

<Post {...post} />

</li>

)}

</ul>

export default PostList


Replace src/index.js with the following code:

import PostList from './components/PostList.jsx'

const posts = [

{ user: 'dan', text: 'hello world!' },

{ user: 'des', text: 'hi!' }

]

ReactDOM.render(

<PostList posts={posts} />,

document.getElementById('root')

)


Now we will connect redux to react

In this small example, we only need one container component, ConnectedPostList:

import React from 'react'

import PostList from '../components/PostList.jsx'

export default class ConnectedPostList extends React.Component {

constructor (props) {

super(props)

this.state = props.store.getState()

}


We continue with the componentDidMount() life cycle method, where we subscribe

to the store and call this.setState with the new Redux state:

componentDidMount () {

const { store } = this.props

this.unsubscribe = store.subscribe(() =>

this.setState({ ...store.getState() }) you can also console.log but we have to use in react component

)

}

store.subscribe() returns an unsubscribe() function, which can be used to

remove the subscription so,

componentWillUnmount () {

this.unsubscribe()

}

Finally, we write the render() method, which will return our component, passing all state down to it as properties:

render () {

return <PostList {...this.state} /> now displaying all value store in component store that taken from central store using ../components/PostList.jsx

}

}


In the main file src/index.js, we now import everything that we need, including

the ConnectedPostList component:

import { createStore } from 'redux'

import React from 'react'

import ReactDOM from 'react-dom'

import { createPost } from './actions'

import appReducer from './reducers'

import ConnectedPostList from './containers/ConnectedPostList.jsx'

Then, we create the Redux store:

let store = createStore(appReducer)


We dispatch a createPost action, so we can see a post:

store.dispatch(createPost('dan', 'hello world'))

After a second, we dispatch another createPost action:

setTimeout(() => store.dispatch(createPost('des', 'hi!')), 1000)


Finally, passing store as a props from index.js to ConnectedPostList then we render our ConnectedPostList component via ReactDOM:

ReactDOM.render(

<ConnectedPostList store={store} />,

document.getElementById('root')

)

We successfully connected React to Redux, without using bindings!

This is one above method using createStore 

Now we will see using another method by using function connect from react-redux using another example

 create cakeTypes.js

export const BUY_CAKE = 'BUY_CAKE'


create cakeActions.js 

import { BUY_CAKE } from './cakeTypes' it is action creator file

export const buyCake = (number = 1) => {

 return {

  type: BUY_CAKE,

  payload: number

 }

}


cakeReducer.js this is reducer.js you can make saparte folder for other reducer in reducer folder

import { BUY_CAKE } from './cakeTypes'

const initialState = {

 numOfCakes: 10

}

const cakeReducer = (state = initialState, action) => {

 switch (action.type) {

  case BUY_CAKE: return {

   ...state,

   numOfCakes: state.numOfCakes - action.payload

  }

default: return state

 }

}

export default cakeReducer

rootReducer.js here you can combine any number of reducer just like here iceCreamReducer userReducer

import { combineReducers } from 'redux'

import cakeReducer from './cake/cakeReducer'

import iceCreamReducer from './iceCream/iceCreamReducer'

import userReducer from './user/userReducer'

const rootReducer = combineReducers({

 cake: cakeReducer,

 iceCream: iceCreamReducer,

 user: userReducer

})

export default rootReducer


store.js to apply middleware and redux thunk is an action creator that returns a function to perform asynchronous dispatch if you want then

import { createStore, applyMiddleware } from 'redux'

import { composeWithDevTools } from 'redux-devtools-extension'

import logger from 'redux-logger'

import thunk from 'redux-thunk'

import rootReducer from './rootReducer'

const store = createStore(

 rootReducer,

 composeWithDevTools(applyMiddleware(logger, thunk))

)

export default store


Now we have to use provider to provide central state as a props to other components

App.js 

import React from 'react'

import { Provider } from 'react-redux'

import store from './redux/store'

import CakeContainer from './components/CakeContainer'

import HooksCakeContainer from './components/HooksCakeContainer' if you will use hooks we will see later


function App () {

 return (

  <Provider store={store}>

   <div className='App'>

     <CakeContainer />

    <HooksCakeContainer />

     </div>

  </Provider>

 )

}

export default App


Now final step

CakeContainer.js to display our data from a store in react app here used that function and concepts

1. react-redux provides a function called connect.

2. connect(mapStateToPropsFunc, mapDispatchToPropsFunc)

(Component)

3. mapstatetoProps function name can be anything,

argument- state(Application State)

whenever state changes prop's value will also change

if prop's values changes then our component will re-render

we return an object of props

4. mapDispatchToProps -

argument- dispatch function

dispatch function is used to dispatch an action

action object we get from action creators

return object of props


import React from 'react'

import { connect } from 'react-redux'

import { buyCake } from 'cakeActions'

function CakeContainer (props) {

 return (

  <div>

   <h2>Number of cakes - {props.numOfCakes} </h2>

   <button onClick ?={props.buyCake}>Buy Cake</button>

  </div>

 )

}

const mapStateToProps = state => {

 return {

  numOfCakes: state.cake.numOfCakes

 }

}

const mapDispatchToProps = dispatch => {

 return {

  buyCake: () => dispatch(buyCake())

 }

}


export default connect(

 mapStateToProps,

 mapDispatchToProps

)(CakeContainer)


Now using hooks third method all will be same only one above file CakeContainer.js will be change

HooksCakeContainer.js

import React from 'react'

import { useSelector, useDispatch } from 'react-redux'

import { buyCake } from 'cakeActions'


function HooksCakeContainer () {

 const numOfCakes = useSelector(state => state.cake.numOfCakes)

 const dispatch = useDispatch()

 return (

  <div>

   <h2>Number of cakes - {numOfCakes} </h2>

   <button onClick ?={() => dispatch(buyCake())}>Buy Cake</button>

  </div>

 )

}

export default HooksCakeContainer

Here useSelector hook can be used to select a part from state to use component here used in a place of mapStateToProps

useDispatch() in a place of mapDispatchToProps


Sometimes you also use useReducer for global state management 

import React, { useReducer } from "react";

// Defining the initial state and the reducer

const initialState = 0;

const reducer = (state, action) => {

switch (action) {

case "add":

return state + 1;

case "subtract":

return state - 1;

case "reset":

return 0;

default:

throw new Error("Unexpected action");

}

};


const App = () => {

// Initialising useReducer hook

const [count, dispatch] = useReducer(reducer, initialState);

return (

<div>

<h2>{count}</h2>

<button onClick ?={() => dispatch("add")}>

add

</button>

<button onClick ?={() => dispatch("subtract")}>

subtract

</button>

<button onClick ?={() => dispatch("reset")}>

reset

</button>

</div>

);

};

export default App;








要查看或添加评论,请登录

社区洞察

其他会员也浏览了