Repository: excalidraw
Issue: Appstate does not properly updates
Labels: package:excalidraw

Hello!

I'm using Excalidraw npm package to structure a personal project and can't fix the issue of the video attached.

Code:

```ts
'use client';
import React, { useRef, useState, useEffect } from 'react';
import { useSearchParams } from 'next/navigation';

//* Components:
import { Excalidraw } from '@excalidraw/excalidraw';
import RabiscaDropdownMenu from './RabiscaDropdownMenu';
import RabiscaWelcomeScreen from './RabiscaWelcomeScreen';
import { PopUpType, PopUps } from '../website/PopUps';
import { Backdrops } from '../website/Backdrop';

//* Types:
import { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types/types';
import { ImportedDataState } from '@excalidraw/excalidraw/types/data/types';

//* Utils:
import * as jwtUtil from '../../utils/jwt';
import * as initialData from '../../utils/initialData';
import * as localStorageUtil from '../../utils/storage/local';
import * as serverStorageUtil from '../../utils/storage/server';

//* Constants:
import { Buttons } from '../website/Buttons';
import { SERVER_STORAGE_VALIDATIONS } from '../../constants/constants';

export default function Rabisca(): JSX.Element {
  const [isLoading, setIsLoading] = useState(false);

  const [isPopUpOpen, setIsPopUpOpen] = useState(false);
  const [popUpData, setPopUpData] = useState<IPopUpData>({
    type: 'success',
    message: '',
  });

  const [isBackdropOpen, setIsBackdropOpen] = useState(false);
  const [backdropData, setBackdropData] = useState<IBackdropData>({
    title: '',
    bodyText: '',
    buttonLabel: '',
    href: '',
    onButtonClick: () => console.log(''),
    onOutsideClick: () => console.log(''),
  });

  const searchParams = useSearchParams();
  const drawInfoToken = searchParams.get('draw');
  const drawInfo = jwtUtil.getTokenPayload(drawInfoToken as string);
  const excalidrawRef = useRef<ExcalidrawImperativeAPI>(null);

  /********************************************************************************************************************************
   *                                                            Methods                                                           *
   ********************************************************************************************************************************/
  //! It is impossible to update states inside this function.
  const onCanvaMount = () => {
    if (!drawInfoToken) return;

    const { at, drawId } = drawInfo;
    return initialData.generate(at, drawId);
  };

  const onCanvaChange = (
    elements: ImportedDataState['elements'],
    state: ImportedDataState['appState'],
    files: ImportedDataState['files']
  ) => {
    const drawId = drawInfo?.drawId ?? 'QUICK_TRIAL';

    localStorageUtil.saveDrawElements(elements, drawId);
    localStorageUtil.saveDrawSettings(state);
    localStorageUtil.saveDrawFiles(files, drawId);
  };

  const onLibraryChange = (items: ImportedDataState['libraryItems']) => {
    const drawId = drawInfo?.drawId ?? 'QUICK_TRIAL';

    localStorageUtil.saveDrawLibrary(items, drawId);
  };

  const saveProgress = async () => {
    setIsLoading(true);

    const serverStorageStatus = await serverStorageUtil.save(drawInfo);

    const backdropData: IBackdropData = {
      title: 'Parece que você não está autenticado(a)...',
      bodyText: 'Por favor, crie uma conta ou realize um novo login.',
      buttonLabel: 'Fazer isso!',
      href: 'https://rabisca.io/',
      onButtonClick: () => setIsBackdropOpen(false),
      onOutsideClick: () => setIsBackdropOpen(false),
    };

    switch (serverStorageStatus) {
      case SERVER_STORAGE_VALIDATIONS.ACCESS_TOKEN_NOT_FOUND:
        setBackdropData(backdropData);
        setIsBackdropOpen(true);
        break;
      case SERVER_STORAGE_VALIDATIONS.USER_NOT_FOUND:
        setBackdropData(backdropData);
        setIsBackdropOpen(true);
        break;
      case SERVER_STORAGE_VALIDATIONS.HTTP_ERROR:
        setPopUpData({
          type: 'error',
          message:
            'Houve um erro durante o salvamento. Por favor, tente novamente em alguns minutos.',
        });
        setIsPopUpOpen(true);
        break;
      default:
        setPopUpData({ type: 'success', message: 'Rabisco salvo com sucesso!', duration: 5000 });
        setIsPopUpOpen(true);
    }

    setIsLoading(false);
  };

  /********************************************************************************************************************************
   *                                                         useEffects                                                           *
   ********************************************************************************************************************************/
  useEffect(() => {
    if (!drawInfoToken) {
      setBackdropData({
        title: 'Use o quanto quiser, mas se liga...',
        bodyText: 'Para salvar seu rabisco, é necessário já ter uma conta na plataforma!',
        buttonLabel: 'Depois eu crio, me deixa testar!',
        onButtonClick: () => setIsBackdropOpen(false),
        onOutsideClick: () => setIsBackdropOpen(false),
      });

      setIsBackdropOpen(true);
      localStorageUtil.removeLastSave();
      return;
    }

    localStorageUtil.registerLastSave();
  }, []);

  useEffect(() => {
    if (drawInfoToken) {
      const millisecondsInterval: number = Number(process.env.NEXT_PUBLIC_AUTOSAVE_SECONDS) * 1000;
      const saveEachIntervalId = setInterval(saveProgress, millisecondsInterval);

      return () => {
        clearInterval(saveEachIntervalId);
      };
    }
  }, []);

  /********************************************************************************************************************************
   *                                                        Sub-Components                                                        *
   ********************************************************************************************************************************/
  const saveButton = (isMobile: boolean) =>
    isMobile ? <></> : <Buttons.Save isLoading={isLoading} onClick={() => saveProgress()} />;

  /********************************************************************************************************************************
   *                                                        Main Component                                                        *
   ********************************************************************************************************************************/
  return (
    <div style={{ height: '100vh', width: '100vw' }} className="custom-styles">
      <Excalidraw
        ref={excalidrawRef}
        langCode="pt-BR"
        initialData={onCanvaMount()}
        renderTopRightUI={(isMobile) => saveButton(isMobile)}
        onChange={(elements, state, files) => onCanvaChange(elements, state, files)}
        onLibraryChange={(items) => onLibraryChange(items)}
      >
        <RabiscaDropdownMenu onSaveToServerClick={() => saveProgress()} />
        <RabiscaWelcomeScreen />
      </Excalidraw>
      <PopUps.Standard
        type={popUpData.type}
        message={popUpData.message}
        autoHideDuration={popUpData.duration}
        isOpen={isPopUpOpen}
        onClose={() => setIsPopUpOpen(false)}
      />
      <Backdrops.Standard
        title={backdropData.title}
        bodyText={backdropData.bodyText}
        buttonLabel={backdropData.buttonLabel}
        isOpen={isBackdropOpen}
        href={backdropData.href}
        onButtonClick={() => backdropData.onButtonClick()}
        onOutsideClick={() => backdropData.onOutsideClick()}
      />
    </div>
  );
}

/********************************************************************************************************************************
 *                                                            Types                                                             *
 ********************************************************************************************************************************/
interface IPopUpData {
  type: PopUpType;
  message: string;
  duration?: number;
}

interface IBackdropData {
  title: string;
  bodyText: string;
  buttonLabel: string;
  href?: string;
  onButtonClick: () => void;
  onOutsideClick: () => void;
}
```

https://github.com/excalidraw/excalidraw/assets/83513721/39bd937a-26ac-4edb-8a99-d24f12ba2089
![image](https://github.com/excalidraw/excalidraw/assets/83513721/1fa4d4c2-0c04-45d3-aff0-916e03a9998f)

package version: `@excalidraw/excalidraw@0.15.2-3d57112`

More context: https://discord.com/channels/723672430744174682/1126982618106298368/1126983283331317820