Using Zustand hooks to update state are behind current string state by one character

142 views Asked by At
  1. I am using remotion/react and Zustand to create a captions editor. I have a text box which I want to reflect the video component. However, the text on the video being displayed (and console.log'ed) are always one character (or render?) behind.

For example, when the box says 'textt' the video component will display 'text'. If the box is 'texttt' the component will be 'textt'. It goes backwards to. If we turn 'texttt' -> 'textt', the component will reach 'texttt'. Why is this happening?

  1. The second issue is tied to the first. A new CaptionBox should appear when a new caption object is added. However, it only happens when we somehow force a re-render, by clicking the pause/play button of the player (which is connected to our captions object).

  2. When we click the pause/play button, the text displayed in the Player component is reset to the initial Zustand state, no longer reflecting our CaptionBoxes. It all also resets to initial state if we refresh the page. How to prevent that?

source code: https://github.com/polooner/makeklips/tree/main

function CaptionBox({ id, timeFrom, timeTo, text }: KeyFrame) {
  console.log(text);
  const [value, setValue] = useState(text);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  // const [cursorPosition, setCursorPosition] = useState(0);

  const { getKeyFrames, updateKeyFrame, setKeyFrames } = useMakeKlips();

  // const handleCursorChange = (e: KeyboardEvent) => {
  //   const input = textareaRef.current;
  //   if (input) {
  //     const cursorPosition = input.selectionStart;
  //     const textBeforeCursor = input.value.substring(0, cursorPosition);
  //     const lineCount = textBeforeCursor.split('\n').length;
  //     setCursorPosition(lineCount);
  //     console.log(cursorPosition);
  //   }
  // };

  //TODO: on backspace, pop the sentence and append to first if last character is the newline sequence (\r\n)
  //TODO: on enter, sentence into another array element
  const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
    //FIXME: text and other elements are a render behind

    console.log(e.target.value);
    setValue(e.target.value);
    console.log(e.target.value.includes('\n\n'));

    // if pushed a double new line, just cut at the end of double \n and create a new keyframe
    if (e.target.value.includes('\n\n')) {

      //FIXME: we see a new object in console but no new CaptionBox appears
      const valueCutIntoArray = e.target.value.split('\n\n');
      setValue(valueCutIntoArray[0]);
      updateKeyFrame(String(id), {
        text: valueCutIntoArray[0],
        timeFrom,
        timeTo,
        id,
      });
      //TODO: prevent user from infinitely pressing Enter
      console.log(valueCutIntoArray[0].length + valueCutIntoArray[1].length);
      setKeyFrames((prevKeyFrames) => {
        return prevKeyFrames.concat({
          id: prevKeyFrames.length + 1,
          text: valueCutIntoArray[1],
          timeFrom: 160,
          timeTo: 180,
        });
      });

      console.log(valueCutIntoArray);
    } else {
      updateKeyFrame(String(id), {
        text: value,
        timeFrom,
        timeTo,
        id,
      });
    }

    const frames = getKeyFrames();
    console.log(frames);
  };
  return (
    <div className='p-4 hover:bg-zinc-900 w-full rounded-2xl flex justify-between items-center !bg-zinc-900 flex-row'>
      <div style={{ direction: 'ltr' }} className='w-full'>
        <span
          suppressHydrationWarning
          className='text-xs px-2 font-sans block text-slate-500 font-display'
        >
          {timeFrom}-{timeTo}
        </span>

        {/* TODO: stretch textarea infinitely */}
        <textarea
          ref={textareaRef}
          className='resize-none w-full text-white bg-transparent overflow-hidden p-2 font-inherit'
          value={value}
          onChange={(e) => handleChange(e)}
          // onKeyUp={handleCursorChange}
          // onClick={handleCursorChange}
          // onSelect={handleCursorChange}
        />
      </div>
    </div>
  );
}

export default Captions;

Zustand state logic (hooks):

import { createWithEqualityFn } from 'zustand/traditional';

// import { applyNodeChanges } from '../utils/';
import getInitialState from './initialState';
import type { KeyFrame, MakeKlipsState } from '@/types/store';

const createMKStore = ({ keyFrames }: { keyFrames?: KeyFrame[] }) =>
  createWithEqualityFn<MakeKlipsState>(
    (set, get) => ({
      ...getInitialState({ keyFrames }),

      setKeyFrames: (keyFrames: KeyFrame[]) => {
        //TODO: implement lookup for perf
        // const { frameLookup } = get();

        // updateFrameLookup(frameLookup, keyFrames);

        set({ keyFrames });
      },

      updateFrame: (params) => {
        const { keyFrames } = get();

        const frameToUpdateIdx = keyFrames.findIndex((frame) => {
          frame.id === params.id;
        });

        //TODO: make params optional
        if (frameToUpdateIdx != -1) {
          if (params.id) keyFrames[frameToUpdateIdx].id = params.id;
          if (params.text) keyFrames[frameToUpdateIdx].text = params.text;
          if (params.timeFrom)
            keyFrames[frameToUpdateIdx].timeFrom = params.timeFrom;
          if (params.timeTo) keyFrames[frameToUpdateIdx].timeTo = params.timeTo;
          get().triggerKeyFramesChanges(keyFrames);
        } else {
          console.log('Could not find the frame to update');
        }
      },

      triggerKeyFramesChanges: (keyFrames) => {
        set({ keyFrames });
        // onNodesChange?.(changes);
      },
    }),
    Object.is
  );

export { createMKStore };
0

There are 0 answers