REDUCKS

Michael Minchang Kim·2020년 7월 6일
0

How to setup Redux with Ducks

Folders

Using Redux with Ducks allows you to minimize the amount of files needed to organize the different parts that redux needs in order to operate. With ducks you only need 1 parent folder "Modules".
Within the modules folder exists an index.js file and 1 module file for each element to be stored.

How to Create a Module file

We will create a counter.js file within Modules to work on

Define action types

The first thing to do is define the different action types that will be used with the stored Element
For example within a simple couter file there will be two actions: Increase and Decrease

                      const INCREASE = "counter/INCREASE";
                      const DECREASE = "counter/DECREASE";

Since the name INCREASE or DECREASE can be used again in other modules, we include filename/ infront in order to make the action type unique. Since each file will have only one action of the same name, this naming technique prevents collision.

Create action functions

Action functions have to be created using the action types created above. Action types define what kind of action the action function will perform. Action functions can take in a parameter that contains information that can be returned along with the action type.

                export const increase = () => ({ type: INCREASE});
                export const decrease = () => ({ type: DECREASE});

Below is an example containing a payload

                let id = 3;
                export const insert = (text) => ({
                    type: INSERT,
                    payload: {
                        id: id++,
                        text,
                        done:false
                    }
                });

Data stored in local variables(payload.id) and preset data(payload.done) can be passed along with the parameter

Using redux-actions to increase Code Readability(ACTIONS)

First install in terminal

                $ yarn add redux-actions
                    or
                $ npm install --save redux-actions

Import createAction from redux-actions

		import { createAction } from 'redux-actions';

Using createAction eliminates the need to create an object that contains type and payload

                export const increase = createAction(INCREASE);
                export const decrease = createAction(DECREASE);

