2

I need to display a list of objects in my Explorer component.

In my app I use useReducer hook what wrapped by Context.

It works well when the data flow is in "input-mode" (when I update data in state). But it does not rerender the application after data was changed.

So, steps that I need to pass and get a positive result.

  1. Press btn with file icon (or folder icon). This btn call hook that take a look into state and make a decision: where this file or folder should be placed in my simple fs.
  2. Write file/folder name (this function doesn't exist yet).
  3. Apply name by press enter or click mouse out of input (the same as 2 step).

Currently, I try to create file/folder with hardcoded name for testing 1-step. And I expect that dispatch function pass data to the state and it would be updated and rerendered. All process runs well except of rerender.

I explain the flow of 1-st step.

  1. After I click btn, I call the func from hook for forming my instance.
  2. Then, the new instance saving into local useState.
  3. After local useState successfully was updated, I call dispatch in useEffect hook.
  4. In reducer I modify my state and return it.

After this steps I expect that my app will automatically rerendered, but it isn't.

Code snippets, step by step.

  1. First step.
  const handleFileClick = () => {
    formAnInstance('file');
    console.log('file btn click')
  };
  1. Second step.
// in useInstancesInteraction hook
const { state, dispatch } = useStateContext();
  const [instance, setInstance] = useState<IInstance>();

  const formAnInstance = (mode: Omit<Mode, 'root'>) => {
    if (
      typeof state?.currentFolder === 'undefined' ||
      state?.currentFolder === null
    ) {
      const target =
        mode === 'folder'
          ? (createInstance('folder', 'folder') as IInstance)
          : (createInstance('file', 'file') as IInstance);

      target['..'] = '/';
      setInstance(target);
    }

  };
  1. Third step.
// in useInstancesInteraction hook
useEffect(() => {
    const updateData = () => {
      if (dispatch && instance) {
        dispatch(createRootInstance(instance));
      }
    };

    updateData();
  }, [instance]);
  1. Fourth step.
export const initialState = {
  root: createInstance('/', 'root') as IInstance,
  currentFolder: null,
};

const reducer = (state = initialState, action: IAction) => {
  const { type, payload } = action;

  switch (type) {
    case ACTION_TYPES.CREATE_ROOT_INSTANCE:
      const myKey = payload['.'];
      Object.assign(state.root, { [myKey]: payload });
      console.log('Reducer', state?.root);

      return state;
    case ACTION_TYPES.CREATE_FILE:
      break;
    case ACTION_TYPES.UPLOAD_FILE:
      break;
    case ACTION_TYPES.RENAME_FILE:
      break;
    case ACTION_TYPES.DELETE_FILE:
      break;
    case ACTION_TYPES.CREATE_FOLDER:
      break;
    case ACTION_TYPES.RENAME_FOLDER:
      break;
    case ACTION_TYPES.DELETE_FOLDER:
      break;
    default:
      return state;
  }
};

Here how my context file look like:

import React, { useContext, useReducer } from 'react';
import { IContext } from './index.types';
import reducer, { initialState } from './reducer';

const StateContext = React.createContext<IContext>({
  state: undefined,
  dispatch: null,
});

const StateProvider = ({
  children,
}: {
  children: JSX.Element | JSX.Element[];
}) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <StateContext.Provider value={{ state, dispatch }}>
      {children}
    </StateContext.Provider>
  );
};

export default StateProvider;

export const useStateContext = () => useContext(StateContext);
  • Hi, it's really hard to tell from your snippets. How does `handleFileClick` even access `formAnInstance` - which from my understanding resides at a completely different location? How does `useStateContext` look like? Do you get the log message from the reducer at `CREATE_ROOT_INSTANCE`? Does a component actually depend on the `instance`, b/c otherwise there's no need to rerender. Did youy log and compare `instance` in that component? – NotX Dec 27 '22 at 10:48
  • @NotX, `formAnInstance` provided by hook which I invoke in `Explorer` component (hook located in other file). I edited post and added code snippet of my context file. Yes I get it, it shows me that state was modified and I see my instance in it. What do u mean? It depends on `state?.root` - object that symbolizes root folder. – Volodymyr Barybin Dec 27 '22 at 13:53
  • What I mean: if `Explorer` receives the changed state (and this can be validated by `console.log(state.root)` in this compoonent), then it's likely that the `Explorer` component is not using/ignoring the changes (and therefore now rerendering is required), or it uses themin some inconventional manner which won't trigger futher reredinerings, e.g. it just logs the new state and nothing else, it stores it in a `ref.current` value etc. It has nothing to do with the `dispatch` - since according to the logs it does it's job properly, i.e. informing `Explorer` about the new state. – NotX Dec 27 '22 at 18:12
  • @NotX, I don't use the `ref`. The `Explorer` component doesn't receive the modified state, this state stuck in `StateProvider` component. If we invoke `console.log(state.root)`. We will see modified state, but in provider value `value={{state, dispatch}}` lay the old version of it. – Volodymyr Barybin Dec 27 '22 at 18:54
  • Sry, I still don't get it. You dispatch the state update. As far as I understand, it reaches the `reducer` sucessfully, so `console.log('Reducer', state?.root);` show's the correct - updated - value, right? Then, virtual rerender is triggered. So, will you see the update if you log the `state` right under `const [state, dispatch] = useReducer(reducer, initialState)`? (Your last comment suggests that this is the part causing error, but I'm still not sure I'm getting it.) – NotX Dec 28 '22 at 00:51
  • @NotX yes, you're right, if i log the `state` right under `const [state, dispatch] = useReducer(reducer, initialState)` I will see that all is "ok" - the `state` was successfully updated. But when I check it in children components the `state` is in old version. – Volodymyr Barybin Dec 28 '22 at 14:04
  • Ok, I fixed it. My child components see the changes, but the react hooks such as `useEffect`, `useMemo` and `useCallback` ignoring this changes as dependencies. Currently, I do not know _how it works_, but when discovering more closely and find the answer I will leave it here. – Volodymyr Barybin Dec 29 '22 at 12:46
  • Great to hear! My first guess would be: you're using the dependencies (of `useEffect` etc.) wrongly. Make sure you depend on _all_ values you're using in there, including those used in methods you're calling inside `useEffect` etc. Also (even if is less likely to cause your current error), make sure your dependencies are only primitives, not functions or objects (or else it will trigger/render to often and might even cause infinite loops). If you're still stuck, maybe add the code where you're calling `useEffect` etc. and they won't update properly. – NotX Dec 29 '22 at 15:11
  • I with update. The fix was temporary, because when I added "delete" functionality to my app the list of _folders_ didn't change after action. I rewrote my custom simple "redux" what consist from `useReducer` + `Context` with original library `Redux` and now all is works. – Volodymyr Barybin Dec 31 '22 at 13:21

0 Answers0