Reputation: 584
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?
Upvotes: 6
Views: 6339
Reputation: 584
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)
Upvotes: 2
Reputation: 21
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);
Upvotes: 2