React.js note #8

Yechan Jeon·2021년 12월 20일
0

React dev

목록 보기
8/18
post-thumbnail

Side Effects, Reducers & Context API


What is side effects?

Again, The main job of react is rendering UI and reacting to user input.
In addition, React evaluate and render JSX, manage states&props , react to events & input, re-evaluate component upon state & prop changes.

Therefore , side effects will be everything execpt for above react roles.
For example, Storing data, send http requests to server, set & manage times and other similar things.

When state changed, react re-run the code. If we make a side effect directly in root, It will definitely re-run our side effect again and may cause something we don't expect.

useEffect() hook

From these reasons, we gonna use 'useEffect()' hook.
useState() has two arguments.
First argument is a function which should be executed after every component evaluation if the specified dependencies changed
Second argument is the specified dependencies. These makes the function run only if the dependencies changed.

If there isn't dependencies, it will be executed whenever the app start.
If there is empty dependencies, it will be executed once when the app start.
If there is dependencies list, it will be excuted whenver the dependencies changed (you should add "everything" you use in the effect function as a dependency except for state update functions, built-in API or functions and variables & functions which are defined outside component)

Using useEffect() to clean up function

We can return a function in useEffect() and that is called by 'clean-up function'.
Clean-up function is not executed at the first time of side effect execution. After that, it will run before side effect execution run.
Let's take this example.

  useEffect(() => {
    const identifier = setTimeout(() => {
      console.log("checking form validity");
      setFormIsValid(
        enteredEmail.includes("@") && enteredPassword.trim().length > 6
      );
    }, 500);

    return () => {
      console.log("clean up");
      clearTimeout(identifier);
    };
  }, [enteredEmail, enteredPassword]);

In this code, i applied identifier which is prevent from tracking every user input and set timeout with 500 milliseconds. Also, there is a return of function (clean-up) which is clear timeout of our identifier.
As a result, whenever user type inside input, clean-up function continue to clear our identifier and when user stops typing, side effect execution will be run.

useReducer() hook

  • When we use?

This is one of the most important hooks.
multiple states -> buggy code -> alternative needed -> Reducer

For example,when email input state and email validity state are belong together Or if one state depends on other state value, it's time to use reducer.

  • How we use?

It looks like:

const [state, dispatchFn] = useReducer(reducerFn, initialState, initFn)
  • state : a current snapshot used in the component.
  • dispatchFn : A function that is used to dispatch a new action.
  • reducerFn : A function that is triggered automatically once an action is dipatched. it receives latest state and should return the new state
  • intialState: an initial state
  • initFn : a function to set the initial state

Let's see with this example.

const passwordReducer = (state, action) => {
    if (action.type === "USER_INPUT") {
      return { value: action.val, isValid: action.val.trim().length > 6 };
    }
    if (action.type === "INPUT_BLUR") {
      return { value: state.value, isValid: state.value.trim().length > 6 };
    }
    return { value: "", isValid: false };
  };

const [passwordState, dispatchPassword] = useReducer(passwordReducer, {
    value: "",
    isValid: false,
  });

 const passwordChangeHandler = (event) => {
    dispatchPassword({ type: "USER_INPUT", val: event.target.value });

    setFormIsValid(emailState.isValid && passwordState.value.trim().length > 6);
  };

 const validatePasswordHandler = () => {
    dispatchPassword({ type: "INPUT_BLUR" });
  };
  1. If you see the passwordReducer, There will be two argument(state and action). State will be current state and Action is dispatchFunction.
    At the first time , if there isn't any action , Reducer return no value and no valid.

  2. In passwordChangeHandler, i gave an object to dispatchFunction(action in reducer). In object, because type is 'USER_INPUT', when it goes into reducer, it will be catched by condition.

  3. validatePasswordHandler is also same mechanism.

useState vs useReducer

useState -> main state management, If there are independent pieces of state, If states updating are easy and limited to a few kind of updates

useReducer -> If you need more power for complex logic, if you dealing with related state inside component, if you have more complex state updates.

React context

  • When we use?
    In bigger app, There will be definitely demand on data of props between other routes.
    For example, In product route, user decide to buy an product and move it to a cart. To move it to a cart, we should send data to cart. While react doing so, there will be unneccesary data forwarding.
    In that case, we need to send our data directly to where it is needed.

  • How we use?

  1. make a context file
import React from "react";

const AuthContext = React.createContext({
  isLoggedIn: false,
});

export default AuthContext;
  1. provide it where it needed (you should wrap all the components which needs context)

Give provider the values you want to send

return (
    <AuthContext.Provider value={{ isLoggedIn: isLoggedIn }}>
      <MainHeader onLogout={logoutHandler} />
      <main>
        {!isLoggedIn && <Login onLogin={loginHandler} />}
        {isLoggedIn && <Home onLogout={logoutHandler} />}
      </main>
    </AuthContext.Provider>
  );
  1. Then you need to hook into it, and listen to it
    Import context and assign it as useContext(context name) to variable and then use it
import AuthContext from "../../context/auth-context";

const Navigation = (props) => {
  const ctx = useContext(AuthContext);

  return (
    <nav className={classes.nav}>
      <ul>
        {ctx.isLoggedIn && (
          <li>
            <a href="/">Users</a>
          </li>
        )}
        {ctx.isLoggedIn && (
          <li>
            <a href="/">Admin</a>
          </li>
        )}
        {ctx.isLoggedIn && (
          <li>
            <button onClick={props.onLogout}>Logout</button>
          </li>
        )}
      </ul>
    </nav>
  );
};

props -> To send data components
context -> To get data components in specific case

Or
Build a custom context file and manage your code leaner

import React, { useState, useEffect } from "react";

const AuthContext = React.createContext({
  isLoggedIn: false,
  onLogout: () => {},
  onLogin: (email, password) => {},
});

export const AuthContextProvider = (props) => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    const storedLoginData = localStorage.getItem("isLoggedIn");

    if (storedLoginData === "1") {
      return setIsLoggedIn(true);
    }
  }, []);

  const logoutHandler = () => {
    localStorage.setItem("isLoggedIn", "0");
    setIsLoggedIn(false);
  };

  const loginHandler = () => {
    localStorage.setItem("isLoggedIn", "1");
    setIsLoggedIn(true);
  };
  return (
    <AuthContextProvider
      value={{
        isLoggedIn: isLoggedIn,
        onLogout: logoutHandler,
        onLogin: loginHandler,
      }}
    >
      {props.children}
    </AuthContextProvider>
  );
};

export default AuthContext;

Ref: react.js docs

Context limitations

It is not optimized for high frequency changes. -> Redux solve this.

Rules of hooks

  1. Call hooks only in react component.
  2. Do not nest hooks and Do not invoke hooks in block.

Ref : React - The complete guide

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

0개의 댓글