Add new RenderProps-style Context from React 16.3#24509
Add new RenderProps-style Context from React 16.3#24509johnnyreilly merged 4 commits intoDefinitelyTyped:masterfrom scally:react-16.3-context
Conversation
|
@scally Thank you for submitting this PR! 🔔 @johnnyreilly @bbenezech @pzavolinsky @digiguru @ericanderson @morcerf @tkrotoff @DovydasNavickas @onigoetz @theruther4d @guilhermehubner @JoshuaKGoldberg @jrakotoharisoa - please review this PR in the next few days. Be sure to explicitly select If no reviewer appears after a week, a DefinitelyTyped maintainer will review the PR instead. |
There was a problem hiding this comment.
Use ComponentType instead of Component and add support for bitmasks.
An example of the alternative defitnion can be seen in this CodeSandbox:
https://codesandbox.io/s/l3j167ljq?module=src/react-context.d.ts
types/react/index.d.ts
Outdated
| ...children: ReactNode[]): ReactElement<P>; | ||
|
|
||
| // Context via RenderProps | ||
| type Provider<T> = () => Component<{ |
types/react/index.d.ts
Outdated
| value: T, | ||
| children?: ReactNode | ||
| }>; | ||
| type Consumer<T> = () => Component<{ |
types/react/index.d.ts
Outdated
| }>; | ||
| type Consumer<T> = () => Component<{ | ||
| children: (value: T) => ReactNode, | ||
| }>; |
There was a problem hiding this comment.
missing field:
unstable_observedBits?: number;
types/react/index.d.ts
Outdated
| Provider: Provider<T>; | ||
| Consumer: Consumer<T>; | ||
| } | ||
| function createContext<T>(defaultValue: T): Context<T>; |
There was a problem hiding this comment.
createContext has an optional second argument:
calculateChangedBits?: (prev: T, next: T) => number
|
@scally Unfortunately, this pull request currently has a merge conflict 😥. Please update your PR branch to be up-to-date with respect to master. Have a nice day! |
|
@rhmoller Thank you for the review! I have addressed your feedback and rebased. |
|
The travis build failure looks spurious, although I don't see a way to retry it. |
rhmoller
left a comment
There was a problem hiding this comment.
Looks good to me. The Travis build seems to originate from the react-flatpickr project, so unrelated to your changes.
|
FYI, I wrote up an article on using the new Context API with TypeScript. I used the current definitions in the PR (linking back here) in the article. I'll probably start publicizing the article tomorrow but I just wanted to circulate it here in case anybody had any constructive feedback on it: https://medium.com/@mtiller/react-16-3-context-api-intypescript-45c9eeb7a384 |
| function createContext<T>( | ||
| defaultValue: T, | ||
| calculateChangedBits?: (prev: T, next: T) => number | ||
| ): Context<T>; |
There was a problem hiding this comment.
It should be possible to call createContext() to create one with defaultValue of undefined.
function createContext<T>(): Context<T|undefined> override? This is one of those that look like the antipattern warned against in the docs, but I think it's correct to use here, similar to a collection constructor.
There was a problem hiding this comment.
Hmm. I'm confused here. Calling createContext() (i.e., no arguments) would require the defaultValue to be optional. Otherwise you'd need to do an explicit createContext(undefined). Also, since T can be anything, I don't understand T|undefined.
FYI, I address this case of optional default values in my article on the new Context API (using the typings as they stand here, but wrapping them in one additional layer). Also note that, in my opinion (see article), it isn't just about making the default value optional. But again, I used these typings without issue.
There was a problem hiding this comment.
16.3 released today and the docs seem to indicate @Kovensky is correct here — they use ‘createContext’ without args so it must be a correct usage. I’ll take a look at updating later today.
There was a problem hiding this comment.
There was a problem hiding this comment.
OK, rebased this post merge conflict.
Spent some time with this tonight and it looks like the behavior of the final API is that
createContext() gives you a context object where _defaultValue is undefined.
createContext('foo') gives you a context object where _defaultValue is foo
Given that, I'm a bit confused as to the correct typing for createContext() with no params. I tried the overload @Kovensky mentioned but doing so types the resulting RenderProps function args with {} or undefined, which I don't think is correct.
I suspect the overload closest to the API's usage would be
function createContext(): Context<any>
Which completely gives up type safety for RenderProps function but at least allows it to be used. {} or undefined doesn't allow you to still give, say, an anonymous object in the Provider's value field and still be able to retrieve it later in the RenderProps.
If anyone has any opinions on this they'd be welcome.
There was a problem hiding this comment.
Yup, I'm just saying that we shouldn't want to correct that behavior, because you can always use Consumer not paired with a Provider so the value of T should not change even if you later pass in the value as a prop to Provider.
I.e. if you create the context with the default value, you are always guaranteed to have a defined value wherever you use Consumer (even not as a descendant of Provider), but once you create the context without a default value, you are not guaranteed that defined value unless you wrap the Consumer inside a Provider, which wouldn't be able to be statically analyzed before runtime.
I'm agreeing with you that it's unfortunate that we'll always have to check for undefined, but I think that should be the way the interface is structured due to the lack of compile-time guarantees of the consumer value
There was a problem hiding this comment.
Well, in your first example you had the comment there /* value here will be defined, even though it's typed as optional */ and that was what's confused me the most. As you have said now, you will have to check for undefined either way. I understand now.
I see a bit of problem arising here. Developers are generally lazy and we might end up with pattern like this.
interface MyContextType { myValue: string }
const Context = createContext<MyContextType>()
<Context.Consumer>{value => {
<div>{value!.myValue.toUpperCase()}</div> // <-- notice the "!" mark there
</Context.Consumer>That might be a good source of runtime errors (and blaming TypeScript/React for it) in case someone really forgets to add the Provider. It's true that if anyone is using ! it's for own risk, but there are people that might just copy&paste and then be surprised.
What about this approach? Would this work the same way? I am not sure if it's a correct syntax, but you get the gist. This would at least allow to override this undefined type in case someone knows what they are doing.
function createContext<T, S = T | undefined>(): Context<S>;There was a problem hiding this comment.
Hmm.. it might not be immediately clear to the user why the second generic type param may be used, and they'll have to dig into the type defs to actually find out how they resolve.
With my suggestion (the two overloads), creating context with a default param will assert the value in the consumer to be always be defined:
interface ContextProps {
foo: string
bar?: number
}
// note the default values passed in as the first parameter
const Context = createContext<Props>({ foo: 'foo' }) // typed as Props
const OtherContext = createContext({ foo: 'foo' }) // inferred to be type { foo: string }
// even without a Provider it is still typed to be required
const app = (
<App>
<Context.Consumer>
{( value ) => /* value is typed to be Props here, no need to check for undefined */ }
</Context.Consumer>
<Context.Consumer>
{( value ) => /* value is typed to be { foo: string } here, no need to check for undefined */ }
</Context.Consumer>
</App>
)in this case, all you need as a type consumer to override the undefined behavior is to just provide a default value, the type inference will work for you or you can explicitly set the generic type parameter, and as long as the default value is passed in as a parameter, the consumer render props will always be defined
There was a problem hiding this comment.
@ferdaber I'm trying to use the TypeScript 2.8 features you mentioned in this definition, but it looks like that's not possible yet?
I've updated the headers to // TypeScript Version: 2.8 but that results in a linting error.
According to this comment, we can only use TypeScript up to 2.7 right now, so I may not be able to use the definition you specify.
There was a problem hiding this comment.
ah, that's too bad, we'll keep it in the backburner then
types/react/index.d.ts
Outdated
| type Provider<T> = ComponentType<{ | ||
| value: T, | ||
| children?: ReactNode | ||
| }>; |
There was a problem hiding this comment.
I'm not sure these should be ComponentType; similarly to a Fragment, they can't actually be constructed, don't have a propTypes property, etc.
There was a problem hiding this comment.
Why do you say they can't be constructed? That's exactly how they are used (or perhaps I'm misunderstanding what you mean by "constructed").
There was a problem hiding this comment.
@Kovensky do you have a suggestion for what types they should be?
There was a problem hiding this comment.
By "constructed" I mean given as argument to new. They're not even functions, as you can see in their definition, but unfortunately TSC doesn't support that 😞
I guess we're stuck with ComponentType here, at least until TSC is updated to use the overloads of React.createElement for JSX type checking.
|
@timwangdev check in regards to what? Did you have a concern about some part in particular? |
types/react/index.d.ts
Outdated
| value: T, | ||
| children?: ReactNode | ||
| }>; | ||
| type Consumer<T> = ComponentType<{ |
There was a problem hiding this comment.
It may be worthwhile to refactor the props interface of Consumer and Provider in case those props are spread from another place.
There was a problem hiding this comment.
@ferdaber I'm not sure I understand the specific refactoring you want here. Could you elaborate?
There was a problem hiding this comment.
I'm talking about refactoring the props as their own exported interface like this:
interface ConsumerProps<T> {
children(value: T): ReactNode,
unstable_observedBits?: number
}
interface ProviderProps<T> {
children?: ReactNode
// if you choose to use my suggestion, otherwise just T
value: T extends undefined ? never : T
}
// this way a consumer of the type can import `ConsumerProps` and `ProviderProps` for other purposes
type Consumer<T> = ComponentType<ConsumerProps<T>>
type Provider<T> = ComponentType<ProviderProps<T>>There was a problem hiding this comment.
Hmmm.. When I try that, in the test code proposed below it no longer is able to determine the type of value:
const ContextRenderPropsConsumerComponent = (
<ContextWithDefaultValue.Consumer>
{value => value.assert} // <--- Value here is now "any" with the props refactor
</ContextWithDefaultValue.Consumer>
);
See the test example below for the full proposed test code.
There was a problem hiding this comment.
@ferdaber It looks like the refactoring you proposed might cause some issues as well, listed in the comment above. Do you see a way around them?
There was a problem hiding this comment.
odd... I tested it out and it works fine for me, these are my relevant snippets (I copied your test code as-is):
// react/index.d.ts
interface ProviderProps<T> {
value: T;
children?: ReactNode;
}
interface ConsumerProps<T> {
children: (value: T) => ReactNode;
unstable_observedBits?: number;
}
type Provider<T> = ComponentType<ProviderProps<T>>;
type Consumer<T> = ComponentType<ConsumerProps<T>>;
interface Context<T> {
Provider: Provider<T>;
Consumer: Consumer<T>;
}
function createContext<T>(
defaultValue: T,
calculateChangedBits?: (prev: T, next: T) => number
): Context<T>;
function createContext<T>(): Context<T | undefined>;// react/test/tsx.tsx
import React from '.'
const RenderPropsContext = React.createContext('defaultValue');
interface RenderPropsContextTest {
assert: boolean;
}
const ContextWithDefaultValue = React.createContext<RenderPropsContextTest>({ assert: true });
const ContextWithoutDefaultValue = React.createContext<RenderPropsContextTest>();
const ContextRenderPropsConsumerComponent = (
<ContextWithDefaultValue.Consumer>
{value => value.assert}
</ContextWithDefaultValue.Consumer>
);
const ContextRenderPropsProviderConsumerComponent = (
<ContextWithoutDefaultValue.Provider value={{ assert: true }}>
<ContextWithoutDefaultValue.Consumer>
{value => value.assert}
</ContextWithoutDefaultValue.Consumer>
</ContextWithoutDefaultValue.Provider>
); // $ExpectErrorThere was a problem hiding this comment.
@scally wrote:
When I try that, in the test code proposed below it no longer is able to determine the type of value:
perhaps that was an IDE bug? There are cases where Webstorm & IntelliJ IDEA don't show inferred argument type correctly (claims it is any), but when used incorrectly it still emits error with message mentioning correct type of the argument
| <StatelessComponentWithoutProps />; | ||
|
|
||
| // React.createContext | ||
| const ContextWithRenderProps = React.createContext('defaultValue'); |
There was a problem hiding this comment.
I think this is the way too simple test to cover the changes. I would propose following, but I am not entirely sure how to run these tests, so there might some mistake (or several)
interface ContextTest = {
assert: boolean
}
const ContextWithValue = React.createContext<ContextTest>({ assert: true })
const ContextWithoutValue = React.createContext<ContextTest>()
<ContextWithValue.Context>{value => value.assert}</ContextWithValue.Context>
<ContextWithoutValue.Provider value={{ assert: true }}>
<ContextWithoutValue.Context>{value => value.assert}</ContextWithoutValue.Context>
</ContextWithoutValue.Provider>There was a problem hiding this comment.
@FredyC I'm not sure myself. The test files here don't seem to be assertion-style tests so much as a sandbox to prove that the typings work without syntax errors.
I agree that there could be more code here exercising both context scenarios (with and without defaults), but I don't know that they should be executable as none of the rest of the code in this file seems to be.
There was a problem hiding this comment.
Well yes, tests code is not executed, because you're testing types, not React's behavior.
But tests shouldn't always be positive, if you think that there might be some usage that is not correct, but could be assumed as correct one.
What you have to do for failing tests is add // $ExpectError line immediately before the test line. More about that in DT readme:
https://github.com/DefinitelyTyped/DefinitelyTyped#lint
And dtslint readme:
https://github.com/Microsoft/dtslint#write-tests
There was a problem hiding this comment.
@scally Are you willing to try that? Perhaps it's overkill for such relatively simple thing. It's your call.
There was a problem hiding this comment.
@FredyC Yes, I'll try it out and see where I get with it.
There was a problem hiding this comment.
I am trying something like this:
const RenderPropsContext = React.createContext('defaultValue');
interface RenderPropsContextTest {
assert: boolean;
}
const ContextWithDefaultValue = React.createContext<RenderPropsContextTest>({ assert: true });
const ContextWithoutDefaultValue = React.createContext<RenderPropsContextTest>();
const ContextRenderPropsConsumerComponent = (
<ContextWithDefaultValue.Consumer>
{value => value.assert}
</ContextWithDefaultValue.Consumer>
);
const ContextRenderPropsProviderConsumerComponent = (
<ContextWithoutDefaultValue.Provider value={{ assert: true }}>
<ContextWithoutDefaultValue.Consumer>
{value => value.assert}
</ContextWithoutDefaultValue.Consumer>
</ContextWithoutDefaultValue.Provider>
); // $ExpectErrorThere was a problem hiding this comment.
@FredyC Are these tests sufficient for what you feel should be achieved here?
There was a problem hiding this comment.
Strange that the error is in the full statement instead of in the argument to Consumer, but seems to be the case.
An additional non-erroring test where the argument is value => value === undefined ? null : value.assert would also help demonstrate it.
There's one last confirmation I'd like to take; if the Context is created with a default value, will the Consumer receive that value even if it's not put inside its Provider in the tree? 🤔 looks at source
EDIT: The context's context has a copy of the default argument as its value, and it's only overwritten by the reconciler if the Provider is present. It's also restored to its original value once the Provider tree has been processed. So indeed it can only be undefined if you passed something that can be undefined (or nothing) to the createContext call.
types/react/index.d.ts
Outdated
| // Context via RenderProps | ||
| type Provider<T> = ComponentType<{ | ||
| value: T, | ||
| children?: ReactNode |
There was a problem hiding this comment.
Should the children be made mandatory? Not much point in a Provider without any children.
There was a problem hiding this comment.
Would it really matter since ReactNode already accounts for undefined? I can see a scenario where someone may not render an app while waiting for an API result but would still like to layer a context provider on top of it (undefined or null children)
There was a problem hiding this comment.
Note that ReactNode is wrong altogether, it should not include undefined. See this #23422 (review) from Dan. It should be better eventually when this is resolved ... microsoft/TypeScript#21699.
There was a problem hiding this comment.
Yeah, ReactNode is definitely wrong. If you actually use ReactNode anywhere, you'll get a warning from React.
I think the only time it doesn't warn is if you put it as an array element, but otherwise it's a warning.
There was a problem hiding this comment.
It has to be ReactNode due to the above TypeScript issue though, otherwise it's impossible to use React with noImplicitAny. After the above issue is resolved this and many more types can be cleaned up but right now there is no option.
There was a problem hiding this comment.
Side note, I do have a custom alias for this so I can actually have render prop SFC components and I am ignoring the type from ofic. typings. declare type ReactNode = React.ReactElement<any> | null. When this is resolved correctly, I can easily update this alias. It's not really useful here, but works well otherwise :)
…WordPress#39526) Part of WordPress#39211 Previously we have been calling `createContext()` without any arguments, but there's a funny note in the official React type for that function. > // If you thought this should be optional, see > // DefinitelyTyped/DefinitelyTyped#24509 (comment) Although not passing an argument is practically the same as passing `undefined` as the argument we have a type error that TypeScript doesn't like while we're relying on it to parse JS files with the JSDoc typings. In this patch we're just adding the explicit `undefined` which should have no behavioral change on the output but removes the type issue.
|
I don't understand the reasoning at all. In 95%+ of the cases, a context provider will be rendered in the tree. A default value is non-sensical, it implies that a context can be used without an explicit provider, but it cannot. E.g. think a default value for Apollo client, react router, We ended up patching |
|
You can do |
|
|
@PatrissolJuns Yes, the |
|
rly aweful, you need to declare it as default value and as a value on the Provider. both is required in typescript without any reason in my opinion. if youre forced to set a default value, the value on the provider instance can be optional so only way to make it useable is like this |
|
Edit: Updated example with the suggestion from @nlukk that will allow null values in the context by using a symbol as a default value. As this thread seems to pop up here and there, a better solution than workarounds using non-null assertions and risking undefined behaviour seems merited. We can use the default value as an indication that the context is missing a value in the tree and throw an error. This won't ensure build-time safety but provides a great developer experience as mistakes done by the developer will show up with an error describing exactly what and where the problem is. Here's a complete example including both a hook and consumer: export enum Theme {
Dark = "dark",
Light = "light"
}/** Symbol used as default value of context. */
const defaultValue = Symbol();
/** Non-exported context so we can force how it's consumed and provided. */
const ThemeContext = createContext<Theme | typeof defaultValue>(defaultValue);
/** Only exporting the provider as we want to manually handle the consumer. */
export const ThemeProvider = ThemeContext.Provider;
/** Context consumer where the value is non-nullable. */
interface ThemeConsumerProps {
children: (value: Theme) => ReactNode;
}
export const ThemeConsumer: FunctionComponent<ThemeConsumerProps> = ({
children
}) => {
return (
<ThemeContext.Consumer>
{(theme) => {
if (theme === defaultValue) {
throw new Error(
"<ThemeConsumer /> used without a <ThemeProvider /> in the tree."
);
}
return children(theme);
}}
</ThemeContext.Consumer>
);
};
/** Hook so we can use the context value without needing to do null checks. */
export const useTheme = (): Theme => {
const theme = useContext(ThemeContext);
if (theme === defaultValue) {
throw new Error(
"useTheme() called without a <ThemeProvider /> in the tree."
);
}
return theme;
};If we try to use the hook or consumer without a const theme = useTheme();<ThemeConsumer>{(theme) => <>The theme is {theme}</>}</ThemeConsumer>We'll get friendly error messages: Use the provider as any other provider or component: <ThemeProvider theme={Theme.Dark}>
...
</ThemeProvider> |
@nlukk I'm not sure how the use of a Symbol would help with the rewrap of the provider? If null or undefined are valid values you can export and use the context as-is: export const ThemeContext = createContext<Theme | undefined | null>(); |
|
@cbodin Yes, but in your previous example, |
|
@nlukk Yes, if exported and passed null it would throw. Could you provide me with an example when using |
|
@cbodin sure, here it is const sym = Symbol();
export const Context = createContext<number | typeof sym>(sym); |
|
@nlukk But aren't we back to the original problem now, where we need to check everywhere that the context returns a valid value? const value = useContext(Context);
if (typeof value === 'number') {
// Use value
} |
|
@cbodin yes, but you can write a custom hook for this, so that you don't need to also import context everywhere. const sym = Symbol();
export const SomethingContext = createContext<number | typeof sym>(sym);
export function useSomethingContext() {
const something = useContext(SomethingContext);
if (something === sym) throw new Error();
return something;
} |
Thx this is great, one step further to satisfy lints as well is you can do: createContext<ContextType>(undefined as never); |
|
@nlukk I've updated my example above with a symbol instead of relying on null as per your suggestion. Also removed the custom provider as the default provider can be used as-is now. Still kept the custom consumer so that a developer-friendly error message can be shown if the provider is missing. |
using the workaround explained in DefinitelyTyped/DefinitelyTyped#24509 (comment)
|
apologies for necroposting (and technically self promoting), but I found myself doing the whole "throw an error if no Provider" pattern often enough that i created a little library for it import { createRequiredContext } from "required-react-context";
export const { CountContext, useCount, CountProvider, CountConsumer } =
createRequiredContext<number>().with({ name: "count" });
function Child() {
// This will throw an error if used outside a CountProvider
const count = useCount();
return <div>{count}</div>;
}
function App() {
return (
<CountProvider count={5}>
<Child />
</CountProvider>
);
}hopefully somebody finds this useful :) |
|
You have to realize that const MyContext = createContext<MyContextValue | null>(null);
export function useMyContext() {
const value = useContext(MyContext);
if (value == null) throw new Error('expected MyContext not to be null');
return value;
} |
Hi, i'm late to the party :), but for curiosity. What would be a valid use case in which you call useContext outside Provider? |
|
@marcog83 In my experience such use cases are quite rare and I don't know that I've ever had one. You would have to have some kind of global default behavior that you want to override only in some contexts. IMO it is very much the norm to only call useContext within a corresponding Provider, and sometimes I wonder if useContext should have been better designed with that in mind. In my project I have a helper function |
|
Life is strange 😄 ... /**
*
* getApiInstance
*
* This function is used to get an instance of Ky with the given configuration.
* @param {ApiConfig} param - The configuration for the API.
* @param {Options} options - The options for the API.
* @category Functions
*/
export const getApiInstance = ({ authToken, prefixUrl }: ApiConfig, options?: Options) => {
let hooks = options?.hooks;
if (authToken) {
hooks = {
...hooks,
beforeRequest: [ getBeforeRequest(authToken), ...(hooks?.beforeRequest ?? []) ],
};
}
const api: KyInstance = ky.create({
...options,
prefixUrl,
hooks,
retry: 0,
});
return api;
};then my provider looks like below. /**
*
Use the `UserPreferencesProvider` component to connect and provide a `preferences` client to your application:
import { UserPreferencesProvider } from "@my-sdk/user-preferences-react"
const authToken="OPTIONAL_AUTH_TOKEN"
const App = () => (
<UserPreferencesProvider
prefixUrl="https://example.domain.com/user-preferences"
authToken={authToken}
>
<MyComponent/>
</UserPreferencesProvider>
);
@param {UserPreferencesProviderProps} props The props for the UserPreferencesProvider component.
@category Providers
*/
export const UserPreferencesProvider = ({ children, prefixUrl, authToken }: UserPreferencesProviderProps) => {
const options = useApiOptions();
const manager = useMemo<Preferences>(
() => getPreferences({ authToken, prefixUrl }, options),
[ prefixUrl, authToken, options ],
);
return (
<UserPreferencesContext.Provider value={manager}>
{children}
</UserPreferencesContext.Provider>
);
};You can see this custom hook import { createContext, useContext } from 'react';
import type { Options } from 'ky';
const ApiContext = createContext<Options | undefined>(undefined);
export const useApiOptions = () => useContext(ApiContext);
/**
* Provider for the API options
* @type {React.Provider<Options>}
* @example
* ```tsx
* const options=defineOptions({
* headers: {
* "X-Custom-Header":"Custom-Value"
* }
* });
* <ApiOptionsProvider value={options}>
* <App />
* </ApiOptionsProvider>
* ```
*/
export const { Provider: ApiOptionsProvider } = ApiContext;How does the cosumer specify custom options? by wrapping the application into a const App = () => (
<UserPreferencesProvider
prefixUrl="https://exmple.domain.com/user-preferences"
authToken={authToken}
>
<MyComponent/>
</UserPreferencesProvider>
);but if they need to add extra infos, like custom headers they can use the library like this const options={
headers: {
"X-Custom-header:"Custom-value"
}
}
const App = () => (
<ApiOptionsProvider value={options}>
<UserPreferencesProvider
prefixUrl="https://exmple.domain.com/user-preferences"
authToken={authToken}
>
<MyComponent/>
</UserPreferencesProvider>
</ApiOptionsProvider>
); |
|
I suggest you create wrapper over context. Something like this: import { createContext, FC, PropsWithChildren, useContext, useMemo } from "react";
import { UserId, UserType } from "types";
type UserContextValue = {
userId: UserId;
userType: UserType;
};
const UserContext = createContext<UserContextValue | null>(null);
type UserContextProviderProps = PropsWithChildren<{
userId: UserId;
userType: UserType;
}>;
export const UserContextProvider: FC<UserContextProviderProps> = ({ userId, userType, children }) => {
const value = useMemo(() => ({
userId,
userType,
}), [userId, userType]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
)
};
export const useUserContext = () => {
const value = useContext(UserContext);
if (value === null) {
throw new Error('`useUserContext` hook used outside of `UserContextProvider`');
}
return value;
}Wrap components with UserContextProvider like this: <UserContextProvider userId={userId} userType={userType}>
<SomeComponent />
</UserContextProvider>And use values inside components like this: const SomeComponent: FC = () => {
const { userId } = useUserContext();
....
} |
When you define context, you may not understand what specific values should go into it yet. This would be similar to providing a "default value" for a SQL table, an array, or some other collection structure. What would go in it? Nothing? A default row with some made up values? Context definition ( What is the rationale for your opinion? Can you provide a model you're using to inform your opinion? |


React 16.3, the latest version, has a new recommended API for Context, with Context, Provider, and Consumer typings.
These changes are merged in this PR to React:
facebook/react#11818
Please fill in this template.
npm test.)npm run lint package-name(ortscif notslint.jsonis present).Select one of these and delete the others:
If changing an existing definition:
tslint.jsoncontaining{ "extends": "dtslint/dt.json" }.