Closed pumanitro closed 5 years ago
This is working as designed. There is a longer discussion about this in https://github.com/facebook/react/issues/14110 if you're curious.
Let's say for some reason you have AppContext
whose value has a theme
property, and you want to only re-render some ExpensiveTree
on appContextValue.theme
changes.
TLDR is that for now, you have three options:
If we just need appContextValue.theme
in many components but appContextValue
itself changes too often, we could split ThemeContext
from AppContext
.
function Button() {
let theme = useContext(ThemeContext);
// The rest of your rendering logic
return <ExpensiveTree className={theme} />;
}
Now any change of AppContext
won't re-render ThemeContext
consumers.
This is the preferred fix. Then you don't need any special bailout.
memo
in betweenIf for some reason you can't split out contexts, you can still optimize rendering by splitting a component in two, and passing more specific props to the inner one. You'd still render the outer one, but it should be cheap since it doesn't do anything.
function Button() {
let appContextValue = useContext(AppContext);
let theme = appContextValue.theme; // Your "selector"
return <ThemedButton theme={theme} />
}
const ThemedButton = memo(({ theme }) => {
// The rest of your rendering logic
return <ExpensiveTree className={theme} />;
});
useMemo
insideFinally, we could make our code a bit more verbose but keep it in a single component by wrapping return value in useMemo
and specifying its dependencies. Our component would still re-execute, but React wouldn't re-render the child tree if all useMemo
inputs are the same.
function Button() {
let appContextValue = useContext(AppContext);
let theme = appContextValue.theme; // Your "selector"
return useMemo(() => {
// The rest of your rendering logic
return <ExpensiveTree className={theme} />;
}, [theme])
}
There might be more solutions in the future but this is what we have now.
Still, note that option 1 is preferable — if some context changes too often, consider splitting it out.
Both of these options will bail out of rendering children if theme hasn't changed.
@gaearon Are the Buttons the children or do the Buttons render children? I'm missing some context how these are used.
Using the unstable_Profiler
option 2 will still trigger onRender
callbacks but not call the actual render logic. Maybe I'm doing something wrong? ~https://codesandbox.io/s/kxz4o2oyoo~ https://codesandbox.io/s/00yn9yqzjw
I updated the example to be clearer.
Using the unstable_Profiler option 2 will still trigger onRender callbacks but not call the actual render logic. Maybe I'm doing something wrong? https://codesandbox.io/s/kxz4o2oyoo
That's exactly the point of that option. :-)
Maybe a good solution for that would be to have the possibility of "taking" the context and rerender component only if given callback return true e.g:
useContext(ThemeContext, (contextData => contextData.someArray.length !== 0 ));
The main problem with hooks that I actually met is that we can't manage from inside of a hook what is returned by a component - to prevent rendering, return memoized value etc.
If we could, it wouldn't be composable.
https://overreacted.io/why-isnt-x-a-hook/#not-a-hook-usebailout
Option 4: Do not use context for data propagation but data subscription. Use useSubscription (because it's hard to write to cover all cases).
There is another way to avoid re-render. "You need to move the JSX up a level out of the re-rendering component then it won't get re-created each time"
Maybe a good solution for that would be to have the possibility of "taking" the context and rerender component only if given callback return true e.g:
useContext(ThemeContext, (contextData => contextData.someArray.length !== 0 ));
The main problem with hooks that I actually met is that we can't manage from inside of a hook what is returned by a component - to prevent rendering, return memoized value etc.
Instead of a true/false here... could we provide an identity based function that allowed us to subset the data from the context?
const contextDataINeed = useContext(ContextObj, (state) => state['keyICareAbout'])
where useContext wouldn't pop in this component unless the result of the selector fn was different identity wise from the previous result of the same function.
found this library that it may be the solution for Facebook to integrate with hooks https://blog.axlight.com/posts/super-performant-global-state-with-react-context-and-hooks/
There is another way to avoid re-render. "You need to move the JSX up a level out of the re-rendering component then it won't get re-created each time"
Problem is it may be costly to restructure the components tree just to prevent top to bottom re-rendering.
@fuleinist Ultimately, it's not that different from MobX, although a lot simplified for a specific use case. MobX already works like that (also using Proxies), the state is mutated and components who use specific bits of the state get re-rendered, nothing else.
@gaearon I don't know if I'm missing something, but I have tried yours second and third options and they are not working correctly. Not sure if this is only react chrome extension bug or there is other catch. Here is my simple example of form, where I see rerendering both inputs. In console I see that memo is doing his job but DOM is rerendered all the time. I have tried 1000 items and onChange event is really slow, that's why I think that memo() is not working with context correctly. Thanks for any advice:
Here is demo with 1000 items/textboxes. But in that demo dev tools doesn't show re-render. You have to download sources on local to test it: https://codesandbox.io/embed/zen-firefly-d5bxk
import React, { createContext, useState, useContext, memo } from "react";
const FormContext = createContext();
const FormProvider = ({ initialValues, children }) => {
const [values, setValues] = useState(initialValues);
const value = {
values,
setValues
};
return <FormContext.Provider value={value}>{children}</FormContext.Provider>;
};
const TextField = memo(
({ name, value, setValues }) => {
console.log(name);
return (
<input
type="text"
value={value}
onChange={e => {
e.persist();
setValues(prev => ({
...prev,
[name]: e.target.value
}));
}}
/>
);
},
(prev, next) => prev.value === next.value
);
const Field = ({ name }) => {
const { values, setValues } = useContext(FormContext);
const value = values[name];
return <TextField name={name} value={value} setValues={setValues} />;
};
const App = () => (
<FormProvider initialValues={{ firstName: "Marr", lastName: "Keri" }}>
First name: <Field name="firstName" />
<br />
Last name: <Field name="lastName" />
</FormProvider>
);
export default App;
On the other hand this approach without context works correctly, still in debug it is slower than I expected but at least rerender is ok
import React, { useState, memo } from "react";
import ReactDOM from "react-dom";
const arr = [...Array(1000).keys()];
const TextField = memo(
({ index, value, onChange }) => (
<input
type="text"
value={value}
onChange={e => {
console.log(index);
onChange(index, e.target.value);
}}
/>
),
(prev, next) => prev.value === next.value
);
const App = () => {
const [state, setState] = useState(arr.map(x => ({ name: x })));
const onChange = (index, value) =>
setState(prev => {
return prev.map((item, i) => {
if (i === index) return { name: value };
return item;
});
});
return state.map((item, i) => (
<div key={i}>
<TextField index={i} value={item.name} onChange={onChange} />
</div>
));
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
@marrkeri I don't see something wrong in the first code snippet. The component that's highlighted in dev tools is the Field
that uses the context, not the TextField
which is a memo component and implements the areEqual function.
I think the performance problem in the codesandbox example comes from the 1000 components that use the context. Refactor it to one component that uses the context, say Fields
, and return from that component (with a map) a TextField
for each value.
@marrkeri I don't see something wrong in the first code snippet. The component that's highlighted in dev tools is the
Field
that uses the context, not theTextField
which is a memo component and implements the areEqual function.I think the performance problem in the codesandbox example comes from the 1000 components that use the context. Refactor it to one component that uses the context, say
Fields
, and return from that component (with a map) aTextField
for each value.
As you said I was under same thinking that
I haven't catch your second point about refactoring to one component
@marrkeri I was suggesting something like this: https://codesandbox.io/s/little-night-p985y.
Is this the reason why react-redux had to stop using the benefits of stable context API and passing the current state to the context when they migrated to Hooks?
So looks like there is
It might be the only option if you don't control the usages (needed for options 2-3) and can't enumerate all the possible selectors (needed for option 1), but still want to expose a Hooks API
const MyContext = createContext()
export const Provider = ({children}) => (
<MyContext.provider value={{subscribe: listener => ..., getValue: () => ...}}>
{children}
</MyContext.provider>
)
export const useSelector = (selector, equalityFunction = (a, b) => a === b) => {
const {subscribe, getValue} = useContext(MyContext)
const [value, setValue] = useState(getValue())
useEffect(() => subscribe(state => {
const newValue = selector(state)
if (!equalityFunction(newValue, value) {
setValue(newValue)
}
}), [selector, equalityFunction])
}
@Hypnosphi : we stopped passing the store state in context (the v6 implementation) and switched back to direct store subscriptions (the v7 implementation) due to a combination of performance problems and the inability to bail out of updates caused by context (which made it impossible to create a React-Redux hooks API based on the v6 approach).
For more details, see my post The History and Implementation of React-Redux.
I read through the thread but I'm still left wondering -- are the only options available today to conditionally rerender on context change, "Options 1 2 3 4" listed above? Is something else in the works to officially address this or are the "4 solutions" considered acceptable enough?
I wrote in the other thread, but just in case. Here's an unofficial workaround. useContextSelector proposal and use-context-selector library in userland.
honestly, this makes me think that the framework just isn't ready to go fully into functions and hooks like we're being encouraged. With classes and lifecycle methods you had a tidy way of controlling these things and now with hooks it's a much worse less readable syntax
Option 3 doesn't seem to work. Am I doing anything wrong? https://stackblitz.com/edit/react-w8gr8z
@Martin, I do not agree with that.
The hook pattern can be readable with well-organized documentation and code structure and React classes and lifecycle are all replaceable by functional equivalent ones.
Unfortunately, the reactive pattern can not be achieved by React along via either React classes or hooks.
On Sat, Jan 11, 2020 at 9:44 AM Martin Genev notifications@github.com wrote:
honestly, this makes me think that the framework just isn't ready to go fully into functions and hooks like we're being encouraged. With classes and lifecycle methods you had a tidy way of controlling these things and now with hooks it's a much worse less readable syntax
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/facebook/react/issues/15156?email_source=notifications&email_token=AAI4DWUJ7WUXMHAR6F2KVXTQ5D25TA5CNFSM4G7UEEO2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIVN6OI#issuecomment-573235001, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAI4DWUCO7ORHV5OSDCE35TQ5D25TANCNFSM4G7UEEOQ .
@mgenev Option 3 prevents re-render of the child (<ExpensiveTree />
, name speaks for itself)
@Hypnosphi thanks, that was correct. https://stackblitz.com/edit/react-ycfyye
I rewrote and now the actual rendering (display) components don't re-render, but all the containers (context connected) do render on any change of prop on the context, no matter if it's in use in them. Now the only option that I can see is to start splitting the contexts, but some things are truly global and wrap at the highest level and a change on any prop in them will cause all the containers to fire in the entire app, i don't understand how that can possibly be good for performance...
Is there anywhere a good example of how to do this in a performant way? The official docs are really limited
You can try my option 4, which is basically what react-redux does internally https://github.com/facebook/react/issues/15156#issuecomment-546703046
To implement subscribe
function, you can use something like Observables or EventEmitter, or just write a basic subscription logic yourself:
function StateProvider({children}) {
const [state, dispatch] = useReducer(reducer, initialState)
const listeners = useRef([])
const subscribe = listener => {
listeners.current.push(listener)
}
useEffect(() => {
listeners.current.forEach(listener => listener(state)
}, [state])
return (
<DispatchContext.Provider value={dispatch}>
<SubscribeContext.Provider value={{subscribe, getValue: () => state}}>
{children}
</SubscribeContext.Provider>
</DispatchContext.Provider>
);
}
For those who may have interests, here's the comparison of various libraries somewhat related to this topic. https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-mode
My latest effort is to check the support of state branching with useTransition
that would be important in the upcoming Concurrent Mode.
Basically, if we use React state and context in a normal way, it's OK. (otherwise, we need a trick, for example, this.)
Thanks @dai-shi I really like your packages and I'm looking into adopting them.
@dai-shi hi, I just found your react-tracked
lib and it looks really good if it solves performance issues with contexts as it promises. Is it still actual or better to use something else? Here I see good example of its use also it showcases how to make middleware level with use-reducer-async
https://github.com/dai-shi/react-tracked/blob/master/examples/12_async/src/store.ts Thanks for it. Currently I've done something similar using useReducer
, wrapping dispatch
with my own one for async middleware and using Context
but worrying for future rendering performance issues because of contexts wrapping.
@bobrosoft react-tracked
is pretty stable, I'd say (as one developer product for sure). Feedback is very welcome and that's how a library would get improved. Currently, it internally uses an undocumented feature of React, and I hope we would be able to replace it with a better primitive in the future. use-reducer-async
is almost like a simple syntax sugar which would never go wrong.
This HoC worked for me:
import React, { useMemo, ReactElement, FC } from 'react';
import reduce from 'lodash/reduce';
type Selector = (context: any) => any;
interface SelectorObject {
[key: string]: Selector;
}
const withContext = (
Component: FC,
Context: any,
selectors: SelectorObject,
): FC => {
return (props: any): ReactElement => {
const Consumer = ({ context }: any): ReactElement => {
const contextProps = reduce(
selectors,
(acc: any, selector: Selector, key: string): any => {
const value = selector(context);
acc[key] = value;
return acc;
},
{},
);
return useMemo(
(): ReactElement => <Component {...props} {...contextProps} />,
[...Object.values(props), ...Object.values(contextProps)],
);
};
return (
<Context.Consumer>
{(context: any): ReactElement => <Consumer context={context} />}
</Context.Consumer>
);
};
};
export default withContext;
usage example:
export default withContext(Component, Context, {
value: (context): any => context.inputs.foo.value,
status: (context): any => context.inputs.foo.status,
});
this could be seen as the Context equivalent of redux mapStateToProps
I made an hoc almost very similar to connect() in redux
const withContext = (
context = createContext(),
mapState,
mapDispatchers
) => WrapperComponent => {
function EnhancedComponent(props) {
const targetContext = useContext(context);
const { ...statePointers } = mapState(targetContext);
const { ...dispatchPointers } = mapDispatchers(targetContext);
return useMemo(
() => (
<WrapperComponent {...props} {...statePointers} {...dispatchPointers} />
),
[
...Object.values(statePointers),
...Object.values(props),
...Object.values(dispatchPointers)
]
);
}
return EnhancedComponent;
};
Implementation :
const mapActions = state => {
return {};
};
const mapState = state => {
return {
theme: (state && state.theme) || ""
};
};
export default connectContext(ThemeContext, mapState, mapActions)(Button);
Update: Ultimately, I switched to EventEmitter for fast changing, granular data with dynamic listeners (on mouse move). I realized I was it was the better tool for the job. Context is great for generally sharing data, but not at high refresh rates.
Doesn’t it come down to declaratively subscribing or not subscribing to context? Conditionally wrapping a component in another that uses useContext().
The main requirement is that the inner component can’t have state, since it will effectively be a different instance because of the branching. Or, maybe you can pre-render the component then use cloneElement to update the props.
Some components nothing to do with context in it's render but needs to "just read a value" from it. What am I missing?
Is Context._currentValue safe to use in production?
I'm trying to subscribe components to contexts which are cheap to render as possible. But then I found myself to passing props like in old way with a sphagetti code or using memo to avoid subscribing for updates when there is nothing logical with updates. Memo solutions are good for when your component render depends on contexts but otherwise ...
@vamshi9666 have you use that a lot ? were you able to notice pros/cons of your approach ? I like it a lot. I like the similarity with redux but also how it frees you to write app state management and logic freely as a context
I found https://recoiljs.org/ a good solution to this issue. I think it’d be awesome if you integrate it into React.
@vamshi9666 have you use that a lot ? were you able to notice pros/cons of your approach ? I like it a lot. I like the similarity with redux but also how it frees you to write app state management and logic freely as a context
I used it only in one place and my initial goal is to isolate state management from jsx but also not to create a not of boilerplate. so when i extended mutations functionality , it looked very similar to redux's reducer and action creator pattern. Which is even better. but I don't see a point to reinvent something, when its actually there already.
try to use a clean context with this library
see this code sandbox example
check also this article
actually, you can create a clean context with createContext from react-hooks-in-callback
and use the useContextSelector hook to pick only the desired part from your context
you can also filter out re-renders noise from derived context's hooks like with this formik example
This is working as designed. There is a longer discussion about this in #14110 if you're curious.
Let's say for some reason you have
AppContext
whose value has atheme
property, and you want to only re-render someExpensiveTree
onappContextValue.theme
changes.TLDR is that for now, you have three options:
Option 1 (Preferred): Split contexts that don't change together
If we just need
appContextValue.theme
in many components butappContextValue
itself changes too often, we could splitThemeContext
fromAppContext
.function Button() { let theme = useContext(ThemeContext); // The rest of your rendering logic return <ExpensiveTree className={theme} />; }
Now any change of
AppContext
won't re-renderThemeContext
consumers.This is the preferred fix. Then you don't need any special bailout.
Option 2: Split your component in two, put
memo
in betweenIf for some reason you can't split out contexts, you can still optimize rendering by splitting a component in two, and passing more specific props to the inner one. You'd still render the outer one, but it should be cheap since it doesn't do anything.
function Button() { let appContextValue = useContext(AppContext); let theme = appContextValue.theme; // Your "selector" return <ThemedButton theme={theme} /> } const ThemedButton = memo(({ theme }) => { // The rest of your rendering logic return <ExpensiveTree className={theme} />; });
Option 3: One component with
useMemo
insideFinally, we could make our code a bit more verbose but keep it in a single component by wrapping return value in
useMemo
and specifying its dependencies. Our component would still re-execute, but React wouldn't re-render the child tree if alluseMemo
inputs are the same.function Button() { let appContextValue = useContext(AppContext); let theme = appContextValue.theme; // Your "selector" return useMemo(() => { // The rest of your rendering logic return <ExpensiveTree className={theme} />; }, [theme]) }
There might be more solutions in the future but this is what we have now.
Still, note that option 1 is preferable — if some context changes too often, consider splitting it out.
Hi!
I made a proof of concept on how to benefit from React.Context
, but avoid re-rendering children that consume the context object. The solution makes use of React.useRef
and CustomEvent
. Whenever you change count
or lang
, only the component consuming the specific proprety gets updated. @gaearon what's your oppinion on this?
Check it out below, or try the CodeSandbox
index.tsx
import * as React from 'react'
import {render} from 'react-dom'
import {CountProvider, useDispatch, useState} from './count-context'
function useConsume(prop: 'lang' | 'count') {
const contextState = useState()
const [state, setState] = React.useState(contextState[prop])
const listener = (e: CustomEvent) => {
if (e.detail && prop in e.detail) {
setState(e.detail[prop])
}
}
React.useEffect(() => {
document.addEventListener('update', listener)
return () => {
document.removeEventListener('update', listener)
}
}, [state])
return state
}
function CountDisplay() {
const count = useConsume('count')
console.log('CountDisplay()', count)
return (
<div>
{`The current count is ${count}`}
<br />
</div>
)
}
function LangDisplay() {
const lang = useConsume('lang')
console.log('LangDisplay()', lang)
return <div>{`The lang count is ${lang}`}</div>
}
function Counter() {
const dispatch = useDispatch()
return (
<button onClick={() => dispatch({type: 'increment'})}>
Increment count
</button>
)
}
function ChangeLang() {
const dispatch = useDispatch()
return <button onClick={() => dispatch({type: 'switch'})}>Switch</button>
}
function App() {
return (
<CountProvider>
<CountDisplay />
<LangDisplay />
<Counter />
<ChangeLang />
</CountProvider>
)
}
const rootElement = document.getElementById('root')
render(<App />, rootElement)
count-context.tsx
import * as React from 'react'
type Action = {type: 'increment'} | {type: 'decrement'} | {type: 'switch'}
type Dispatch = (action: Action) => void
type State = {count: number; lang: string}
type CountProviderProps = {children: React.ReactNode}
const CountStateContext = React.createContext<State | undefined>(undefined)
const CountDispatchContext = React.createContext<Dispatch | undefined>(
undefined,
)
function countReducer(state: State, action: Action) {
switch (action.type) {
case 'increment': {
return {...state, count: state.count + 1}
}
case 'switch': {
return {...state, lang: state.lang === 'en' ? 'ro' : 'en'}
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
function CountProvider({children}: CountProviderProps) {
const [state, dispatch] = React.useReducer(countReducer, {
count: 0,
lang: 'en',
})
const stateRef = React.useRef(state)
React.useEffect(() => {
const customEvent = new CustomEvent('update', {
detail: {count: state.count},
})
document.dispatchEvent(customEvent)
}, [state.count])
React.useEffect(() => {
const customEvent = new CustomEvent('update', {
detail: {lang: state.lang},
})
document.dispatchEvent(customEvent)
}, [state.lang])
return (
<CountStateContext.Provider value={stateRef.current}>
<CountDispatchContext.Provider value={dispatch}>
{children}
</CountDispatchContext.Provider>
</CountStateContext.Provider>
)
}
function useState() {
const context = React.useContext(CountStateContext)
if (context === undefined) {
throw new Error('useCount must be used within a CountProvider')
}
return context
}
function useDispatch() {
const context = React.useContext(CountDispatchContext)
if (context === undefined) {
throw new Error('useDispatch must be used within a AccountProvider')
}
return context
}
export {CountProvider, useState, useDispatch}
FYI to future readers, there is an open PR for context selectors, so it might happen sometime: https://github.com/reactjs/rfcs/pull/119
This is working as designed. There is a longer discussion about this in #14110 if you're curious.
Let's say for some reason you have
AppContext
whose value has atheme
property, and you want to only re-render someExpensiveTree
onappContextValue.theme
changes.TLDR is that for now, you have three options:
Option 1 (Preferred): Split contexts that don't change together
If we just need
appContextValue.theme
in many components butappContextValue
itself changes too often, we could splitThemeContext
fromAppContext
.function Button() { let theme = useContext(ThemeContext); // The rest of your rendering logic return <ExpensiveTree className={theme} />; }
Now any change of
AppContext
won't re-renderThemeContext
consumers.This is the preferred fix. Then you don't need any special bailout.
Option 2: Split your component in two, put
memo
in betweenIf for some reason you can't split out contexts, you can still optimize rendering by splitting a component in two, and passing more specific props to the inner one. You'd still render the outer one, but it should be cheap since it doesn't do anything.
function Button() { let appContextValue = useContext(AppContext); let theme = appContextValue.theme; // Your "selector" return <ThemedButton theme={theme} /> } const ThemedButton = memo(({ theme }) => { // The rest of your rendering logic return <ExpensiveTree className={theme} />; });
Option 3: One component with
useMemo
insideFinally, we could make our code a bit more verbose but keep it in a single component by wrapping return value in
useMemo
and specifying its dependencies. Our component would still re-execute, but React wouldn't re-render the child tree if alluseMemo
inputs are the same.function Button() { let appContextValue = useContext(AppContext); let theme = appContextValue.theme; // Your "selector" return useMemo(() => { // The rest of your rendering logic return <ExpensiveTree className={theme} />; }, [theme]) }
There might be more solutions in the future but this is what we have now.
Still, note that option 1 is preferable — if some context changes too often, consider splitting it out.
Also In Redux re-render problem not happen, so I change my state management ContextApi to Redux
Better Solution: Bigger Application use Redux
try to use a clean context with this library
see this code sandbox example
check also this article
actually, you can create a clean context with createContext from react-hooks-in-callback
and use the useContextSelector hook to pick only the desired part from your context
you can also filter out re-renders noise from derived context's hooks like with this formik example
For anyone reading this thread now, the context selector functionality was removed from the linked library and put in a standalone library.
I wonder why the option 3 is not the build-in option for React.
Option 3: One component with useMemo inside
The reason why i say that is, the Root is always bailed out in the single dispatch case. But when the Provider is used, no one is taking care of the path between the Provider to the consumer any more. So if Root can be bailed out always, why not the first children of the Provider. I would relate the Root to the Provider, as in single dispatch vs multiple dispatch case.
Maybe we can have a component called ContextProviderSimple
which joins the ContextProvider
and SimpleMemo
. I blogged this approach here, https://windmaomao.medium.com/should-a-context-provider-be-that-expensive-7cafa3727507.
Hope this makes sense, because i think Context shouldn't be by default expensive, because it should be designed to solve a very important problem, so if a single dispatch is working, we should just scale the solution up without introducing an "unexpected" behavior from the single case, assuming bailing out the Root is the expected case.
FYI, this change has nothing to do with useContext
, maybe i picked the wrong thread, but the solution provided as Option 3 ignites the conversation.
@steida
Option 4: Do not use context for data propagation but data subscription. Use useSubscription (because it's hard to write to cover all cases).
What does it mean? Could you explain both data propagation and data subscription using examples? As per my understanding they are same: data is propagated using subscribe/publish, right?
@steida it means that the context value should not be the data, i.e. something that changes, but rather a data source, an object that always stays the same by reference, but has something like subscribe
and getCurrentData
methods.
@Hypnosphi
@steida it means that the context value should not be the data, i.e. something that changes, but rather a data source, an object that always stays the same by reference, but has something like
subscribe
andgetCurrentData
methods.
OK. Such data sources can be global/singleton objects and that would still work? Why do we need context then?
Maybe I am missing something, but @gaearon maybe we should add clarification to Option 1 that it prevents rerenders in some cases; for example here it doesn't help:
import React from 'react';
import './style.css';
const { useState, createContext, useContext, useEffect, useRef } = React;
const ViewContext = createContext();
const ActionsContext = createContext();
function MyContainer() {
const [contextState, setContextState] = useState();
return (
<ViewContext.Provider value={contextState}>
<ActionsContext.Provider value={setContextState}>
<MySetCtxComponent />
<MyViewCtxComponent />
</ActionsContext.Provider>
</ViewContext.Provider>
);
}
function MySetCtxComponent() {
const setContextState = useContext(ActionsContext);
const counter = useRef(0);
console.log('Set');
useEffect(() => {
console.log('=======>>>>>>>>>>>> Use Effect run in MySetCtxComponent');
const intervalID = setInterval(() => {
setContextState('New Value ' + counter.current);
counter.current++;
}, 1000);
return () => clearInterval(intervalID);
}, [setContextState]);
return <button onClick={() => (counter.current = 0)}>Reset</button>;
}
function MyViewCtxComponent() {
const contextState = useContext(ViewContext);
console.log('View');
return <div>This is the value of the context: {contextState}</div>;
}
export default MyContainer;
One can see both "View" and "Set" are being logged, which means both components got rerendered.
Maybe I am missing something, but @gaearon maybe we should add clarification to Option 1 that it prevents rerenders in some cases; for example here it doesn't help:
import React from 'react'; import './style.css'; const { useState, createContext, useContext, useEffect, useRef } = React; const ViewContext = createContext(); const ActionsContext = createContext(); function MyContainer() { const [contextState, setContextState] = useState(); return ( <ViewContext.Provider value={contextState}> <ActionsContext.Provider value={setContextState}> <MySetCtxComponent /> <MyViewCtxComponent /> </ActionsContext.Provider> </ViewContext.Provider> ); } function MySetCtxComponent() { const setContextState = useContext(ActionsContext); const counter = useRef(0); console.log('Set'); useEffect(() => { console.log('=======>>>>>>>>>>>> Use Effect run in MySetCtxComponent'); const intervalID = setInterval(() => { setContextState('New Value ' + counter.current); counter.current++; }, 1000); return () => clearInterval(intervalID); }, [setContextState]); return <button onClick={() => (counter.current = 0)}>Reset</button>; } function MyViewCtxComponent() { const contextState = useContext(ViewContext); console.log('View'); return <div>This is the value of the context: {contextState}</div>; } export default MyContainer;
One can see both "View" and "Set" are being logged, which means both components got rerendered.
Wondering the same, have you found any solution for this specific example?
Do you want to request a feature or report a bug?
bug
What is the current behavior?
I can't rely on data from context API by using (useContext hook) to prevent unnecessary rerenders with React.memo
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
What is the expected behavior?
I should have somehow access to the context in React.memo second argument callback to prevent rendering Or I should have the possibility to return an old instance of the react component in the function body.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React? 16.8.4