Repository: auth0/nextjs-auth0
Pull request #945 (2022-12-06 16:30:45)

fix/rerenders: useMemo to avoid unnecessary rerenders
<!--
❗ For general support or usage questions, use the Auth0 Community forums or raise a support ticket.

By submitting a pull request to this repository, you agree to the terms within the Auth0 Code of Conduct: https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md.
-->

- All new/changed/fixed functionality is covered by tests **N/A**
- I have added documentation for all new/changed functionality **N/A**

<!--
❗ All the above items are required. Pull requests with an incomplete or missing checklist will be closed.
-->

### 📋 Changes
`useContext` is triggering unnecessary re-renders. With `useMemo` this can be avoided as described [here](https://blog.agney.dev/useMemo-inside-context/).

tl;tr
I noticed this when I integrated [why-did-you-render](https://github.com/welldone-software/why-did-you-render) to [release-raccoon](https://github.com/stavros-liaskos/release-raccoon/commit/b066d95068f210d505167afb39bf0a8ef85abc7e).  Atm it is using 2 contexts, I implemented the above fix to [ThemeProvider](https://github.com/stavros-liaskos/release-raccoon/commit/b066d95068f210d505167afb39bf0a8ef85abc7e#diff-e141c0adf082355a42cf2966d4290b966ffeecc03040917c3b421a1c3e1d243bR14) and this PR takes care of fixing this in `useUser` too.
<!--
Describe both what is changing and why this is important. Include:

- Types and methods added, deleted, deprecated, or changed
- A summary of usage if this is a new feature or a change to a public API
-->

### 📎 References
- Related issue from other repository: https://github.com/stavros-liaskos/release-raccoon/pull/20/files#r1041055599
<!--
Add relevant links supporting this change, such as:

- GitHub issue/PR number addressed or fixed
- Auth0 Community post
- StackOverflow answer
- Related pull requests/issues from other repositories

If there are no references, simply delete this section.
-->

### 🎯 Testing
Clicking on the `Home` btn calls the hook again but returns same results _different objects that are equal by value._
<img width="1792" alt="withoutFix" src="https://user-images.githubusercontent.com/17932287/205943604-72dc6dbb-8932-467e-b017-d23e6f7d6108.png">
<img width="1792" alt="withUseMemo" src="https://user-images.githubusercontent.com/17932287/205943618-64c6b4bc-7f39-4d8b-bb96-eb3910f860fa.png">

<!--
Describe how this can be tested by reviewers. Be specific about anything not tested and why. Include any manual steps for testing end-to-end, or for testing functionality not covered by unit tests.
-->



Patch:
diff --git a/README.md b/README.md
index c907e09d..1087aadb 100644
--- a/README.md
+++ b/README.md
@@ -305,7 +305,7 @@ We appreciate feedback and contribution to this repo! Before you get started, pl
 
 - [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
 - [Auth0's code of conduct guidelines](https://github.com/auth0/express-openid-connect/blob/master/CODE-OF-CONDUCT.md)
-- [This repo's contribution guide](https://github.com/auth0/express-openid-connect/blob/master/CONTRIBUTING.md)
+- [This repo's contribution guide](./CONTRIBUTING.md)
 
 ## Vulnerability Reporting
 
diff --git a/src/client/use-user.tsx b/src/client/use-user.tsx
index f76f3c97..becaad40 100644
--- a/src/client/use-user.tsx
+++ b/src/client/use-user.tsx
@@ -1,5 +1,5 @@
 'use client';
-import React, { ReactElement, useState, useEffect, useCallback, useContext, createContext } from 'react';
+import React, { ReactElement, useState, useEffect, useCallback, useContext, createContext, useMemo } from 'react';
 
 import ConfigProvider, { ConfigContext } from './use-config';
 
@@ -214,10 +214,11 @@ export default ({
   }, [state.user]);
 
   const { user, error, isLoading } = state;
+  const value = useMemo(() => ({ user, error, isLoading, checkSession }), [user, error, isLoading, checkSession]);
 
   return (
     <ConfigProvider loginUrl={loginUrl}>
-      <UserContext.Provider value={{ user, error, isLoading, checkSession }}>{children}</UserContext.Provider>
+      <UserContext.Provider value={value}>{children}</UserContext.Provider>
     </ConfigProvider>
   );
 };


Test patch:
diff --git a/tests/frontend/use-user.test.tsx b/tests/frontend/use-user.test.tsx
index 26da42c0..c6354a2d 100644
--- a/tests/frontend/use-user.test.tsx
+++ b/tests/frontend/use-user.test.tsx
@@ -270,3 +270,21 @@ describe('check session', () => {
     expect(result.current.isLoading).toEqual(false);
   });
 });
+
+describe('re-renders', () => {
+  afterEach(() => delete (global as any).fetch);
+
+  test('should not update context value after rerender with no state change', async () => {
+    (global as any).fetch = fetchUserErrorMock;
+    const { waitForNextUpdate, result, rerender } = renderHook(() => useUser(), {
+      wrapper: withUserProvider()
+    });
+
+    await waitForNextUpdate();
+    const memoized = result.current;
+
+    rerender();
+
+    expect(result.current).toBe(memoized);
+  });
+});