How to split state to avoid rerender in react app

200 Views Asked by At

I'm building a block based editor from scratch. I have the model as

initialState = {
id,
name,
blocks: [{
id,
content: [{text: '', attributes: []}]
}]
}

In My React Editor is a component that will have this state in the useReducer.

const Editor: React.FC<Props> = ({ document }: Props) => {

  const reducer = (state: any, action: any) =>
    produce(state, (draft: any) => {
      switch (action.operation) {
        case ReducerActions.create: {
          const index = draft.blocks.findIndex(
            (block: any) => block.id === action.block.prevBlock,
          );
          draft.blocks.splice(index + 1, 0, action.block);
          break;
        }
        case ReducerActions.delete:
          draft.blocks = draft.blocks.filter(
            (block: any) => block.id !== action.blockId,
          );
          break;
        case ReducerActions.updateContent: {
          draft.blocks = draft.blocks.map((block: any) => {
            if (block.id === action.blockId) {
              return { ...block, content: action.content };
            }
            return block;
          });
          break;
        }
      }
    });

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

  const handleOnInput = (block: any, blockAction: any) => {
    switch (blockAction.action) {

      case BlockOperations.backSpace: {
        const index = state.blocks.findIndex(
          (elem: any) => elem.id === block.id,
        );
        if (index === 0 || index === -1) {
          return;
        }
        const prevBlock = { ...state.blocks[index - 1] };
        prevBlock.content = [...prevBlock.content, ...block.content];
        dispatch({
          operation: ReducerActions.updateContent,
          blockId: prevBlock.id,
          content: prevBlock.content,
        });
        dispatch({
          operation: ReducerActions.delete,
          blockId: block.id,
        });
        break;
      }
    }
  };

  return(
    <div>
      <Toolbar/>
      <div id="editor">
        {state.blocks.map((block: any) => (
          <div key={block.id} id={block.id}>
            <Block
              key={block.id}
              block={block}
              onInput={handleOnInput}
              onSelect={handleSelect}
            />
          </div>
        ))}
      </div>
    </div>
  )
}

So I was memoizing Block component and wrapped all the props for block in useCallback to avoid reinitializing.

But the problem started with OnInput backspace case where i need to access the other blocks content and frame the new content. So i need the latest state that forced me to remove the useCallback and that makes all the changes to state rerender the whole block list.

Is there any way that we could split the state in better way and avoid rerendering of all blocks?

One approach that i have in my mind is let the blocks manage their state and only on blur of editable element will update the state in the parent block. But that would affect us in future where we might need collaboration or we could make the api calls directly in the block content to update the particular block. Since the backend schema is structured in such a way that

documentModel = {
  id,
  name,
  blockIds,
}

blockModel = {
  id,
  type,
  content,
}

Thanks for the help.

0

There are 0 best solutions below