From 845665d2cf3f612baaaa671ba4d0ba26deee5079 Mon Sep 17 00:00:00 2001 From: Aleksandr Khizhnyi Date: Mon, 20 Jul 2026 13:09:16 +0300 Subject: [PATCH 1/2] fix(Provider): throw actionable error when rendered in a React Server Component (#2082) --- src/components/Provider.tsx | 19 ++++++++++++ test/components/Provider-rsc.spec.tsx | 42 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 test/components/Provider-rsc.spec.tsx diff --git a/src/components/Provider.tsx b/src/components/Provider.tsx index 5f532c459..4c3ef84ac 100644 --- a/src/components/Provider.tsx +++ b/src/components/Provider.tsx @@ -57,6 +57,25 @@ export interface ProviderProps< function Provider = UnknownAction, S = unknown>( providerProps: ProviderProps, ) { + if (process.env.NODE_ENV !== 'production') { + // React Server Components do not provide the effect hooks that `` + // relies on, so `useIsomorphicLayoutEffect` (which resolves to + // `useLayoutEffect`/`useEffect`) is `undefined` in that environment. Detect + // this before any hook runs and throw an actionable error, instead of the + // cryptic "X is not a function" that would otherwise surface. + if (typeof useIsomorphicLayoutEffect !== 'function') { + throw new Error( + 'The React Redux `` component requires React hooks that are ' + + 'not available in a React Server Component. This usually means you are ' + + 'rendering `` in a Server Component. Add the `"use client"` ' + + 'directive to the top of the file that renders `` (or a ' + + 'parent that ends up rendering it) so it is treated as a Client ' + + 'Component. See https://react.dev/reference/rsc/use-client for more ' + + 'information.', + ) + } + } + const { children, context, serverState, store } = providerProps const contextValue = React.useMemo(() => { diff --git a/test/components/Provider-rsc.spec.tsx b/test/components/Provider-rsc.spec.tsx new file mode 100644 index 000000000..0b0df97db --- /dev/null +++ b/test/components/Provider-rsc.spec.tsx @@ -0,0 +1,42 @@ +/*eslint-disable react/prop-types*/ + +import * as rtl from '@testing-library/react' +import { Provider } from 'react-redux' +import { createStore } from 'redux' + +// In a React Server Components environment, React does not expose the effect +// hooks that `` relies on, so `useIsomorphicLayoutEffect` (which +// resolves to `useLayoutEffect`/`useEffect`) ends up `undefined`. Simulate that +// here to assert `` surfaces a clear, actionable error rather than a +// cryptic "X is not a function". +vi.mock('../../src/utils/useIsomorphicLayoutEffect', () => ({ + useIsomorphicLayoutEffect: undefined, +})) + +// This mock replaces an internal source module, which only exists as a separate +// module in the `src` build. When the suite runs against the bundled build +// artifact (`TEST_DIST`), `useIsomorphicLayoutEffect` is inlined into a single +// file with no module boundary left to mock, so the RSC condition cannot be +// simulated there. The guard itself is unchanged between builds, so exercising +// it against `src` is sufficient. +const describeSource = process.env.TEST_DIST ? describe.skip : describe + +describeSource('React', () => { + describe('Provider in a React Server Component environment', () => { + it('throws an actionable error when React effect hooks are unavailable', () => { + const store = createStore(() => ({})) + + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + expect(() => + rtl.render( + +
+ , + ), + ).toThrow(/React Server Component/) + + spy.mockRestore() + }) + }) +}) From 8705869c79dc31e47fe69431ba3c73b171a2baf0 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 29 Sep 2026 22:04:45 -0400 Subject: [PATCH 2/2] Trim Provider RSC check comments --- src/components/Provider.tsx | 6 +----- test/components/Provider-rsc.spec.tsx | 15 ++------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/components/Provider.tsx b/src/components/Provider.tsx index 4c3ef84ac..ba841f8cf 100644 --- a/src/components/Provider.tsx +++ b/src/components/Provider.tsx @@ -58,11 +58,7 @@ function Provider = UnknownAction, S = unknown>( providerProps: ProviderProps, ) { if (process.env.NODE_ENV !== 'production') { - // React Server Components do not provide the effect hooks that `` - // relies on, so `useIsomorphicLayoutEffect` (which resolves to - // `useLayoutEffect`/`useEffect`) is `undefined` in that environment. Detect - // this before any hook runs and throw an actionable error, instead of the - // cryptic "X is not a function" that would otherwise surface. + // The React Server Components build of React has no effect hooks if (typeof useIsomorphicLayoutEffect !== 'function') { throw new Error( 'The React Redux `` component requires React hooks that are ' + diff --git a/test/components/Provider-rsc.spec.tsx b/test/components/Provider-rsc.spec.tsx index 0b0df97db..afd35c3a3 100644 --- a/test/components/Provider-rsc.spec.tsx +++ b/test/components/Provider-rsc.spec.tsx @@ -1,24 +1,13 @@ -/*eslint-disable react/prop-types*/ - import * as rtl from '@testing-library/react' import { Provider } from 'react-redux' import { createStore } from 'redux' -// In a React Server Components environment, React does not expose the effect -// hooks that `` relies on, so `useIsomorphicLayoutEffect` (which -// resolves to `useLayoutEffect`/`useEffect`) ends up `undefined`. Simulate that -// here to assert `` surfaces a clear, actionable error rather than a -// cryptic "X is not a function". +// Simulates the React Server Components build of React, which has no effect hooks vi.mock('../../src/utils/useIsomorphicLayoutEffect', () => ({ useIsomorphicLayoutEffect: undefined, })) -// This mock replaces an internal source module, which only exists as a separate -// module in the `src` build. When the suite runs against the bundled build -// artifact (`TEST_DIST`), `useIsomorphicLayoutEffect` is inlined into a single -// file with no module boundary left to mock, so the RSC condition cannot be -// simulated there. The guard itself is unchanged between builds, so exercising -// it against `src` is sufficient. +// The bundled build inlines `useIsomorphicLayoutEffect`, so there is no module to mock const describeSource = process.env.TEST_DIST ? describe.skip : describe describeSource('React', () => {