I am defining a context provider to wrap my <App/>
component like this:
// index.ts
ReactDOM.render(
<ApolloProvider client={client}>
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>
</ApolloProvider>,
document.getElementById('root')
);
I got the context provider definiton working by passing {children}
deconstructed from props with the React.ReactNode
type, and the return wraps the children between two <AuthContext.Provider>
tags like this:
// src/context/AutContext.ts
// Context definition
const AuthContext = React.createContext<{currentUser: User} | undefined>(undefined);
// Context provider wrapper definition:
const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const { currentUser } = useCurrentUse();
return (
<AuthContext.Provider value={{ currentUser }}>
{children} // <- note the different ways of passing children to the Provider
</AuthContext.Provider>
);
};
But I am trying to streamline the code by reducing <AuthContext.Provider/>
to a one liner, which right now I have working passing props
regularly, but with type any
:
// Context provider wrapper definition (one liner variant):
const AuthProvider = (props: any) => { // <- any type. props instead of destructured {children}
const { currentUser } = useCurrentUser();
return <AuthContext.Provider value={{ currentUser }} {...props} />; // <- spread one liner
};
Any idea of how should I type props
in the second variant? I have tried inferring it but the result does not work (and is a mess), and using React.ReactNode
as a type only works with {children}
, not with the entire props
.
In @types/react
I have found a ProviderProps<T>
which might be the solution, but if it is, I still don't know what to replace T
with.
Looking at the type definition for ProviderProps<T>
the generic T
is used to type the value
property.
This means you would provide the same T
as provided to React.createContext<T>()
.
Simple Method:
// We know from inspecting the ProviderProps type that it has two properties
// The first property value is not to be passed in.
// The second property children is an optional set of react nodes.
// The React.FunctionComponent type with no generic added only has a prop of type
// children?: React.ReactNode.
// This will make the props of AuthProvider compatible with the props of AuthContext.Provider.
const AuthProvider: React.FunctionComponent = (props) => {
const { currentUser } = useCurrentUser();
return (
<AuthContext.Provider value={{ currentUser }} {...props} />
);
};
More Complex Method:
type ContextValue = {currentUser: User} | undefined;
// depending on your version of typescript Omit might not like types with generics
// the work around for this is to create a wrapping type around Omit to trick it into
// thinking that it is getting a type it approves of.
// If this does not effect your version of typescript just use:
// Omit<ProviderProps<ContextValue>, 'value'>
type OmitValue<T> = Omit<T, 'value'>
const AuthContext = React.createContext<ContextValue>(undefined);
// We do not control the type ProviderProps, this may change in the future.
// To give better future proofing we can use the ProviderProp type and remove the
// properties we do not want to be changed (value in this case) using Omit.
// This means that any additional properties added to ProviderProps in the future
// will be valid for AuthProvider, but at this point in time it will just be
// children.
const AuthProvider: React.FunctionComponent<OmitValue<ProviderProps<ContextValue>>> = (props) => {
const { currentUser } = useCurrentUser();
return (
<AuthContext.Provider value={{ currentUser }} {...props} />
);
};
React 18 removes the default children
property off of React.FunctionComponent
or its proxy React.FC
. To make the 'Simple Method' above work without type errors the new React.PropsWithChildren
can be used.
Simple Method:
// We know from inspecting the ProviderProps type that it has two properties
// The first property value is not to be passed in.
// The second property children is an optional set of react nodes.
// The React.FunctionComponent type with no generic added only has a prop of type
// children?: React.ReactNode.
// This will make the props of AuthProvider compatible with the props of AuthContext.Provider.
const AuthProvider: React.FunctionComponent<React.PropsWithChildren<{}>> = (props) => {
const { currentUser } = useCurrentUser();
return (
<AuthContext.Provider value={{ currentUser }} {...props} />
);
};