user178456
user178456

Reputation: 45

'TypeError: Cannot read properties of undefined' when accessing state member only when running on vitest

I am trying to write unit tests for my React application, built with react-query and ky. I am constantly faced with the error Error: Uncaught [TypeError: Cannot read properties of undefined (reading 'name')] when running vitest, but when I run my application on the browser (not in test mode) everything works fine, there are zero errors thrown.

The error is thrown in the following lines (in the JSX markup):

          <input
            type='text'
            value={activeData.page.name}    <== this line
            onInput={onPageNameChange}
            className='w-full h-full px-4 text-2xl'
            data-testid='page-name-input'
          />

This is my full code:

export default function NotebookPage() {
  const [activeData, setActiveData] = useImmer<ActiveData>({
    notebook: { id: -1, name: '', sections: [] },
    section: { id: -1, name: '', pages: [] },
    page: { id: -1, name: '', content: '' },
  });

  const initialDataQuery = useInitialData();

  useEffect(() => {
    if (initialDataQuery.data) {
      const data = initialDataQuery.data;
      setActiveData({
        notebook: data.notebook,
        section: data.section,
        page: data.page,
      });
    }
  }, [initialDataQuery.data]);

  const onContentChange = (content: string) => {
    setActiveData((draft) => {
      draft.page.content = content;
    });
  };

  const onPageNameChange = (e: FormEvent<HTMLInputElement>) => {
    const name = e.currentTarget.value;
    setActiveData((draft) => {
      draft.page.name = name;
    });
  };

  if (initialDataQuery.isFetching) {
    return <p>Loading...</p>;
  }

  if (initialDataQuery.isError) {
    return <p>An error has occurred: {initialDataQuery.error.message}</p>;
  }

  return (
    <div className='h-screen flex'>
      <div className='w-1/5'>
        <NavPane activeData={activeData} setActiveData={setActiveData} />
      </div>

      <div className='flex-1 flex flex-col'>
        <div className='h-16 border-b border-slate-300'>
          <input
            type='text'
            value={activeData.page.name}
            onInput={onPageNameChange}
            className='w-full h-full px-4 text-2xl'
            data-testid='page-name-input'
          />
        </div>

        <div className='h-[calc(100%-4rem)] flex'>
          <div id='editor-container' className='flex-1 border-r border-slate-300'>
            <EditorPane content={activeData.page.content} onContentChange={onContentChange} />
          </div>
          <div id='preview-container' className='flex-1'>
            <PreviewPane rawText={activeData.page.content} />
          </div>
        </div>
      </div>
    </div>
  );
}

It does not look like there is any way for activeData.page to be undefined; in fact activeData.section is also undefined when I inspected the state further (only activeData.notebook is normal). useInitialData returns a useQuery hook, with the queryFn being a simple ky.get(<url>) that returns { notebook, section, page }. I am using MSW to mock this endpoint, so it is not possible for it to return undefined.

This is the test I am writing:

test.only('can parse to markdown', async () => {
  const user = userEvent.setup();
  render(<NotebookPage />);

  await user.clear(await screen.findByTestId('editor'));
  await user.type(await screen.findByTestId('editor'), '# hello world');

  const h1 = (await screen.findByTestId('preview')).querySelector('h1');
  expect(h1).toBeTruthy();
  expect(h1).toHaveTextContent('hello world');
});

I have tried adding the following waitFor before doing my actions thinking maybe the DOM needs more time to update, but the same error happens.

  await waitFor(async () => {
    expect(await screen.findByTestId('editor')).toBeInTheDocument();
  });

If I keep re-running the test, it passes like ~30% of the time, which is puzzling. I have also checked all calls to setActiveData, and unless I am missing something, none of the calls were made with any undefined data.

Upvotes: 0

Views: 628

Answers (1)

user178456
user178456

Reputation: 45

I figured out the problem... Turns out it was the -1 ids when I defined my activeData state. In my child components I use activeData.notebook.id and activeData.section.id to fetch more data, but after useInitialData, the set state call hasn't fully resolved so -1 was used in my api calls. Since -1 isn't a proper id my api resolves to an empty array, which I did not check for and immediately tried to do array[0], hence causing undefined in my activeData state.

My next question now is how come in the development server when developing on the browser, this error isn't caught...

Upvotes: 0

Related Questions