Below is an example containing a payload

				let id = 3;
                export const insert = createAction(INSERT,(text)=>({
                    id: id++,
                    text,
                	done:false
               	});
           *** when the parameter is passed along without change ***
              export const changeInput = createAction(CHANGE_INPUT, input => input);

Create initialState / Reducer function

Creating an intialState is very simple. Just define a const variable containing the state you need.

                        const initialState = {
                            number: 0
                        };

So how do you create a reducer function? The reducer function in ducks looks exactly the same.

                function counter(state = initialState, action){
                    switch () {
                        case INCREASE:
                            return {
                                number: state.number + 1
                            };
                        case DECREASE:
                            return {
                                number: state.number - 1
                            };
                        default:
                            return state;
                    }
                }
                
                export default counter;

Using redux-actions to increase Code Readability(REDUCERS)

Import handleActions from redux-actions

	    import { createAction, handleActions } from 'redux-actions';

handleActions is used to make the reducer function easier to read by eliminating the need to use the switch case.

            const counter = handleActions(
                {
                    [INCREASE]: (state, action) => ({ number: state.number + 1});
                    [DECREASE]: (state, action) => ({ number: state.number - 1});
                },
                initialState,
            );

            export default counter;

Additional tips to increase readability

    const todos = handleActions(
        {
            [CHANGE_INPUT]: (state, action) => ({...state, input: action.payload}),
            [INSERT]: (state, action) => ({...state, todos: action.payload})
        },
        initialState,
    );

In the case above, since all the payloads have the same name of action.payload, what the payload is refering to can get confusing.

    const todos = handleActions(
        {
            [CHANGE_INPUT]: (state, {payload:input}) => ({...state, input}),
            [INSERT]: (state, {payload:todo}) => ({...state, todo})
        },
        initialState,
    );

By using destructuring assignment you can replace state.payload with an element name that is substantially more comprehensible.

immer

When updating States through reducers, spread operators are often used. In other cases the map function and filter function is used to return a new element. However when a piece of data stored deep inside of an object needs to be accessed, the produce function from the immer library can be used.

import { produce } from 'immer';

*** using map function ***

[TOGGLE]: (state, { payload: id }) => ({
	...state,
    todos: state.todos.map(todo => 
    	todo.id === id ? {...todo, dont: !todo.done} : todo
    )
})

*** using produce ***

[TOGGLE]: (state, { payload: id }) => 
	produce(state, draft => {
    	const index = draft.todos.find(todo => todo.id === id);
        todo.done = !todo.done;
    })

How to import Actions and Reducers

import counter from './counter';
import { increase, decrease } from './counter';
			or
import counter, { increase, decrease } from './counter';

Using Hooks simplify redux application

Using Hooks allows you to eliminate the need to use connect, mapstatetoprops, etc. This makes the use of actions and store substantially simpler.

Access State using useSelector

You can acess states stored in Redux without having to use the connect function with useSelector

      import { useSelector, useDispatch } from 'react-redux';
      import { increase, decrease } from './modules/counter';

      const number = useSelector(state=>state.counter.number);

Dispatching actions using useDispatch

useDispatch allows you to activate the imported actions easily

      import { useSelector, useDispatch } from 'react-redux';
      import { increase, decrease } from './modules/counter';
    
      const number = useSelector(state=>state.counter.number);
      
      const onIncrease = () => dispatch(increase());
      const onDecrease = () => dispatch(decrease());

Caution

When using the connect function, even if the parent component rerenders, if the props of the currrent component doesn't change, the component doesnt rerender. However, when using the useSelector Hooks this isn't applied. Thus use React.memo for performance optimization.

import React from 'react';

(...)

export default React.memo(TodosContainer);

Example Code

import { createAction, handleActions } from "redux-actions";

let listCNT = 4;
let cardCNT = 10;

//Action variables
const ADD_Card = "lists/ADD_Card";
const ADD_LIST = "lists/ADD_LIST";
const DRAG_HAPPENED = "lists/DRAG_HAPPENED";

//Actions
export const addCard = createAction(ADD_Card, (listID, text) => ({
  text,
  listID,
}));

export const addList = createAction(ADD_LIST);

export const sort = createAction(
  DRAG_HAPPENED,
  (
    droppableIdStart,
    droppableIdEnd,
    droppableIndexStart,
    droppableIndexEnd,
    draggableId,
    type
  ) => ({
    droppableIdStart,
    droppableIdEnd,
    droppableIndexStart,
    droppableIndexEnd,
    draggableId,
    type,
  })
);

//Reducer
const initialState = [
  {
    title: "RFQ",
    id: `list-${0}`,
    cards: [
      {
        id: `card-${0}`,
        text: "we created 1st card",
      },
      {
        id: `card-${1}`,
        text: "we created 2nd card",
      },
    ],
  },

  {
    title: "OFFER",
    id: `list-${1}`,
    cards: [
      {
        id: `card-${2}`,
        text: "this is new one",
      },
      {
        id: `card-${3}`,
        text: "everything is going good, right?",
      },
      {
        id: `card-${4}`,
        text: "We will also make some change",
      },
      {
        id: `card-${5}`,
        text: "Trade Force, Vamos",
      },
    ],
  },

  {
    title: "ORDER",
    id: `list-${2}`,
    cards: [
      {
        id: `card-${6}`,
        text: "we created 1st card",
      },
      {
        id: `card-${7}`,
        text: "we created 2nd card",
      },
    ],
  },

  {
    title: "SHIPMENT",
    id: `list-${3}`,
    cards: [
      {
        id: `card-${8}`,
        text: "멋쟁이 아들",
      },
      {
        id: `card-${9}`,
        text: "we created 2nd card",
      },
    ],
  },
];

const listsReducers = handleActions(
  {
    [ADD_Card]: (state, { payload: cardinfo }) => {
      const newState = state.map((list) => {
        if (list.id === cardinfo.listID) {
          const newCard = {
            text: cardinfo.text,
            id: `card-${cardCNT}`,
          };
          cardCNT++;
          return {
            ...list,
            cards: [...list.cards, newCard],
          };
        } else {
          return list;
        }
      });
      return newState;
    },
    [ADD_LIST]: (state, { payload: listinfo }) => {
      const newList = { title: listinfo, cards: [], id: `list-${listCNT}` };
      listCNT++;
      return [...state, newList];
    },
    [DRAG_HAPPENED]: (state, { payload: draginfo }) => {
      const {
        droppableIdStart,
        droppableIdEnd,
        droppableIndexStart,
        droppableIndexEnd,
        draggableId,
        type,
      } = draginfo;

      const newState = [...state];

      // dragging lists around
      if (type === "list") {
        const list = newState.splice(droppableIndexStart, 1);
        newState.splice(droppableIndexEnd, 0, ...list);
        return newState;
      }

      // In the same list
      if (droppableIdStart === droppableIdEnd) {
        const list = state.find((list) => droppableIdStart === list.id);
        const card = list.cards.splice(droppableIndexStart, 1);
        list.cards.splice(droppableIndexEnd, 0, ...card);
      }

      // other list

      if (droppableIdStart !== droppableIdEnd) {
        // find the list where drag happened
        const listStart = state.find((list) => droppableIdStart === list.id);

        // pull out the card from this list
        const card = listStart.cards.splice(droppableIndexStart, 1);

        // find the list where drag ended
        const listEnd = state.find((list) => droppableIdEnd === list.id);

        // put the card in the new list
        listEnd.cards.splice(droppableIndexEnd, 0, ...card);
      }

      return newState;
    },
  },
  initialState
);

export default listsReducers;
profile
Soon to be the world's Best programmer ;)

0개의 댓글