قالب وردپرس درنا توس
Home / Tips and Tricks / How to get started with Redux for JavaScript State Management – CloudSavvy IT

How to get started with Redux for JavaScript State Management – CloudSavvy IT



Redux logo

Redux is a permission management tool, built specifically for client-side JavaScript applications that are highly dependent on complex data and external APIs, and offers great developer tools that make it easy to work with your data.

What does Redux do?

Simply put, Redux is a centralized data warehouse. All your application information is stored in a large object. Redux Devtools makes it easy to visualize:

A visualized Redux data warehouse

This state is immutable, which is a strange concept at first, but is meaningful for some reason. If you want to change the state, you must send one action, which basically takes some arguments, forms a payload and sends it to Redux. Redux transfers current status to a reduce function, which modifies the existing state and returns a new state that replaces the current one and triggers a recharging of the affected components. For example, you can have a reducer to add a new item to a list or delete or edit one that already exists.

Doing it this way means you never get any undefined behavior with your app-modifying state at will. Because there is a record of each action and what it has changed, it allows time travel troubleshooting, where you can scroll back in your application state to troubleshoot what happens to each action (much like a git history).

A register of each action

Redux can be used with all front-end frames, but it is often used with React, and that is what we will focus on here. Under the hood, Redux uses React’s Context API, which works in the same way as Redux and is good for simple apps if you want to completely give up Redux. However, Redux’s Devtools are great for working with complex data, and are actually more optimized to prevent unnecessary renderings.

If you use TypeScript, things are much more complicated to get Redux strictly typed. Instead, you want to follow this guide, which uses typesafe-actions to handle the measures and reductions in a type-friendly way.

Structure your project

First you want to upload your folder structure. This is up to you and your team’s styling preferences, but there are basically two main patterns that most Redux projects use. The first is simply to split each file type (action, reducer, middleware, side effect) into its own folder, like this:

store/
  actions/
  reducers/
  sagas/
  middleware/
  index.js

However, this is not the best, as you often need both an action and reduction file for each feature you add. It is better to merge the folders for actions and reducers and divide them by function. In this way, each action and the corresponding reducer are in the same file. You

store/
  features/
    todo/
    etc/
  sagas/
  middleware/
  root-reducer.js
  root-action.js
  index.js

This clears the import, as you can now import both the actions and the reductions in the same statement with:

import { todosActions, todosReducer } from 'store/features/todos'

It’s up to you if you want to keep the Redux code in its own folder (/store in the examples above) or integrate it into the app’s root src folder. If you are already separating code per component and writing many custom actions and reductions for each component, you may want to merge /features/ and /components/ folders and store JSX components along with reduction code.

If you use Redux with TypeScript, you can add an additional file to each function folder to define your types.

Install and configure Redux

Install Redux and React-Redux from NPM:

npm install redux react-redux

You will probably want to too redux-devtools:

npm install --save-dev redux-devtools

The first thing you want to create is your store. Save this as /store/index.js

import { createStore } from 'redux'
import rootReducer from './root-reducer'

const store = createStore(rootReducer)

export default store;

Of course, your store will be more complicated than that when you add things like add-ons, middleware and other tools like connected-react-router, but this is all it takes for now. This file takes the root reducer and calls createStore() uses it, which is exported so that the app can be used.

Then we create a simple to-do list function. You probably want to start by defining the actions that this feature requires and the arguments that are sent to them. Create an /features/todos/ and save the following as types.js:

export const ADD = 'ADD_TODO'
export const DELETE = 'DELETE_TODO'
export const EDIT = 'EDIT_TODO'

This defines some string constants for the action names. No matter what information you send around, each action will have one type property, which is a unique string that identifies the action.

You do not need to have a type file like this, because you can only print the string name of the action, but it is better for interoperability to do it this way. For example, you can have todos.ADD and reminders.ADD in the same app, which saves the hassle of typing _TODO or _REMINDER each time you refer to an action for that function.

Then save the following as /store/features/todos/actions.js:

import * as types from './types.js'

