-
-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add @data-client/react/nextjs entrypoint (#3093)
- Loading branch information
Showing
23 changed files
with
1,488 additions
and
7 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,21 @@ | ||
--- | ||
'@data-client/react': patch | ||
--- | ||
|
||
Add /nextjs entrypoint - eliminating the need for @data-client/ssr package | ||
|
||
```tsx | ||
import { DataProvider } from '@data-client/react/nextjs'; | ||
|
||
export default function RootLayout({ children }) { | ||
return ( | ||
<html> | ||
<body> | ||
<DataProvider> | ||
{children} | ||
</DataProvider> | ||
</body> | ||
</html> | ||
); | ||
} | ||
``` |
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 |
---|---|---|
|
@@ -3,3 +3,4 @@ | |
/legacy | ||
/index.d.ts | ||
/next.d.ts | ||
/nextjs.d.ts |
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 |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import type { State } from '@data-client/core'; | ||
|
||
export const ServerData = ({ | ||
data, | ||
nonce, | ||
id = 'data-client-data', | ||
}: { | ||
data: State<unknown>; | ||
id?: string; | ||
nonce?: string | undefined; | ||
}) => { | ||
try { | ||
const encoded = JSON.stringify(data); | ||
return ( | ||
<script | ||
id={id} | ||
type="application/json" | ||
dangerouslySetInnerHTML={{ | ||
__html: encoded, | ||
}} | ||
nonce={nonce} | ||
/> | ||
); | ||
} catch (e) { | ||
console.error(`Error serializing json for ${id}`); | ||
console.error(e); | ||
return null; | ||
} | ||
}; | ||
|
||
export default ServerData; |
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,52 @@ | ||
import { initialState } from '@data-client/core'; | ||
|
||
export const awaitInitialData = (id = 'data-client-data'): Promise<any> => { | ||
return new Promise<any>((resolve, reject) => { | ||
let el: HTMLScriptElement | null; | ||
if ((el = document.getElementById(id) as any)) { | ||
resolve(getDataFromEl(el, id)); | ||
return; | ||
} | ||
document.addEventListener('DOMContentLoaded', () => { | ||
el = document.getElementById(id) as any; | ||
if (el) resolve(getDataFromEl(el, id)); | ||
else | ||
reject(new Error('failed to find DOM with reactive data client state')); | ||
}); | ||
}); | ||
}; | ||
|
||
export const getInitialData = (id = 'data-client-data') => { | ||
const el: HTMLScriptElement | null = document.getElementById(id) as any; | ||
if (!el) return initialState; | ||
return getDataFromEl(el, id); | ||
}; | ||
|
||
function getDataFromEl(el: HTMLScriptElement, key: string) { | ||
if (el.text === undefined) { | ||
console.error( | ||
`#${key} is completely empty. This could be due to CSP issues.`, | ||
); | ||
} | ||
if (getInitialData.name !== 'getInitialData') { | ||
(document as any).FUNC_MANGLE = function () { | ||
console.error( | ||
'Data Client Error: https://dataclient.io/errors/osid', | ||
this, | ||
); | ||
delete (document as any).FUNC_MANGLE; | ||
}; | ||
} | ||
if (Test.name !== 'Test') { | ||
(document as any).CLS_MANGLE = function () { | ||
console.error( | ||
'Data Client Error: https://dataclient.io/errors/dklj', | ||
this, | ||
); | ||
delete (document as any).CLS_MANGLE; | ||
}; | ||
} | ||
return el?.text ? JSON.parse(el?.text) : undefined; | ||
} | ||
|
||
class Test {} |
29 changes: 29 additions & 0 deletions
29
packages/react/src/server/nextjs/DataProvider/DataProvider.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,29 @@ | ||
'use client'; | ||
import { useMemo, type ComponentProps } from 'react'; | ||
|
||
import createPersistedStore from './createPersistedStore.js'; | ||
import ServerDataComponent from './ServerDataComponent.js'; | ||
import type CacheProvider from '../../../components/CacheProvider.js'; | ||
|
||
export default function DataProvider({ | ||
children, | ||
...props | ||
}: ProviderProps): React.ReactElement { | ||
const [ServerCacheProvider, initPromise] = useMemo(createPersistedStore, []); | ||
|
||
return ( | ||
<> | ||
<ServerDataComponent initPromise={initPromise} /> | ||
<ServerCacheProvider {...props} initPromise={initPromise}> | ||
{children} | ||
</ServerCacheProvider> | ||
</> | ||
); | ||
} | ||
|
||
type ProviderProps = Omit< | ||
Partial<ComponentProps<typeof CacheProvider>>, | ||
'initialState' | ||
> & { | ||
children: React.ReactNode; | ||
}; |
17 changes: 17 additions & 0 deletions
17
packages/react/src/server/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,17 @@ | ||
import { use } from 'react'; | ||
|
||
import ServerData from '../../ServerData.js'; | ||
|
||
const id = 'data-client-data'; | ||
|
||
const ServerDataComponent = ({ | ||
nonce, | ||
initPromise, | ||
}: { | ||
nonce?: string | undefined; | ||
initPromise: Promise<any>; | ||
}) => { | ||
const data = use(initPromise); | ||
return <ServerData data={data} id={id} nonce={nonce} />; | ||
}; | ||
export default ServerDataComponent; |
8 changes: 8 additions & 0 deletions
8
packages/react/src/server/nextjs/DataProvider/createPersistedStore.ts
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 @@ | ||
import createPersistedStoreClient from './createPersistedStoreClient.js'; | ||
import createPersistedStoreServer from './createPersistedStoreServer.js'; | ||
|
||
const createPersistedStore = | ||
typeof window === 'undefined' ? | ||
createPersistedStoreServer | ||
: createPersistedStoreClient; | ||
export default createPersistedStore; |
32 changes: 32 additions & 0 deletions
32
packages/react/src/server/nextjs/DataProvider/createPersistedStoreClient.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,32 @@ | ||
/// <reference types="react/canary" /> | ||
import { type State } from '@data-client/core'; | ||
import { ComponentProps, use } from 'react'; | ||
|
||
import CacheProvider from '../../../components/CacheProvider.js'; | ||
import { awaitInitialData } from '../../getInitialData.js'; | ||
|
||
export default function createPersistedStore() { | ||
const initPromise = awaitInitialData(); | ||
|
||
const StoreCacheProvider = ({ | ||
children, | ||
initPromise, | ||
...props | ||
}: ProviderProps) => { | ||
const initialState = use(initPromise); | ||
return ( | ||
<CacheProvider {...props} initialState={initialState}> | ||
{children} | ||
</CacheProvider> | ||
); | ||
}; | ||
return [StoreCacheProvider, initPromise] as const; | ||
} | ||
|
||
type ProviderProps = Omit< | ||
Partial<ComponentProps<typeof CacheProvider>>, | ||
'initialState' | ||
> & { | ||
children: React.ReactNode; | ||
initPromise: Promise<State<any>>; | ||
}; |
85 changes: 85 additions & 0 deletions
85
packages/react/src/server/nextjs/DataProvider/createPersistedStoreServer.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,85 @@ | ||
import { | ||
Controller, | ||
Manager, | ||
NetworkManager, | ||
State, | ||
createReducer, | ||
initialState, | ||
applyManager, | ||
} from '@data-client/core'; | ||
import type { ComponentProps } from 'react'; | ||
|
||
import type CacheProvider from '../../../components/CacheProvider.js'; | ||
import { | ||
ExternalCacheProvider, | ||
PromiseifyMiddleware, | ||
} from '../../redux/index.js'; | ||
import { createStore, applyMiddleware } from '../../redux/redux.js'; | ||
|
||
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()); | ||
|
||
const initPromise: Promise<State<any>> = (async () => { | ||
let firstRender = true; | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
const inFlightFetches = nm.allSettled(); | ||
if (inFlightFetches) { | ||
firstRender = false; | ||
await inFlightFetches; | ||
continue; | ||
} | ||
if (firstRender) { | ||
firstRender = false; | ||
// TODO: instead of waiting 10ms - see if we can wait until next part of react is streamed and race with nm getting new fetches | ||
await new Promise(resolve => setTimeout(resolve, 10)); | ||
continue; | ||
} | ||
break; | ||
} | ||
return getState(); | ||
})(); | ||
|
||
const StoreCacheProvider = ({ children }: ProviderProps) => { | ||
return ( | ||
<ExternalCacheProvider | ||
store={store} | ||
selector={selector} | ||
controller={controller} | ||
> | ||
{children} | ||
</ExternalCacheProvider> | ||
); | ||
}; | ||
|
||
return [StoreCacheProvider, initPromise] as const; | ||
} | ||
|
||
type ProviderProps = Omit< | ||
Partial<ComponentProps<typeof CacheProvider>>, | ||
'initialState' | ||
> & { | ||
children: React.ReactNode; | ||
initPromise: Promise<State<any>>; | ||
}; |
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 @@ | ||
export { default as DataProvider } from './DataProvider/DataProvider.js'; |
Oops, something went wrong.