最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React memo keeps rendering when props have not changed - Stack Overflow

programmeradmin11浏览0评论

I have a stateless functional component which has no props and populates content from React context. For reference, my app uses NextJS and is an Isomorphic App. I'm trying to use React.memo() for the first time on this component but it keeps re-rendering on client side page change, despite the props and context not changing. I know this due to my placement of a console log.

A brief example of my component is:

const Footer = React.memo(() => {
  const globalSettings = useContext(GlobalSettingsContext);
  console.log('Should only see this once');

  return (
    <div>
      {globalSettings.footerTitle}
    </div>
  );
});

I've even tried passing the second parameter with no luck:

const Footer = React.memo(() => {
  ...
}, () => true);

Any ideas what's going wrong here?

EDIT: Usage of the context provider in _app.js looks like this:

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    ...
    return { globalSettings };
  }

  render() {    
    return (
      <Container>
        <GlobalSettingsProvider settings={this.props.globalSettings}>
          ...
        </GlobalSettingsProvider>
      </Container>
    );
  }
}

The actual GlobalSettingsContext file looks like this:

class GlobalSettingsProvider extends Component {
  constructor(props) {
    super(props);
    const { settings } = this.props;
    this.state = { value: settings };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        {this.props.children}
      </Provider>
    );
  }
}

export default GlobalSettingsContext;
export { GlobalSettingsConsumer, GlobalSettingsProvider };

I have a stateless functional component which has no props and populates content from React context. For reference, my app uses NextJS and is an Isomorphic App. I'm trying to use React.memo() for the first time on this component but it keeps re-rendering on client side page change, despite the props and context not changing. I know this due to my placement of a console log.

A brief example of my component is:

const Footer = React.memo(() => {
  const globalSettings = useContext(GlobalSettingsContext);
  console.log('Should only see this once');

  return (
    <div>
      {globalSettings.footerTitle}
    </div>
  );
});

I've even tried passing the second parameter with no luck:

const Footer = React.memo(() => {
  ...
}, () => true);

Any ideas what's going wrong here?

EDIT: Usage of the context provider in _app.js looks like this:

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    ...
    return { globalSettings };
  }

  render() {    
    return (
      <Container>
        <GlobalSettingsProvider settings={this.props.globalSettings}>
          ...
        </GlobalSettingsProvider>
      </Container>
    );
  }
}

The actual GlobalSettingsContext file looks like this:

class GlobalSettingsProvider extends Component {
  constructor(props) {
    super(props);
    const { settings } = this.props;
    this.state = { value: settings };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        {this.props.children}
      </Provider>
    );
  }
}

export default GlobalSettingsContext;
export { GlobalSettingsConsumer, GlobalSettingsProvider };
Share Improve this question edited Feb 19, 2019 at 10:30 CaribouCode asked Feb 19, 2019 at 10:13 CaribouCodeCaribouCode 14.4k33 gold badges111 silver badges183 bronze badges 7
  • 2 It might be because of useContext. reactjs.org/docs/hooks-reference.html#usecontext – bamtheboozle Commented Feb 19, 2019 at 10:17
  • Thought this might be the case. The provider for my GlobalSettingsContext wraps everything, so I can't stop that updated/re-rendering because of it's children props. So technically that value is updating every time even though the value doesn't actually change. – CaribouCode Commented Feb 19, 2019 at 10:21
  • 1 Have you considered using the context in the parent component and passing the footerTitle directly as a prop instead of context? – bamtheboozle Commented Feb 19, 2019 at 10:22
  • Can you show how you are rendering the Provider children? – Shubham Khatri Commented Feb 19, 2019 at 10:26
  • @ShubhamKhatri I've added the code to my question – CaribouCode Commented Feb 19, 2019 at 10:30
 |  Show 2 more comments

1 Answer 1

Reset to default 15

The problem is coming from useContext. Whenever any value changes in your context, the component will re-render regardless of whether the value you're using has changed.

The solution is to create a HOC (i.e. withMyContext()) like so;

// MyContext.jsx
// exported for when you really want to use useContext();
export const MyContext = React.createContext();

// Provides values to the consumer
export function MyContextProvider(props){
  const [state, setState] = React.useState();
  const [otherValue, setOtherValue] = React.useState();
  return <MyContext.Provider value={{state, setState, otherValue, setOtherValue}} {...props} />
}

// HOC that provides the value to the component passed.
export function withMyContext(Component){
  <MyContext.Consumer>{(value) => <Component {...value} />}</MyContext.Consumer>
}

// MyComponent.jsx
const MyComponent = ({state}) => {
  // do something with state
}

// compares stringified state to determine whether to render or not. This is
// specific to this component because we only care about when state changes, 
// not otherValue
const areEqual = ({state:prev}, {state:next}) => 
  JSON.stringify(prev) !== JSON.stringify(next)

// wraps the context and memo and will prevent unnecessary 
// re-renders when otherValue changes in MyContext.
export default React.memo(withMyContext(MyComponent), areEqual)

Passing context as props instead of using it within render allows us to isolate the changing values we actually care about using areEqual. There's no way to make this comparison during render within useContext.

I would be a huge advocate for having a selector as a second argument similar to react-redux's new hooks useSelector. This would allow us to do something like

const state = useContext(MyContext, ({state}) => state);

Who's return value would only change when state changes, not the entire context.

But I'm just a dreamer.

This is probably the biggest argument I have right now for using react-redux over hooks for simple apps.

发布评论

评论列表(0)

  1. 暂无评论