'Rerendering component and useReducer initialization

EDIT: below the line is the initial problem where I asked if my whole architecture was fine. I later edited the topic (and title) to go straight to what was my issue in order to help the community. Therefore, you might find what you want by jumping directly to the answer while skipping the main post


I am new to react and I am encountering issues back to back. I suspect that something is wrong in my pattern and would really love to have it criticized by someone a bit stronger.

Component A:

It renders (among others) a Component B

Component A has a useState hook on an array of C_obj it renders, called C_obj_arr

const [C_obj_arr, ASetter] = useState<C_obj[]>()

It provides this C_obj_arr and ASetter to Component B as properties to allow the data to go back up.

Component B
It renders each C_obj of the C_obj_arr in a list of Component C.

It has a useReducer hook that controls the states of C_obj_arr

const [C_obj_array, dispatch] = useReducer(reducer, props.C_obj_array);

It has a useEffect hook such that if C_obj_arr changes, data goes back up to Compoennt A

useEffect(() => {
      ASetter(C_obj_array);
  }, [C_obj_array, ASetter]); 

Question: Is it fine so far to use the prop like this to init the reducer?

it also uses a useCallback hook to wrap a function that will allow getting the data back from Component C

  const fn_callback = useCallback(
    (c: C_obj) =>
      dispatch({
        kind: Kind.AN_ACTION,
        payload: { wells_plan: c },
      }),
    []
  );

Component C

It has another useReducer that controls the states of C_obj

const [C_obj, dispatch] = useReducer(reducer, props.C_obj);

To send the information back up to Component B, it uses the function fn_callback, created in B thanks to a useEffect hook with dep on C_obj

  useEffect(() => {
    props.fn_callback(C_obj);
  }, [C_obj, props.fn_callback]);

I hope it is not a total brain schmuck to read, I am very new to all of that so I understand I can be doing something totally broken by design. Many thanks for help

EDIT: as requested, here is a block of code to synthetize


const A = (): JSX.Element => {
  const [C_obj_arr, ASetter] = useState<C_obj[]>();

  return (
    <>
      <B>C_obj_arr=C_obj_arr ASetter=ASetter</B>
    </>
  );
};

const B = (C_obj_arr_init: C_obj[], ASetter: () => void): JSX.Element => {
  const [C_obj_array, dispatch] = useReducer(reducer, C_obj_arr_init);

  useEffect(() => {
    ASetter(C_obj_array);
  }, [C_obj_array, ASetter]);

  const fn_callback = useCallback(
    (c_obj: C_obj) =>
      dispatch({
        kind: Kind.UPDATE_OBJ,
        payload: { wells_plan: c_obj },
      }),
    []
  );

  return C_obj_array.map(C_obj => (
    <C C_obj={C_obj} fn_callback={fn_callback}></C>
  ));
};

const C = (C_obj_init, fn_callback): JSX.Element => {
  const [C_obj, dispatch] = useReducer(reducer, C_obj_init);

  useEffect(() => {
    fn_callback(C_obj);
  }, [C_obj, fn_callback]);

  return <div>{C.toString()}</div>;
};



Solution 1:[1]

I assume, that you mean

import { useState, useEffect, useReducer, useCallback } from "react"

type SomeObj = {
  name: string
  key: string
}

const A = (): JSX.Element => {
  const [items, setitems] = useState<SomeObj[]>([
    {
      key: "a",
      name: "hello"
    }
  ])

  return (
    <>
      <B items={items} setitems={setitems} />
    </>
  )
}

const B = ({ items, setitems }: { items: SomeObj[]; setitems: (x: SomeObj[]) => void }): JSX.Element => {
  const [items2, dispatch] = useReducer(
    (
      x: SomeObj[],
      a: {
        kind: string
        payload: {}
      }
    ) => {
      return x
    },
    items
  )

  useEffect(() => {
    setitems(items2)
  }, [items2, setitems])

  const fn_callback = useCallback(
    (item: SomeObj) =>
      dispatch({
        kind: "update",
        payload: { wells_plan: item },
      }),
    []
  )

  return (
    <div>
      {items2.map((item) => (
        <C itemInit={item} fn_callback={fn_callback} key={item.key}></C>
      ))}
    </div>
  )
}

const C = ({ itemInit, fn_callback }: { itemInit: SomeObj; fn_callback: (c_obj: SomeObj) => void }): JSX.Element => {
  const [item, dispatch] = useReducer((x: SomeObj) => x, itemInit)

  useEffect(() => {
    fn_callback(item)
  }, [item, fn_callback])

  return <div>{item.name}</div>
}

function App() {
  return (
    <main>
      <A></A>
    </main>
  )
}

export default App

And, I think, it's basically the reason for using global/atom state managers to react like: redux, recoil, jotai, etc.

In your scenario, you have a couple of prop passing layers through components, and only if I understand correctly, you try to take up changes from deeply nested component.

Using global state, you can have only one array and source of truth, and only one handler in each child component. That probably will remove all unnecessary hooks

Solution 2:[2]

The missing bolt for this thing to work was to handle the rerendering of component C. The problem was that useReducer initial state is applied only once, then not when the component is rendered. Therefore, there is a need to force the update of the obj_c when re-rendering it. To do that, I added an extra dispatch action in the Component C body.

useEffect(() => {                                                                                                                                                                           
    dispatch({                                                                                                                                                                                
      kind: Kind.UPDATE,                                                                                                                                                                      
      payload: {                                                                                                                                                                              
        obj_C: C_obj_init,                                                                                                                                                                        
      },                                                                                                                                                                                      
    });                                                                                                                                                                                       
  }, [C_obj_init]);

which associated case in the reducer is

  switch (action.kind) {                                                                       
    case Kind.UPDATE:                                                                          
      return action.payload.obj_C;

An important thing point here is to not return a recreated obj_C, this wouldlead to an infinite loop to this other hook seen earlier:

  useEffect(() => {
    fn_callback(C_obj);
  }, [C_obj, fn_callback]);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Фарид Ахмедов
Solution 2