panzerdp / dmitripavlutin.com-comments

7 stars 0 forks source link

react-usereducer/ #141

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

An Easy Guide to React useReducer() Hook

How to use React useReducer() hook to manage complex state: initial state, action object, dispatch, reducer.

https://dmitripavlutin.com/react-usereducer/

HugaidaS commented 2 years ago

I got a task at work to create a simple application with CRUD aka internal blog ( app inside the app ) and here we go - useReducer() exactly what was necessary!

I used the logic from another of your article, here is my code:

in the component

  const [posts, dispatch] = useReducer(reducer, []);
  const [newPost, setNewPost] = useState("");

my reducer

export default function reducer(state, action) {
  switch (action.type) {
    case "add":
      return [...state, action.post];
    case "delete":
      return state.filter((post) => post !== action.post);
    default:
      throw new Error();
  }
}

I think that I will recreate a new post as an object later with the "title" and "body" of the post.

Thank you for help in my learning!

panzerdp commented 2 years ago

@Ellinsa You're welcome!

sergioreynoso commented 2 years ago

This was concise and very clear. Thank you for another very helpful article.

Cary123 commented 2 years ago

Very helpful, thank you~

panzerdp commented 2 years ago

This was concise and very clear. Thank you for another very helpful article.

Glad you like it @sergioreynoso!

panzerdp commented 2 years ago

Very helpful, thank you~

You're welcome @Cary123!

Tritomit-AP commented 2 years ago

Hi Dmitri! I am controlling a popup from a wrapper component using useReducer() and sending in the data that I want to display on the popup using its payload from the children of this wrapper. And on this popup I usually have two buttons that need to do different things depending on the child that opened the popup. Can you please tell what would be the best way to: first, write the initial state of the reducere to accept a new function and second, what would be the best way to send this function in the payload. As of now, my initial reducer looks something in the lines of:

const [userActions, dispatch] = useReducer(reducer, [
    {
        popup: false,
         ...
        action: new Function()
    },
]);

my first case to get the data looks something like:

case "get-data":
    return {
        ...userActions,
        ...action.payload,
}

and from the child I'm setting the action and open the popup like:

props.dispatch({
    type: "get-data",
    payload: {
            popup: true,
            action: function() {
                return someFn(e)
            }
     }
})

and once the popup is open, when pressing confirm, I am calling the previously set function in a case like:

case "do-the-action":
    return {
        ...userActions,
        popup: false,
        action: userActions.action()
}

Does this make sense? It's working but also causing some troubles in some instances and I would really like to now a best approach. Thank you for taking the time to read this! Adrian

navdeepsingh commented 2 years ago

To modularise the main reducer function to use and expose different reducer functions, it will be like. It follows Adapter Pattern.

const mainReducer = (reducer) => {
  return (prevState, action) => {
    const nextState = reducer(prevState, action);
    return nextState;
  };
};

const todoReducer = mainReducer((prevState, action) => {
  const {type, payload} = action;
  switch(type) {
   case 'ADD' : ...
   case 'REMOVE' : ...
   default: throw new Error('Unhandled action');
  }
});

export {todoReducer}

Hope it makes sense too. Thanks Dmitri for being there, I love your blog posts, Keep it up.

ChristBM commented 2 years ago

The custom Hook

import React, {useReducer} from "react"

function reducer( state, action ) {
  switch( action.type ) {
    case 'toggle': return !state
    default: throw new Error()
  }
}

export function useMyState( initialState ){
  const [ state, dispatch ] = useReducer( reducer, initialState )
  return{ state, dispatch }
}

The React Component

import React from "react"
import { useMyState } from "../hooks/useMyState"

export function MyComponent(){
  const { state: sayHello, dispatch: setSayHello } = useMyState( false )

  return(
    <div>
      <p>{ sayHello ? "Hello" : "" }</p>
      <button onClick={ () => setSayHello( { type: 'toggle'} ) }>Greeting</button>
    </div>
  )
}
panzerdp commented 2 years ago

@ChristBM Thanks for trying the challenge, however the requirement is useMyState to implement the same API const [state, setState] = useMyState(initialState) as useState().

