React Context with TypeScript

Let me show you that you can manage states on React without any additional library like Redux or MobX. It is simple and easy to learn.

Some years ago and even now, we found that some React developers included a state management library. The popular one is Redux. Some even started app development already with Redux installed. So, it is unnecessary now. React can build a complex app without any additional state management library like Redux or MobX. You can visit this page, https://reactjs.org/docs/faq-state.html#should-i-use-a-state-management-library-like-redux-or-mobx. It confirmed the statement.

React should not get started with an additional library to manage states. That is also not beginner-friendly because we need to learn how to use the additional one. Some said it is overkill, and I think that it will make the code more complex. It is better to get an approach with less effort and few libraries.

I also recommend moving to React Hook for better code if you are still using React Component. You can visit https://reactjs.org/docs/hooks-intro.html for more information about React Hook.

There are many examples of React Context out there on JavaScript. Here I implemented it to the previous Image Service that I made on TypeScript. I also show you how to test it.

createContext

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { createContext } from "react";
import { Props } from "../atoms/buttons/DownloadButton";

export type UploadPageContextType = {
  downloadList: Props[]
  setJPEG: (value: string) => void
  setWebP: (value: string) => void
}

const UploadPageContext = createContext<UploadPageContextType>({
  downloadList: [],
  setJPEG: (value: string) => {},
  setWebP: (value: string) => {},
})

export default UploadPageContext;

First, we need to create a context. Here is the upload page context with a list and two setters.

Provider

type Props = {
  title: string
  description: string
}

export default function UploadPage(props: Props) {
  let [downloadList] = useState<DownloadButtonProps[]>([])
  let [jpeg, setJPEG] = useState('');
  let [webp, setWebP] = useState('');

  return (
    <UploadPageContext.Provider value={{downloadList, setJPEG, setWebP}}>
      <div className="container mx-auto max-w-md my-8">
        <Title value={props.title} />
        <Description value={props.description} />
        <UploadForm />
        <DownloadList downloadList={downloadList} />
      </div>
      <ImageComparison jpeg={jpeg} webp={webp} />
      <Copyright />
    </UploadPageContext.Provider>
  )
}

In the upload page, we created 3 states; download list, JPEG, and WebP. We need setters for JPEG and WebP. But we didn't need that for the download list. Then we called the UploadPageContext.Provider. We put it on the root of the return and set the values.

useContext

export default function File() {
  let uploadPageContext = useContext(UploadPageContext);
  let handler = (file: any) => {
    if (file) {
      generateImages(file, uploadPageContext.setJPEG, uploadPageContext.setWebP, uploadPageContext.downloadList)
    }
  }

  return (
    <FileUploader handleChange={handler} types={fileTypes} maxSize={16} />
  )
}

At the bottom level, we called those states, a list, and two setters, with useContext, then used them. Above, I called them when generating images.

Test

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { fireEvent, render } from '@testing-library/react';
import UploadPageContext from '../../contexts/UploadPageContext';
import { Props } from "../../atoms/buttons/DownloadButton";
import _File from './File';

it('should contain a file input and succeed upload an image', () => {
  let downloadList: Props[] = []
  let setJPEG = (value: string) => {}
  let setWebP = (value: string) => {}
  let file = render(
    <UploadPageContext.Provider value={{downloadList, setJPEG, setWebP}}>
      <_File />
    </UploadPageContext.Provider>
  );
  let element = file.container.querySelector('input[type="file"]')!

  fireEvent.change(element, {
    target: {
      files: [new File(['(⌐□_□)'], 'chucknorris.png', {type: 'image/png'})],
    },
  })

  expect(file.container).toContainHTML('type="file"');
});

Because I worked with Atomic Design, I only needed to test the atom File.tsx with File.test.tsx. I renamed the File function to _File to avoid a conflict. I covered the _File with UploadPageContext.Provider and put dummy setters there.

Related Articles