-
-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add NextJS app router support (#3074)
- Loading branch information
Showing
26 changed files
with
248 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
--- | ||
'@data-client/ssr': patch | ||
--- | ||
|
||
Add DataProvider export to /nextjs namespace. | ||
|
||
This provides 'App Router' compatibility. Simply add it to the root layout, ensuring | ||
`children` is rendered as a descendent. | ||
|
||
<details open> | ||
<summary><b>app/layout.tsx</b></summary> | ||
|
||
```tsx | ||
import { DataProvider } from '@data-client/react/nextjs'; | ||
import { AsyncBoundary } from '@data-client/react'; | ||
|
||
export default function RootLayout({ children }) { | ||
return ( | ||
<html> | ||
<body> | ||
<DataProvider> | ||
<header>Title</header> | ||
<AsyncBoundary>{children}</AsyncBoundary> | ||
<footer></footer> | ||
</DataProvider> | ||
</body> | ||
</html> | ||
); | ||
} | ||
``` | ||
|
||
</details> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
'@data-client/use-enhanced-reducer': patch | ||
'@data-client/react': patch | ||
'@data-client/redux': patch | ||
'@data-client/ssr': patch | ||
--- | ||
|
||
Compatibility with server/client component build rules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
'use client'; | ||
import { | ||
initialState as defaultState, | ||
NetworkManager, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
'use client'; | ||
import React from 'react'; | ||
import type { JSX } from 'react'; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,27 @@ | ||
'use client'; | ||
import type { State } from '@data-client/core'; | ||
import React, { useContext } from 'react'; | ||
import React from 'react'; | ||
|
||
import use from './useUniversal.js'; | ||
import { StateContext, StoreContext } from '../context.js'; | ||
|
||
const useCacheState: () => State<unknown> = | ||
/* istanbul ignore if */ | ||
( | ||
typeof window === 'undefined' && | ||
Object.hasOwn(React, 'useSyncExternalStore') | ||
) ? | ||
/* istanbul ignore next */ | ||
() => { | ||
const store = useContext(StoreContext); | ||
const state = useContext(StateContext); | ||
const store = use(StoreContext); | ||
const state = use(StateContext); | ||
const syncState = React.useSyncExternalStore( | ||
store.subscribe, | ||
store.getState, | ||
store.getState, | ||
); | ||
return store.uninitialized ? state : syncState; | ||
} | ||
: () => useContext(StateContext); | ||
: () => use(StateContext); | ||
|
||
export default useCacheState; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,12 @@ | ||
// Server Side Component compatibility (specifying this cannot be used as such) | ||
// context does not work in server components | ||
// https://beta.nextjs.org/docs/rendering/server-and-client-components#third-party-packages | ||
'use client'; | ||
import type { Controller } from '@data-client/core'; | ||
import { useContext } from 'react'; | ||
|
||
import use from './useUniversal.js'; | ||
import { ControllerContext } from '../context.js'; | ||
|
||
/** | ||
* Imperative control of Reactive Data Client store | ||
* @see https://dataclient.io/docs/api/useController | ||
*/ | ||
export default function useController(): Controller { | ||
return useContext(ControllerContext); | ||
return use(ControllerContext); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
'use client'; | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
import { | ||
EndpointInterface, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
'use client'; | ||
import { | ||
EndpointInterface, | ||
Schema, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
import React, { useContext } from 'react'; | ||
|
||
const useUniversal: <T>(context: React.Context<T>) => T = | ||
/* istanbul ignore if */ | ||
'use' in React ? /* istanbul ignore next */ (React.use as any) : useContext; | ||
export default useUniversal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
'use client'; | ||
import { | ||
State, | ||
ActionTypes, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
'use client'; | ||
import { | ||
ExternalCacheProvider, | ||
PromiseifyMiddleware, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
'use client'; | ||
import { CacheProvider } from '@data-client/react'; | ||
import { Suspense, useMemo, type ComponentProps } from 'react'; | ||
|
||
import { readyContext } from './context.js'; | ||
import ServerDataComponent from './ServerDataComponent.js'; | ||
import ServerProvider from './ServerProvider.js'; | ||
import { getInitialData } from '../../getInitialData.js'; | ||
|
||
const DataProvider = | ||
typeof window !== 'undefined' ? | ||
({ children, ...props }: ProviderProps) => { | ||
const [initialState, useReady] = useMemo(() => { | ||
const initialState = getInitialData(); | ||
return [initialState, () => initialState]; | ||
}, []); | ||
|
||
return ( | ||
<CacheProvider {...props} initialState={initialState}> | ||
<readyContext.Provider value={useReady}> | ||
<Suspense> | ||
<ServerDataComponent /> | ||
</Suspense> | ||
{children} | ||
</readyContext.Provider> | ||
</CacheProvider> | ||
); | ||
} | ||
: ({ children, ...props }: ProviderProps) => ( | ||
<ServerProvider {...props}> | ||
<Suspense> | ||
<ServerDataComponent /> | ||
</Suspense> | ||
{children} | ||
</ServerProvider> | ||
); | ||
export default DataProvider; | ||
|
||
type ProviderProps = Omit< | ||
Partial<ComponentProps<typeof CacheProvider>>, | ||
'initialState' | ||
> & { | ||
children: React.ReactNode; | ||
}; |
12 changes: 12 additions & 0 deletions
12
packages/ssr/src/nextjs/DataProvider/ServerDataComponent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { use } from 'react'; | ||
|
||
import { readyContext } from './context.js'; | ||
import ServerData from '../../ServerData.js'; | ||
|
||
const id = 'data-client-data'; | ||
|
||
const ServerDataComponent = ({ nonce }: { nonce?: string | undefined }) => { | ||
const data = use(readyContext)(); | ||
return <ServerData data={data} id={id} nonce={nonce} />; | ||
}; | ||
export default ServerDataComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use client'; | ||
import { type CacheProvider } from '@data-client/react'; | ||
import { useMemo, type ComponentProps } from 'react'; | ||
|
||
import createPersistedStore from './createPersistedStore.js'; | ||
|
||
export default function ServerProvider({ | ||
children, | ||
...props | ||
}: ProviderProps): React.ReactElement { | ||
const [ServerCacheProvider] = useMemo(createPersistedStore, []); | ||
|
||
return <ServerCacheProvider {...props}>{children}</ServerCacheProvider>; | ||
} | ||
|
||
type ProviderProps = Omit< | ||
Partial<ComponentProps<typeof CacheProvider>>, | ||
'initialState' | ||
> & { | ||
children: React.ReactNode; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use client'; | ||
import { type State, __INTERNAL__ } from '@data-client/react'; | ||
import { createContext } from 'react'; | ||
|
||
export const readyContext = createContext( | ||
(): State<unknown> => __INTERNAL__.initialState, | ||
); |
70 changes: 70 additions & 0 deletions
70
packages/ssr/src/nextjs/DataProvider/createPersistedStore.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
'use client'; | ||
import { | ||
ExternalCacheProvider, | ||
PromiseifyMiddleware, | ||
Controller, | ||
Manager, | ||
NetworkManager, | ||
State, | ||
__INTERNAL__, | ||
} from '@data-client/redux'; | ||
import { useSyncExternalStore } from 'react'; | ||
import { createStore, applyMiddleware } from 'redux'; | ||
|
||
import { readyContext } from './context.js'; | ||
|
||
const { createReducer, initialState, applyManager } = __INTERNAL__; | ||
|
||
export default function createPersistedStore(managers?: Manager[]) { | ||
const controller = new Controller(); | ||
managers = managers ?? [new NetworkManager()]; | ||
const nm: NetworkManager = managers.find( | ||
m => m instanceof NetworkManager, | ||
) as any; | ||
if (nm === undefined) | ||
throw new Error('managers must include a NetworkManager'); | ||
const reducer = createReducer(controller); | ||
const enhancer = applyMiddleware( | ||
// redux 5's types are wrong and do not allow any return typing from next, which is incorrect. | ||
// `next: (action: unknown) => unknown`: allows any action, but disallows all return types. | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
...applyManager(managers, controller), | ||
PromiseifyMiddleware, | ||
); | ||
const store = createStore(reducer, initialState as any, enhancer); | ||
managers.forEach(manager => manager.init?.(store.getState())); | ||
|
||
const selector = (state: any) => state; | ||
|
||
const getState = () => selector(store.getState()); | ||
let firstRender = true; | ||
function useReadyCacheState(): State<unknown> { | ||
const inFlightFetches = nm.allSettled(); | ||
if (inFlightFetches) { | ||
firstRender = false; | ||
throw inFlightFetches; | ||
} | ||
if (firstRender) { | ||
firstRender = false; | ||
throw new Promise(resolve => setTimeout(resolve, 10)); | ||
} | ||
|
||
return useSyncExternalStore(store.subscribe, getState, getState); | ||
} | ||
|
||
function ServerCacheProvider({ children }: { children: React.ReactNode }) { | ||
return ( | ||
<readyContext.Provider value={useReadyCacheState}> | ||
<ExternalCacheProvider | ||
store={store} | ||
selector={selector} | ||
controller={controller} | ||
> | ||
{children} | ||
</ExternalCacheProvider> | ||
</readyContext.Provider> | ||
); | ||
} | ||
return [ServerCacheProvider, controller, store] as const; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
'use client'; | ||
import React, { | ||
useReducer, | ||
useMemo, | ||
|
Oops, something went wrong.