In your case useMyState() is just a toggler.

ZacheeNiyokwizera commented 2 years ago

Hi Hi Dmitri!, Thanks for sharing your skills with us,

would you please tell me what this code does ?

return () => { clearInterval(idRef.current); idRef.current = 0; };

they are inside the useEffect at line 19 on CodeSandBox. I as thinking if this code runs when the action type is "stop", is it true ?

Thanks !

panzerdp commented 2 years ago

would you please tell me what this code does ?

return () => { clearInterval(idRef.current); idRef.current = 0; };

@zackniyokwizera The code that you mention is the cleanup function of useEffect() hook.

This code will stop the watch if the component unmounts.

ZacheeNiyokwizera commented 2 years ago

I understand now, Thanks !

bluevoxInc commented 2 years ago

Here is my solution to your challenge:

Custom Hook

import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'set':
      return action.payload;
    default:
      throw new Error('invalid action.type');
  }
}

export function useMyState(initialState) {
  const [state, dispatch] = useReducer(reducer, initialState);

  const setState = (payload) => dispatch({ type: 'set', payload: payload });

  return [state, setState];
}

Component Use Case

import React from 'react';
import { useMyState } from './hooks/useMyState';

export function MyComponent() {
  const [state, setState] = useMyState(0);

  return (
    <div>
      <p>{state}</p>
      <button onClick={() => setState(state + 1)}>increment</button>
    </div>
  );
}
inimist commented 2 years ago

Great post! Especially the example "engine order telegraph" is very helpful to understand reducer.

@bluevoxInc's hook works for me! :)

panzerdp commented 2 years ago

Great post! Especially the example "engine order telegraph" is very helpful to understand reducer.

@bluevoxInc's hook works for me! :)

You're welcome @inimist!

mshakibdev commented 2 years ago

I've a question that , what is the use case of the dispatch function return by the useReducer ?

panzerdp commented 2 years ago

I've a question that , what is the use case of the dispatch function return by the useReducer ?

The dispatch() function dispatches actions.

james-transfire commented 2 years ago

Hi Dimitri,

Thank you so much for writing and providing these clear and detailed articles on React and JavaScript! I've been making a web app and back end in JavaScript and React and so often when I need to answer a question on how things work your website has the best answers. You saved me so much time and headache with your articles. Thank you again!

tigran-iii commented 2 years ago

I'm probably late to the party, but thanks for a great article! Loved the final little challenge too. Seems to work, haha.

import { useReducer } from 'react';

export const useMyState = (initialState) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const setState = (newState) => dispatch(newState);
  return [state, setState];
};

