React note #18

Yechan Jeon·2022년 1월 1일
0
post-thumbnail

Replace redux with hooks


Redux is definitely strong and cool tool to manage actions and state.
However, you may want to keep your code more indepedently.
we can replace redux with several react hooks by merging those hooks into custom hook.

Build custom store hook

  1. Create store hook
import { useState } from "react";

let globalState = {};
let listeners = [];
let actions = {};

const useStore = () => {
  const setState = useState(globalState)[1];

  useEffect(() => {
    listeners.push(setState);

    return () => {
      listeners = listeners.filter((li) => li !== setState);
    };
  }, [setState]);
};

globalState is similar to context.
Any component which uses this custom hook can access this global state and this global state object will be shared by those components.

listeners array is set of functions which you can call to update all components that are using this custom hooks.

useEffect return a function as a clean up.
When a component mounnted, we will push setState to listeners, then when the component unmounted, Clean up function(filtering setState) will be executed and it will remove setState from inside.

const dispatch = (actionIdentifier) => {
    const newState = actions[actionIdentifier](globalState);
    globalState = { ...globalState, ...newState };

    for(let listener of listeners) {
        listener(globalState)
    }
  };

This dispatch function work process is same with redux things.
First, we get action identifier parameter to know which action we need.

Second, we extract that action from global actions object.

Third, we execute that action with globalState and assign it as new state.

Fourth, update existed global state to both old global state and new state.
Maybe you can ask 'Why we updated both old state and new state together?'
This behavior is exactily same with redux. Redux has only one big store and manage all states and actions in that store.
As redux does this, also in our custom hooks, we managing all states in global state.
That's why we updated old existed global state and new states together.

Fifth, loop listeneres array(this array has 'setState's as items) and give globalState to each listener(setState)

import { useState, useEffect } from "react";

let globalState = {};
let listeners = [];
let actions = {};

export const useStore = () => {
  const setState = useState(globalState)[1];

  const dispatch = (actionIdentifier) => {
    const newState = actions[actionIdentifier](globalState);
    globalState = { ...globalState, ...newState };

    for (let listener of listeners) {
      listener(globalState);
    }
  };

  useEffect(() => {
    listeners.push(setState);

    return () => {
      listeners = listeners.filter((li) => li !== setState);
    };
  }, [setState]);

  return [globalState, dispatch];
};

export const initStore = (userActions, initialState) => {
  if (initialState) {
    globalState = { ...globalState, initialState };
  }
  actions = { ...actions, ...userActions };
};

Here, we return globalState and dispatch function like redux and export initStore function to set actions later.

To configure store for specific usage, here is example.
Keep your eyes on how this code use actions, initStore and what it returns.

import { initStore } from "./store";

const configureStore = () => {
  const actions = {
    TOGGLE_FAV: (curState, productId) => {
      	//some logic
      };
      return { products: updatedProducts };
    },
  };
  initStore(actions, {'some initial state'})
   
};

We defined actions(TOGGLE_FAV) and use globalState as curState.
initStore takes the actions and some initial state.
Here we use productId as additional payload.
Go back to store.js and add payload as an argument so that we can give this payload to actions.

const dispatch = (actionIdentifier, payload) => {
    const newState = actions[actionIdentifier](globalState, payload);
    globalState = { ...globalState, ...newState };

    for (let listener of listeners) {
      listener(globalState);
    }
  };

use store hook

  1. Invoke configure in index.js
import React from "react";
import ReactDOM from "react-dom";

import { BrowserRouter } from "react-router-dom";

import "./index.css";
import App from "./App";
import configureStore from "./hooks-store/products-store";

configureStore();

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);
  1. state from useStore()
const Products = (props) => {
  const state = useStore()[0];
  console.log(state);
  return (
    <ul className="products-list">
      {state.products.map((prod) => (
        <ProductItem
          key={prod.id}
          id={prod.id}
          title={prod.title}
          description={prod.description}
          isFav={prod.isFavorite}
        />
      ))}
    </ul>
  );
};

In this 'state', we have products property. Why? It's because we set that products as an initial state in configureStore() and we invoke it in Index.js

  1. dispatch from useStore()
const ProductItem = (props) => {
  const dispatch = useStore()[1];
  const toggleFavHandler = () => {
    dispatch("TOGGLE_FAV", props.id);
  };

  return (
    <Card style={{ marginBottom: "1rem" }}>
      <div className="product-item">
        <h2 className={props.isFav ? "is-fav" : ""}>{props.title}</h2>
        <p>{props.description}</p>
        <button
          className={!props.isFav ? "button-outline" : ""}
          onClick={toggleFavHandler}
        >
          {props.isFav ? "Un-Favorite" : "Favorite"}
        </button>
      </div>
    </Card>
  );
};

In dispatch funcition, we give "TOGGLE_FAV" as action identifier and props.id as payload.
This information will go to store.js and interpreted as we defined in there.

profile
방황했습니다. 다시 웹 개발 하려고요!

0개의 댓글