Image

React redux

  1. Concept of redux

    What Is Redux?

    Redux is a state management library that helps you better manage state in your applications.

    The Redux library is not specific to React. It's a library that you can use in any other library or framework like Angular, Vue, and even vanilla JavaScript.

    But developers mostly use Redux when working with React.

    Redux provides a single store that you can use to manage a large amount of data.

  2. Note image
    Example 2 of Redux

    Core Components of Redux

    Redux introduces a couple of new components to an application to facilitate this state management.

    • ActionsActions are used to send data from the application to the Redux store. A typical action consists of the type property and an optional payload property.
    • Reducers — This is a function that accepts the current state and action as arguments and returns the updated state according to the action.
    • Store — This is the heart of Redux where the state is stored. The state is kept in plain JavaScript objects and arrays.
  3. Examples of redux

    index.js

    import { createStore } from "redux";
    
    const reducer = (state = 0, action) => {
      switch (action.type) {
        case "INCREMENT":
          return state + action.payload;
        case "DECREMENT":
          return state - action.payload;
        default:
          return state;
      }
    };
    
    const store = createStore(reducer);
    
    store.subscribe(() => {
      console.log("current state", store.getState());
    });
    
    store.dispatch({
      type: "INCREMENT",
      payload: 1
    });
    
    store.dispatch({
      type: "INCREMENT",
      payload: 5
    });
    
    store.dispatch({
      type: "DECREMENT",
      payload: 2
    });
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(
      <StrictMode>
        <h3>Open console to see the output.</h3>
      </StrictMode>,
      rootElement
    );
    

    package.json

    {
      "name": "react",
      "version": "1.0.0",
      "description": "React example starter project",
      "keywords": [
        "react",
        "starter"
      ],
      "main": "src/index.js",
      "dependencies": {
        "react": "17.0.2",
        "react-dom": "17.0.2",
        "react-scripts": "4.0.0",
        "redux": "4.1.0"
      },
      "devDependencies": {
        "@babel/runtime": "7.13.8",
        "typescript": "4.1.3"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject"
      },
      "browserslist": [
        ">0.2%",
        "not dead",
        "not ie <= 11",
        "not op_mini all"
      ]
    }
    

  4. Note image
    Example 2 of Redux

    How to Get Started with Redux

    Let's create a new React project so we can learn Redux basics.

    Execute the following command in the terminal/command prompt to create a new React project using create-react-app:

    npx create-react-app redux-demo
    

    npx in this case allows us to use the create-react-app npm package to create a new React project without installing it on our local machine.

    Once you've created the project, delete all the files from the src folder and create a new file index.js inside the src folder.

    Now open the terminal again and execute the following command from the redux-demo folder:

    npm install [email protected]
    

    The above command will install the Redux library with version 4.1.0 to use in your project (which is the latest version at the time of writing this article).

    How to Create the Redux Store

    In Redux, you use the store to manage and track the data that's changing in the application.

    To create a store, we need to import the createStore function like this:

    import { createStore } from 'redux';
    

    The createStore function accepts three arguments:

    • the first argument is a function that is normally known as a reducer (required)
    • the second argument is the initial value of the state (optional)
    • the third argument is an enhancer where we can pass middleware, if any (optional)

    Take a look at the below code:

    import { createStore } from 'redux';
    
    const reducer = (state, action) => {
      console.log('reducer called');
      return state;
    };
    
    const store = createStore(reducer, 0);
    

    Here, we've first defined a reducer function using ES6 arrow function syntax. You can use the normal function instead of the arrow function if you want.

    Inside the reducer function, we're logging some text to the console and then returning the value of the state from the function.

    Then we pass that reducer function to the createStore function as the first argument and 0 as the initial value of the state as the second argument.

    The createStore function returns a store that we can use to manage the application data.

    The reducer function receives state and action as the parameters.

    The initial value of the state which we passed as 0 for the createStore function is automatically passed as the value of the state parameter.

    But it's a much more common practice to initialize the state inside the reducer itself rather than passing it as a second argument to the createStore function like this:

    import { createStore } from 'redux';
    
    const reducer = (state = 0, action) => {
      console.log('reducer called');
      return state;
    };
    
    const store = createStore(reducer);
    

    Here, we're using ES6 default parameter syntax for initializing the state parameter to value 0.

    Once the store is created, we can use the subscribe method provided by the store to subscribe to the changes in the store as shown below:

    store.subscribe(() => {
      console.log('current state', store.getState());
    });
    

    Here, using the subscribe function, we're registering a callback function that will be called once the store is changed.

    And inside the callback function, we're calling the store.getState method to get the current value of the state.

    Now, open the src/index.js file and add the following contents inside it:

    import { createStore } from 'redux';
    
    const reducer = (state = 0, action) => {
      console.log('reducer called');
      return state;
    };
    
    const store = createStore(reducer);
    
    store.subscribe(() => {
      console.log('current state', store.getState());
    });
    

    Now, if you run the application by executing the npm start command from the terminal and access http://localhost:3000/, you will see the reducer called message printed in the console.

    This is because the reducer gets called immediately once we pass it to the createStore function.

    How to Change the Store

    Now we're done with creating the store. But the store is not much use to us right now. This is because the store is connected using the reducer function, but we have not added any code inside the reducer to manage the store. So let's do that.

    The only way to change the store is by dispatching actions.

    An action is an object sent to the store like this:

    store.dispatch({
      type: 'INCREMENT'
    })
    

    Here, we're calling the dispatch function available on the store to send an action with the type INCREMENT to the store.

    The dispatch function takes an object as a parameter which is known as an action.

    The action must have a type property as shown above. If you don't pass the type property then you will get an error.

    It's a common practice and recommended to specify the type value in uppercase.

    The type can be any operation you want to perform, like ADD_USER, DELETE_RECORD, GET_USERS and so on.

    If you have multiple words, you can separate them with underscores like this { type: 'INCREMENT_NUMBER' }.

    Now, open the index.js file and replace its contents with the following code:

    import { createStore } from 'redux';
    
    const reducer = (state = 0, action) => {
      if (action.type === 'INCREMENT') {
        return state + 1;
      } else if (action.type === 'DECREMENT') {
        return state - 1;
      }
    
      return state;
    };
    
    const store = createStore(reducer);
    
    store.subscribe(() => {
      console.log('current state', store.getState());
    });
    
    store.dispatch({
      type: 'INCREMENT'
    });
    
    store.dispatch({
      type: 'INCREMENT'
    });
    
    store.dispatch({
      type: 'DECREMENT'
    });
    

    Now, if you run the application by executing the npm start command from the terminal, you will see the following logs printed in the console:

    As you can see, for every action dispatched to the store, the store gets changed. So we're able to see the different values of the state in the console.

    In the above code, our reducer function looks like this:

    const reducer = (state = 0, action) => {
      if (action.type === 'INCREMENT') {
        return state + 1;
      } else if (action.type === 'DECREMENT') {
        return state - 1;
      }
    
      return state;
    };
    

    Whenever we call the store.dispatch function, the reducer function will be called. Whatever is returned from the reducer will become the new value of the store.

    So the first time we dispatch an action to the store like this:

    store.dispatch({
      type: 'INCREMENT'
    });
    

    the first if condition inside the reducer function will be executed. It will increment the state value to 1 which was initially initialized to 0 using ES6 default parameter syntax. Then it will be returned from the reducer function.

    Note that we're using the value of the state to calculate the new value and we're not modifying the original state value like this:

    if (action.type === 'INCREMENT') {
       state = state + 1;
       return state;
    } 
    

    So the above code is not correct, because in the reducer we should not modify the original state. Doing so will create issues in your application and so it's not recommended.

    And because we've added the store.subscribe function in the index.js file, we get notified about the changing store as we can see the logs in the console.

    So when we again call the dispatch with type INCREMENT, the first if condition will be executed again. So it'll add 1 to the previous state value which was 1, and the final state value will become 2.

    Then we're dispatching the DECREMENT action to the store like this:

    store.dispatch({
      type: 'DECREMENT'
    });
    

    which will execute the else condition inside the reducer and it will decrement the state value by 1 (so 2 - 1 will become 1).

    Note that, inside the reducer, we're also returning state at the end. So if none of the condition matches, the default previous state will be returned from the function.

    It's a common practice to use a switch statement inside the reducer instead of the if-else condition like this:

    const reducer = (state = 0, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return state + 1;
        case 'DECREMENT':
          return state - 1;
        default:
          return state;
      }
    };
    

    In addition to the type, we can also pass extra information as a part of the action.

    Replace the contents of the index.js file with the following code:

    import { createStore } from 'redux';
    
    const reducer = (state = 0, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return state + action.payload;
        case 'DECREMENT':
          return state - action.payload;
        default:
          return state;
      }
    };
    
    const store = createStore(reducer);
    
    store.subscribe(() => {
      console.log('current state', store.getState());
    });
    
    store.dispatch({
      type: 'INCREMENT',
      payload: 1
    });
    
    store.dispatch({
      type: 'INCREMENT',
      payload: 5
    });
    
    store.dispatch({
      type: 'DECREMENT',
      payload: 2
    });
    

    Now, if you run the application by executing the npm start command from the terminal, you will see the following logs printed in the console:

    Here, while dispatching an action to the store, we're passing a payload with some value which we're using inside the reducer to increment or decrement the store value.

    Here, we've used payload as a property name but you can name it whatever you want.

    Our reducer function looks like this now:

    const reducer = (state = 0, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return state + action.payload;
        case 'DECREMENT':
          return state - action.payload;
        default:
          return state;
      }
    };
    

    So when we dispatch actions with type INCREMENT like this:

    store.dispatch({
      type: 'INCREMENT',
      payload: 1
    });
    
    store.dispatch({
      type: 'INCREMENT',
      payload: 5
    });
    

    the following code from the reducer will be executed:

    return state + action.payload;
    

    This will first add 1 and then 5 to the previous value of the state, so we go from 1 to 6. And because of the DECREMENT action type:

    store.dispatch({
      type: 'DECREMENT',
      payload: 2
    });
    

    we go from 6 to 4. So the final value of the store will become 4.

    Ref : Redux for Beginners – Learn Redux Basics with Code Examples

  5. Note image
    Example 3 of Redux

    Simple example of React.js Redux

    Step 1 - Initialize App and Install Dependencies

    npx create-react-app rtk-todo --use-npm
    

    this step initializes a basic react-app for you using npm. Next you need to install the dependencies.

    npm install react-redux @reduxjs/toolkit
    

    now run this following command to start the development server

    npm start
    

    Step 2 - Setup Your Folder Structure

    Create a structure like this inside the src of your project, we will create our components in the components folder and put all store related stuff inside the redux folder.

    📦src
    ┣ 📂components
    ┃ ┣ 📜AddTodo.js
    ┃ ┣ 📜TodoItem.js
    ┃ ┗ 📜TodoList.js
    ┣ 📂redux
    ┃ ┣ 📜store.js
    ┃ ┗ 📜tasksSlice.js
    ┣ 📜App.js
    ┣ 📜index.css
    ┗ 📜index.js
    

    Step 3 - Redux

    Configuring a Redux is an extremely simple process with Redux toolkit. Let's start with writing our first slice.

    import { createSlice } from "@reduxjs/toolkit";
    
    export const tasksSlice = createSlice({
        name: "tasks",
        initialState:[],
        reducers:{
            addTask: (state, action)=>{
                const newTask = {
                    id: new Date(),
                    name: action.payload.task
                }
                state.push(newTask);
            },
            deleteTask: (state, action)=>{
                return state.filter((item) => item.id !== action.payload.id);
            }
        }
    });
    
    export const {addTask, deleteTask} = tasksSlice.actions;
    
    export default tasksSlice.reducer;
    

    Here we are using createSlice, it takes a 'slice name', 'initial state' and an object of reducers, and then generates corresponding action generators and action creators to reducers, and each reducer has access to the state and the action.

    Then using this we need to configure out store. For that we need to use configueStore. It is an abstraction over the standard Redux createStore function adding some defaults easing our setup process.

    import { configureStore } from "@reduxjs/toolkit";
    import taskReducer from "./tasksSlice";
    
    export default configureStore({
        reducer:{
            tasks: taskReducer,
        }
    });
    

    Here we pass the slice reducer we created.

    Note : If there is one function (like in our case), it is considered as the root reducer. If we have more than one a rootReducer is created behind the scenes, using the combineReducers functionality.

    Next we just use this store in our index.js, like we used to do in normal redux.

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import store from "./redux/store";
    import { Provider } from "react-redux";
    
    ReactDOM.render(
    	
    	<React.StrictMode>
    		<Provider store={store}>
    			<App />
    		</Provider>
    	</React.StrictMode>,
    	document.getElementById('root')
    );
    

    Step 4 - UI Components

    Before building any UI, we should always visualize our component tree. In this app too we would follow a structure something like this.

    App
    ┣ AddTodo
    ┗ TodoList
    ┗ TodoItem
    

    In App.js we just call our components and wrap them together.

    import React from 'react';
    import AddTodo from './components/AddTodo';
    import TodoList from './components/TodoList';
    
    const App = () => {
    	return (
    		<div className="app">
    			<h1 className="app-title">My Tasks</h1>
    			<AddTodo />
    			<TodoList />
    		</div>
    	);
    };
    
    export default App;
    

    Next, we make a way to enter the task and dispatch the addTask action.

    import React, { useState } from 'react';
    import { useDispatch } from "react-redux";
    import { addTask } from "../redux/tasksSlice";
    
    const AddTodo = () => {
    	const [value, setValue] = useState('');
    
    	const dispatch = useDispatch();
    
    	const onSubmit = (event) => {
    		event.preventDefault();
    
    		if(value.trim().length === 0)
    		{
    			alert("Enter a task before adding !!");
    			setValue("");
    			return;
    		}
    
    		dispatch(
    			addTask({
    				task: value
    			})
    		);
    
    		setValue("");
    	};
    
    	return (
    		<div className="add-todo">
    			<input
    				type="text"
    				className="task-input"
    				placeholder="Add task"
    				value={value}
    				onChange={(event) => setValue(event.target.value)}
    			></input>
    
    			<button className="task-button" onClick={onSubmit}>
    				Save
    			</button>
    		</div>
    	);
    };
    
    export default AddTodo;
    

    We can easily import the action from the slice and dispatch it using the useDispatch function.

    Now that we can add the task we need to display them. For that we can just access the state using useSelector and map over the todo list to display as we want.

    import React from 'react';
    import TodoItem from './TodoItem';
    import { useSelector } from "react-redux";
    
    const TodoList = () => {
    	const todos = useSelector((state)=>{
    		return state.tasks;
    	});
    
    	return (
    		<ul className="tasks-list">
    			{todos.map((todo) => (
    				<TodoItem id={todo.id} title={todo.name} completed={todo.status} />
    			))}
    		</ul>
    	);
    };
    
    export default TodoList;
    

    Almost done, we just need to display each task and call the deleteTask action just like we had called the addTask action.

    import React from 'react';
    import { useDispatch } from "react-redux";
    import { deleteTask } from "../redux/tasksSlice";
    
    const TodoItem = ({ id, title }) => {
    
    	const dispatch = useDispatch();
    
    	const removeTask=()=>{
    		dispatch(
    			deleteTask({
    				id: id
    			})
    		)
    	}
    
    	return (
    		<li className="task-item">
    			<div>
    				{title}
    			</div>
    			<div>
    				<button className="remove-task-button" onClick={()=>{
    					removeTask();
    				}}>Delete</button>
    			</div>
    		</li>
    	);
    };
    
    export default TodoItem;
    

    So now if we try to check the progress, we can see that we can add or delete any task and it looks like this, and its quite bad.

    So now we add the styles, for this tutorial I have only added basic CSS, but you guys can go wild, maybe use a UI framework like Antd or MaterialUI too, would surely look nice.

    body{
        background-color: #edf5d1;
        padding: 0px;
        margin: 0px;
        color: rgba(0,0,0,0.87);
        font-family: 'Roboto', sans-serif;
    }
    
    .app{
        text-align: center;
        background-color: white;
        height: auto;
        min-height: 100vh;
        width : 40%;
        margin: auto;
        padding : 30px;
        box-shadow: 2px 2px 20px #ececec;
    }
    
    .app-title{
        margin: 0;
        margin-bottom: 30px;
    }
    
    .add-todo{
        margin: 30px 0px;
    }
    
    .task-input{
        height : 45px;
        width : 300px;
        border : 1px solid #e4f2f7;
        border-radius: 6px;
        padding : 2px 10px;
        margin-right : 10px;
    }
    
    .task-input:focus {
        box-shadow: 0 0 8px rgba(142,228,175,0.6);
        outline: none;
    }
    
    .task-button{
        height : 49px;
        width : 100px;
        border : none;
        border-radius: 6px;
        background-color: #05386b;
        color:white;
    }
    
    .task-button:hover{
        cursor: pointer;
    }
    
    .tasks-list{
        list-style: none;
        margin: auto;
        padding : 0px;
    }
    
    .task-item{
        margin: 15px 0px;
        display: flex;
        justify-content: space-between;
        padding : 8px 40px;
    }
    
    .remove-task-button{
        background-color: #ac3b61;
        height: 32px;
        color: white;
        border-radius: 4px;
        width: 70px;
        border:none;
    }
    
    .remove-task-button:hover{
        cursor: pointer;
    }
    
  6. Note image
    Example 4 of Redux

    App.js

    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import * as toDoAction from './actions/toDoAction';
     
    class App extends Component {
     
      constructor(props){
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
        this.state = {
          name: ''
        }
      }
     
      handleChange(e){
        this.setState({
          name: e.target.value
        })
      }
     
      handleSubmit(e){
        e.preventDefault();
        let todo = {
          name: this.state.name
        }
        this.setState({
          name: ''
        });
        this.props.createToDo(todo);
      }
     
      // View ToDo List
      listView(data, index){
        return (
          <div className="row">
            <div className="col-md-10">
              <li key={index} className="list-group-item clearfix">
                {data.name}
              </li>
            </div>
            <div className="col-md-2">
              <button onClick={(e) => this.deleteToDo(e, index)} className="btn btn-danger">
                Remove
              </button>
            </div>
        </div>
        )
      };
     
      deleteToDo(e, index){
        e.preventDefault();
        this.props.deleteToDo(index);
      }
     
      // Render the Form
      render() {
        let name;
        return(
          <div>
            <h1>Simple To-Do Application</h1>
            <hr />
            <div>
              <h3>Add Task </h3>
              <form onSubmit={this.handleSubmit}>
                <input type="text" onChange={this.handleChange} className="form-control" value={this.state.name}/><br />
                <input type="submit" className="btn btn-success" value="ADD"/>
              </form>
              <hr />
              { <ul className="list-group">
                {this.props.todo.map((todo, i) => this.listView(todo, i))}
              </ul> }
            </div>
          </div>
        )
      }
    }
     
    // Manage Props
    const mapStateToProps = (state, ownProps) => {
      return {
        todo: state.todo
      }
    };
     
    const mapDispatchToProps = (dispatch) => {
      return {
        createToDo: todo => dispatch(toDoAction.createToDo(todo)),
        deleteToDo: index => dispatch(toDoAction.deleteToDo(index))
      }
    };
     
    export default connect(mapStateToProps, mapDispatchToProps)(App);
    

    toDoActions.js

    import * as actionTypes from './actionTypes';
     
    export const createToDo = (todo) => {
        return {
          type: actionTypes.CREATE_NEW_TODO,
          todo: todo
        }
    };
    
    export const deleteToDo = (id) => {
        return {
            type: actionTypes.REMOVE_TODO,
            id: id
        }
    };
    

aung thu oo

#HIRE ME

OPEN TO WORK

Ko Aung Thu Oo

Having over 15 years of working experience in Laravel, PHP, Flutter, Node.Js, Vue.JS, JAVA. Strong information technology professional with a degree i...

View detail