34
loading...
This website collects cookies to deliver better user experience
export const StateContext = createContext(null);
const Provider = () => {
return (
<StateContext.Provider value={state}>
<ChildComponent />
</StateContext.Provider>
)
}
const StateContext = createContext(null);
const DispatchContext = createContext(null);
export const Provider = () => {
const [state, setState] = useState(...)
const dispatch = (action) => {
switch (action.type) {
case 'CHANGE_STATE':
setState(action.payload)
break;
...
}
}
return (
<StateContext.Provider value={{state, ...}}>
<DispatchContext.Provider value={dispatch}>
<ChildComponent />
</DispatchContext.Provider>
</StateContext.Provider>
)
}
export const useDispatch = () => {
return useContext(DispatchContext)
}
export const Provider = ({ children }) => {
...
return (
<StateContext.Provider value={{state, ...}}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
)
}
export const Parent = ({ children }) => {
return (
<Provider>
<ChildComponent />
</Provider>
)
}
<ChildComponent >
, we are basically creating new React.Node every time, so all the children are re-rendered, unless we wrap them in React.memo
.useCallback
, but in this case it will help us only partially, because, that will basically cause "caching" of dispatch function and we wouldn't be able to use outer scope variables without including them into dependencies
- and then the dispatch function would still get recreated when dependencies change. We will need to use ref
to help us with this....
export const Provider = ({ children }) => {
const [state, setState] = useState(...)
const dispatchRef = useRef()
// new function with every render
const dispatchRef.current = (action) => {
switch (action.type) {
case 'CHANGE_STATE':
// we can use outer scope without restrictions
setState({...action.payload, ...state})
break;
...
}
}
// stable dispatch function
const dispatch = useCallback(
(action: ActionType) => dispatchRef.current(action),
[dispatchRef]
);
return (
<StateContext.Provider value={{state, ...}}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
)
}
DispatchContext
and we can use outer scope without limitations.import { createContext } from 'use-context-selector';
const StateContext = createContext(null);
export const Provider = ({ children }) => {
return (
<StateContext.Provider value={{state, ...}}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
)
}
import { useContextSelector } from 'use-context-selector';
export const Subscriber = () => {
const somePart = useContextSelector(StateContext, context => context.somePart)
}
ref
to wrap passed value, so that components are not re-rendering automatically and then it keeps list of subscribers. When value changes it runs all the subscribed functions and if the value from the selector is different than before it forces the subscribed Component to re-render. Similar concept is used e.g. in redux useSelector hook. So I say, it's quite standard solution and why build a new one, when it already exists?PS: there is even a open RFC to add something like this directly into react
import React, { useCallback, useRef } from 'react';
import { createContext, useContextSelector } from 'use-context-selector';
type DispatchType<ActionType, DispatchReturn> = (
action: ActionType
) => DispatchReturn;
type SelectorType<StateType> = (state: StateType) => any;
export const createProvider = <
StateType,
ActionType,
DispatchReturn,
ProviderProps
>(
body: (
props: ProviderProps
) => [state: StateType, dispatch: DispatchType<ActionType, DispatchReturn>]
) => {
const StateContext = createContext<StateType>(null as any);
const DispatchContext = React.createContext<
DispatchType<ActionType, DispatchReturn>
>(null as any);
const Provider: React.FC<ProviderProps> = ({ children, ...props }) => {
const [state, _dispatch] = body(props as any);
const dispatchRef = useRef(_dispatch);
dispatchRef.current = _dispatch;
// stable dispatch function
const dispatch = useCallback(
(action: ActionType) => dispatchRef.current?.(action),
[dispatchRef]
);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
};
const useDispatch = () => React.useContext(DispatchContext);
const useStateContext = (selector: SelectorType<StateType>) =>
useContextSelector(StateContext, selector);
return [Provider, useDispatch, useStateContext] as const;
};
type ActionType =
| { type: 'CHANGE_STATE'; payload: ... }
...
export const [
TranslationsContextProvider,
useTranslationsDispatch,
useTranslationsSelector,
] = createProvider(
(props /* provider props */) => {
const [state1, setState1] = useState(...)
const [state2, setState2] = useState(...)
const {data, isLoading} = useQuery(...)
const dispatch = (action: ActionType) => {
switch (action.type) {
case 'CHANGE_STATE':
setState(action.payload)
break;
...
}
}
const state = {
state1,
state2,
data,
isLoading
}
// don't forget to return state and dispatch function
return [state, dispatch]
})
PS: Check Tolgee.io and give us github stars