React context with generics
Asked Answered
C

2

6

How can we use generics in React.CreateContext?

I had this:

interface Props {
}

export interface SearchContextProps {
  dtos: any[];
}

export const SearchContext = React.createContext<SearchContextProps>({
  dtos: []
});

const SearchPage: React.FunctionComponent<Props> = (props) => {
  const [dtos, setDtos] = React.useState<any[]>([]);

  const searchContext = {
    dtos: dtos
  };

  return (
    <SearchContext.Provider value={searchContext}>
      ...
    </SearchContext.Provider>
  );
}

export default SearchPage;

Now I want to use generics, so I would write something like:

interface Props<T> {
}

export interface SearchContextProps<T> {
  dtos: T[];
}

export const SearchContext = React.createContext<SearchContextProps<T>>({
  dtos: []
});

const SearchPage = <T extends object>(props: Props<T>) => {
  const [dtos, setDtos] = React.useState<T[]>([]);

  const searchContext = {
    dtos: dtos
  };

  return (
    <SearchContext.Provider value={searchContext}>
      ...
    </SearchContext.Provider>
  );
}

export default SearchPage;

but I am missing how can I get to work the line:

export const SearchContext = React.createContext<SearchContextProps<T>>({

how can I use generics here, how can I have access to T?

I tried to move context inside the component:

interface Props<T> {
}

export interface SearchContextProps<T> {
  dtos: T[];
}

const SearchPage = <T extends object>(props: Props<T>) => {
  const [dtos, setDtos] = React.useState<T[]>([]);

  const SearchContext = React.createContext<SearchContextProps<T>>({
    dtos: [],
  });

  const searchContext = {
    dtos: dtos,
  };

  return (
    <SearchContext.Provider value={searchContext}>
      ...
    </SearchContext.Provider>
  );

}

export default SearchPage;

but now I don't know how to export it.

Any help?

Cypher answered 17/3, 2020 at 15:39 Comment(2)
Hi Simone, I am currently dealing with the same topic. Have you figured out a solution yet?Fridafriday
Yes, see my own answer.Cypher
C
2

I ended up with:

import * as React from 'react';

interface Props<T> {}

export interface SearchContextProps<T> {
  dtos: T[];
}

export const SearchContext = React.createContext<SearchContextProps<any>>({
  dtos: [],
});

const SearchPage = <T extends object>(props: React.PropsWithChildren<Props<T>>) => {
  const [dtos, setDtos] = React.useState<T[]>([]);

  const searchContext = {
    dtos: dtos,
  };

  return <SearchContext.Provider value={searchContext}>...</SearchContext.Provider>;
};

export default SearchPage;

And the consumer

const searchContext = React.useContext<SearchContextProps<Interfaces.FileDto>>(SearchContext)
Cypher answered 31/3, 2020 at 11:2 Comment(3)
Can you please add an example of SearchContext.Consumer?Birthstone
React.useContext<SearchContextProps<SomeSpecificInterface>>(SearchContext)Cypher
Thank you, op! Very simple and for my case I had a lot of properties in my Context type that weren't affected by the generic type, so in the components that only used those non generic properties from the context I didn't even specify the type when calling useContext.Footloose
Y
2

I've been trying to figure this out for a while too, finally did it with a wizard I'm building atm.

The provider part is similar to yours:

import React, { Dispatch, SetStateAction } from "react";

export interface IWizardContext<T> {
  formData: T;
  setFormData: Dispatch<SetStateAction<T>>;
}

export const WizardContext = React.createContext<IWizardContext<any>>({
  formData: {},
  setFormData: () => {},
});

const WizardProvider: React.FC = (props) => {
  const [formData, setFormData] = React.useState<any>({});

  return (
    <WizardContext.Provider
      value={{
        formData: formData,
        setFormData: setFormData,
      }}
    >
      {props.children}
    </WizardContext.Provider>
  );
};

export default WizardProvider;

export function withWizardProvider<TProps = {}>(component: React.FC<TProps>) {
  const Component = component;

  return ((props: TProps) => {
    return (
      <WizardProvider>
        <Component {...props} />
      </WizardProvider>
    );
  }) as React.FC<TProps>;
}

And the consumer:

import React from "react";
import {
  IWizardContext,
  withWizardProvider,
  WizardContext,
} from "./WizardProvider";

interface ISampleWizardState {
  name?: string;
  lastName?: string;
}

const SampleWizard: React.FC = (props) => {
  const wizard = React.useContext<IWizardContext<ISampleWizardState>>(
    WizardContext
  );

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "flex-start",
        flexDirection: "column",
      }}
    >
      <span>{`${wizard.formData.name || "John"} ${
        wizard.formData.lastName || "Doe"
      }`}</span>
      <input
        type="text"
        name="name"
        value={wizard.formData.name}
        onChange={(e) => {
          const text = e?.target?.value;
          wizard.setFormData((prev) => ({
            ...prev,
            name: text,
          }));
        }}
      />
      <input
        type="text"
        name="lastName"
        value={wizard.formData.lastName}
        onChange={(e) => {
          const text = e?.target?.value;
          wizard.setFormData((prev) => ({
            ...prev,
            lastName: text,
          }));
        }}
      />
    </div>
  );
};

export default withWizardProvider(SampleWizard);

So the main thing for the consumer is to define your T in the useContext:

React.useContext<IWizardContext<ISampleWizardState>>(WizardContext);
Yeager answered 16/12, 2020 at 8:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.