function reducer(state, action) {
  return typeof action === 'function' ? action(state) : action;
panzerdp commented 2 years ago

Hi Dimitri,

Thank you so much for writing and providing these clear and detailed articles on React and JavaScript! I've been making a web app and back end in JavaScript and React and so often when I need to answer a question on how things work your website has the best answers. You saved me so much time and headache with your articles. Thank you again!

You're welcome @james-transfire! Thanks for the nice words.

camden-kid commented 2 years ago

Really nice explanation. Thanks.

sotobry commented 2 years ago

Hello Dmitri,

This article is very clear. Thank you for that! I think I got it. I made my custom useState and pasted it below.

I thought using the switch statement was overboard, so I didn't use it. Also, I know many of my lines of code are not necessary, but I decided to still add them to clearly communicate to other developers what I was trying to do in as easy-to-understand as I could think of. Any advice on how to improve it would be great :)

const _useState = initialState => {
  const reducer = (currentState, action) => {
    const { type, payload} = action;
    const {cb, value} = payload;

    const updatedState = type === 'function' ?
      cb(currentState) :
      value;

    return updatedState;
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  let setStateTo = input => {
    const type = typeof input;
    const key = (type === 'function' ? 'cb' : 'value'); //cb: callback fn
    const payload = { [key]: input };

    dispatch({ type, payload });
  };

  return [state, setStateTo]
};

Also, if anyone would like to a junior Front-End React/JS Developer, please let me know. I'm looking for new opportunities.

AxelJarni commented 1 year ago

Hi, I'm new to React and was going through your article to get some information on the useReducer hook. Just a question though, on the code demo, shouldn't the "case 'reset'" be returning 'initialState' ? And if no, why ? Thanks

Gautam-ofc commented 1 year ago

Loved it. strict to point with simple examples

vignesh53 commented 1 year ago

Great article. Very clear explanation!!

dimitriost1 commented 1 year ago

@panzerdp

would you please tell me what this code does ?

return () => { clearInterval(idRef.current); idRef.current = 0; };

@zackniyokwizera The code that you mention is the cleanup function of useEffect() hook.

This code will stop the watch if the component unmounts.


Dimitri correct me if I'm wrong but I don't see in the app any component to unmount. No component of the app disappears from the screen after the initial render. I think that the clean up function is called after the stop and the reset button clicked, after the rerender of component and before the callback in useEffect called, because in this case the depedency of useEffect state.isRunning is updated.

dimitriost1 commented 1 year ago

My solution in the Challenge:

my custom hooks is useFakeUseState.js:

import { useReducer } from "react";

const useFakeUseState = (initialState) => {
  const reducer = (state, action) => {
    switch (action.type) {
      case "updateState":
        return action.newState;
    }
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  const setState = (newState) => {
    if (typeof newState === "function") {
        //if new state given literally
        dispatch({
            type: "updateState",
            newState: newState(state),
          });
    }else{
        //if new state given functionally
        dispatch({
            type: "updateState",
            newState: newState,
          });
    }

  };

  return [state, setState];
};

export default useFakeUseState;

Here is a live demo.

stackpointer1 commented 1 year ago

customhook import { useReducer, } from "react"; // console.log('>>>>>', useReducer); const getReducer = (data) => (state, action) => { // eslint-disable-next-line default-case switch (action.type) { case "start": return { ...state, isRunning: true, status: 'started' }; case "tick": return { ...state, timer: state.timer + 1, status: 'running' }; case "reset": return { ...state, isRunning: false, timer : 0, status: 'reset' }; case "stop": console.log('stopped'); return{...state, isRunning: false, status: 'freeze'}; case 'clear': return { ...state, status: '' };
} };

export function useForm(initialState, data = {}) { const [state, dispatch] = useReducer(getReducer(data), initialState); const handleStart = () => () => dispatch({ type: "start" });

const handleReset = () =>()=> { dispatch({ type: "reset"}); }; const handleStop = ()=> ()=>{ console.log('handle stop') dispatch({type:"stop"}) } const onTick =()=>{ dispatch({type:"tick"}) }

return { state, handleStart , handleReset, handleStop, onTick}; }

component

import "./App.css"; import { useEffect, useRef } from "react"; import { useForm } from "./hooks/use-formhook"; let initialState = { isRunning: false, timer: 0, }; // eslint-disable-next-line react-hooks/rules-of-hooks

function App() { const idRef = useRef(0); const { state, handleStart, onTick, handleReset, handleStop } = useForm( initialState );

useEffect(() => { console.log("state12", state); if (!state?.isRunning) { return; } idRef.current = setInterval(() => onTick(), 1000); return () => { clearInterval(idRef.current); }; }, [state?.isRunning]); return (

{state?.timer} sec

); }

export default App;

imbroisi commented 1 year ago

Hello Dimitri,

Thanks for the excellent article!

A doubt: it seems that we need to clear the timer (interval) every time we stop the clock inside the useEffect, isn't it? Otherwise, we will define a new interval every time we turn on (start) the clock. I wanted to say this:

  useEffect(() => {
    if (!state.isRunning) { 
      clearInterval(idRef.current); //  <---HERE
      return; 
    }
    ...

What do you think?

imbroisi commented 1 year ago

Oh, sorry, is DMITRI. I have a cousin named Dimitri :)

Waldemar-00 commented 12 months ago

Отличный пост! Особенно пример с моторным телеграфом!) Спасибо

steinhardt21 commented 3 months ago

One of the best content out there! Compliments Dmitri! Keep writing! I love your work