export const addTodo = text => ({ type: types.ADD, text })
export const deleteTodo = id => ({ type: types.DELETE, id })
export const editTodo = (id, text) => ({ type: types.EDIT, id, text })

This defines some actions using the types from the string constants, which specify the arguments and the creation of payloads for each. These do not have to be completely static, as they are features – an example you can use is to set a runtime CUID for certain actions.

The most complicated piece of code, and where you implement most of your business logic, is in the reduction tools. These can take many forms, but the most common setting is with a switch statement that handles each case based on the type of action. Save this as reducer.js:

import * as types from './types.js'

const initialState = [
  {
    text: 'Hello World',
    id: 0
  }
]

export default function todos(state = initialState, action) {
  switch (action.type) {
    case types.ADD:
      return [
        ...state,
        {
          id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
          text: action.text
        }
      ]    

    case types.DELETE:
      return state.filter(todo =>
        todo.id !== action.id
      )

    case types.EDIT:
      return state.map(todo =>
        todo.id === action.id ? { ...todo, text: action.text } : todo
      )

    default:
      return state
  }
}

The state is sent as an argument, and each case returns a modified version of the state. In this example, ADD_TODO adds a new object to the state (with a new ID each time), DELETE_TODO deletes all objects with the specified ID and EDIT_TODO maps and replaces the text of the article with the specified ID.

The initial state should also be defined and sent to the reduction function as the default value for the state variable. Of course, this does not define your entire Redux state structure, only state.todos section.

These three files are usually separated into more complex apps, but if you want you can also define them all in one file, just make sure you import and export properly.

With that feature complete, let’s connect it to Redux (and to our app). IN /store/root-reducer.js, import todosReducer (and all other function reducers from /features/ folder) and then send it to combineReducers()and forms a top-level root level that is sent to the store. This is where you set the root state and make sure to keep each function in its own branch.

import { combineReducers } from 'redux';

import todosReducer from './features/todos/reducer';

const rootReducer = combineReducers({
  todos: todosReducer
})

export default rootReducer

Using Redux In React

Of course, none of this is useful if it is not connected to React. To do this, you must wrap your entire app in a vendor component. This ensures that the necessary permission and hooks are sent down to each component of your app.

IN App.js or index.js, wherever you have your root render function, turn your app into one and send it to the store (imported from /store/index.js) as a plug:

import React from 'react';
import ReactDOM from 'react-dom';

// Redux Setup
import { Provider } from 'react-redux';
import store, { history } from './store';

ReactDOM.render(
    
       
    
    , document.getElementById('root'));

You are now free to use Redux in your components. The simplest method is with functional components and hooks. For example, to send an action, use useDispatch() hook, which allows you to call actions directly, e.g. dispatch(todosActions.addTodo(text)).

The following containers have an input connected to the local React state, which is used to add a new todo to the state when a button is clicked:

import React, { useState } from 'react';

import './Home.css';

import { TodoList } from 'components'
import { todosActions } from 'store/features/todos'
import { useDispatch } from 'react-redux'

function Home() {
  const dispatch = useDispatch();
  const [text, setText] = useState("");

  function handleClick() {
    dispatch(todosActions.addTodo(text));
    setText("");
  }

  function handleChange(e: React.ChangeEvent) {
    setText(e.target.value);
  }

  return (
    
      

        

        
          Add New Todo
        

        
      
    
  ); } export default Home;

When you then want to use the data stored in the state, use useSelector hook. This takes a function that selects part of the permission for use in the app. In this case, it sets post variable to the current list of todos. This is then used to create a new todo object for each entry in state.todos.

import React from 'react';
import { useSelector } from 'store'

import { Container, List, ListItem, Title } from './styles'

function TodoList() {
  const posts = useSelector(state => state.todos)

  return (
    
      
        {posts.map(({ id, title }) => (
          
            {title} : {id}
          
        ))}
      
    
  );
}

export default TodoList;

You can actually create custom selector functions to handle this for you, saved in /features/ folder much like actions and reductions.

Once you have everything set up and calculated, you may want to investigate how to configure Redux Devtools, set up intermediate programs such as Redux Logger or connected-react-router, or install a side effect model like Redux Sagas.


Source link