Skip to content

Commit

Permalink
Make fixture list updates more resilient in lazy mode (#1474)
Browse files Browse the repository at this point in the history
* Don't override playground fixture with renderer fixture

* WIP: Make fixture list updates more resilient in lazy mode

* Remove fixtureListItemUpdate renderer response

* Only store one lazy item at a time

* Update index.tsx
  • Loading branch information
ovidiuch committed May 7, 2023
1 parent 660d495 commit 3afe6dd
Show file tree
Hide file tree
Showing 19 changed files with 162 additions and 227 deletions.
16 changes: 1 addition & 15 deletions packages/react-cosmos-core/src/renderer/rendererConnect.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { FixtureState } from '../fixtureState/types.js';
import {
FixtureId,
FixtureList,
FixtureListItem,
} from '../userModules/fixtureTypes.js';
import { FixtureId, FixtureList } from '../userModules/fixtureTypes.js';

// FYI: Renderer ids are self assigned in remote environments, so uniqueness
// cannot be established by consensus
Expand Down Expand Up @@ -77,15 +73,6 @@ export type FixtureListUpdateResponse = {
};
};

export type FixtureListItemUpdateResponse = {
type: 'fixtureListItemUpdate';
payload: {
rendererId: RendererId;
fixturePath: string;
fixtureItem: FixtureListItem;
};
};

// Caused by an organic state change inside the renderer. Also dispatched
// after a fixtureSelect request, when rendering stateful components, as their
// initial state is read.
Expand All @@ -112,7 +99,6 @@ export type RendererResponse =
| RendererReadyResponse
| RendererErrorResponse
| FixtureListUpdateResponse
| FixtureListItemUpdateResponse
| FixtureStateChangeResponse
| PlaygroundCommandResponse;

Expand Down
57 changes: 36 additions & 21 deletions packages/react-cosmos-dom/src/DomRendererProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from 'react-cosmos-renderer';
import {
RendererContext,
RendererContextValue,
RendererProvider,
} from 'react-cosmos-renderer/client';
import { getDomRendererId } from './domRendererId.js';

Expand All @@ -31,33 +31,48 @@ export function DomRendererProvider({
searchParams,
setSearchParams,
}: Props) {
const value = React.useMemo<RendererContextValue>(() => {
return {
rendererId: getDomRendererId(),
rendererConnect: createDomRendererConnect(rendererConfig.playgroundUrl),
searchParams: decodeRendererSearchParams(searchParams),
setSearchParams: (nextParams: RendererSearchParams) => {
// Implementing setSearchParams is optional. It is required for server
// fixture loaders that cannot listen to client-side 'selectFixture'
// requests from the Cosmos UI.
if (setSearchParams) {
setSearchParams(encodeRendererSearchParams(nextParams));
}
},
reloadRenderer: () => {
window.location.reload();
},
};
}, [setSearchParams, rendererConfig, searchParams]);
const rendererId = React.useMemo(() => getDomRendererId(), []);

const rendererConnect = React.useMemo(
() => createDomRendererConnect(rendererConfig.playgroundUrl),
[rendererConfig.playgroundUrl]
);

const decodedParams = React.useMemo(
() => decodeRendererSearchParams(searchParams),
[searchParams]
);

const setDecodedParams = React.useCallback(
(nextParams: RendererSearchParams) => {
// Implementing setSearchParams is optional. It is required for server
// fixture loaders that cannot listen to client-side 'selectFixture'
// requests from the Cosmos UI.
if (setSearchParams) {
setSearchParams(encodeRendererSearchParams(nextParams));
}
},
[setSearchParams]
);

return (
<RendererContext.Provider value={value}>
<RendererProvider
rendererId={rendererId}
rendererConnect={rendererConnect}
searchParams={decodedParams}
setSearchParams={setDecodedParams}
reloadRenderer={reloadRenderer}
>
{children}
{typeof window !== 'undefined' && <GlobalErrorHandler />}
</RendererContext.Provider>
</RendererProvider>
);
}

function reloadRenderer() {
window.location.reload();
}

function GlobalErrorHandler() {
const { rendererId, rendererConnect } = React.useContext(RendererContext);

Expand Down
44 changes: 25 additions & 19 deletions packages/react-cosmos-native/src/NativeRendererProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from 'react';
import { FixtureId, RendererConfig } from 'react-cosmos-core';
import { createWebSocketsConnect } from 'react-cosmos-renderer';
import {
RendererContext,
RendererContextValue,
} from 'react-cosmos-renderer/client';
import { RendererProvider } from 'react-cosmos-renderer/client';
import { DevSettings } from 'react-native';
import { getSocketUrl } from './getSocketUrl.js';

Expand All @@ -18,23 +15,32 @@ export function NativeRendererProvider({
rendererConfig,
initialFixtureId,
}: Props) {
const value = React.useMemo<RendererContextValue>(() => {
const socketUrl = getSocketUrl(rendererConfig.playgroundUrl);
return {
// TODO: Generate unique ID per device
rendererId: 'native-renderer',
rendererConnect: createWebSocketsConnect(socketUrl),
searchParams: { fixtureId: initialFixtureId },
setSearchParams: () => {},
reloadRenderer: () => {
DevSettings.reload();
},
};
}, [initialFixtureId, rendererConfig]);
const rendererConnect = React.useMemo(
() => createWebSocketsConnect(getSocketUrl(rendererConfig.playgroundUrl)),
[rendererConfig.playgroundUrl]
);

const searchParams = React.useMemo(
() => ({ fixtureId: initialFixtureId }),
[initialFixtureId]
);

return (
<RendererContext.Provider value={value}>
<RendererProvider
// TODO: Generate unique ID per device
rendererId="native-renderer"
rendererConnect={rendererConnect}
searchParams={searchParams}
setSearchParams={noop}
reloadRenderer={reloadRenderer}
>
{children}
</RendererContext.Provider>
</RendererProvider>
);
}

function reloadRenderer() {
DevSettings.reload();
}

function noop() {}
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,24 @@ testRenderer(
);

testRenderer(
'posts lazy fixture list item update on initially selected fixture',
'posts lazy fixture list with item names on initially selected fixture',
{
rendererId,
searchParams: { fixtureId: { path: 'first' } },
fixtures,
lazy: true,
},
async ({ rendererReady, fixtureListUpdate, fixtureListItemUpdate }) => {
async ({ rendererReady, fixtureListUpdate }) => {
await rendererReady({
rendererId,
selectedFixtureId: { path: 'first' },
});
await fixtureListUpdate({
rendererId,
fixtures: {
first: { type: 'single' },
first: { type: 'multi', fixtureNames: ['one'] },
second: { type: 'single' },
},
});
await fixtureListItemUpdate({
rendererId,
fixturePath: 'first',
fixtureItem: { type: 'multi', fixtureNames: ['one'] },
});
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const fixtures = wrapDefaultExport({
});

testRenderer(
'posts fixture list with names',
'posts fixture list with item names',
{ rendererId, fixtures },
async ({ fixtureListUpdate }) => {
await fixtureListUpdate({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ const fixtures = wrapDefaultExport({
});

testRenderer(
'posts lazy fixture list item update on fixture select',
'posts lazy fixture list with item names on fixture select',
{ rendererId, fixtures, lazy: true },
async ({ selectFixture, fixtureListUpdate, fixtureListItemUpdate }) => {
async ({ selectFixture, fixtureListUpdate }) => {
await fixtureListUpdate({
rendererId,
fixtures: {
Expand All @@ -24,10 +24,12 @@ testRenderer(
fixtureId: { path: 'first' },
fixtureState: {},
});
await fixtureListItemUpdate({
await fixtureListUpdate({
rendererId,
fixturePath: 'first',
fixtureItem: { type: 'multi', fixtureNames: ['a', 'b', 'c'] },
fixtures: {
first: { type: 'multi', fixtureNames: ['a', 'b', 'c'] },
second: { type: 'single' },
},
});
}
);
1 change: 1 addition & 0 deletions packages/react-cosmos-renderer/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './fixture/useSelect/index.js';
export * from './fixture/useValue/index.js';
export * from './fixtureLoaders/ClientFixtureLoader.js';
export * from './rendererConnect/RendererContext.js';
export * from './rendererConnect/RendererProvider.js';
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ type Props = {
fixtures: FixtureList;
};
export function RendererSync({ children, fixtures }: Props) {
const { searchParams, rendererId, rendererConnect, reloadRenderer } =
React.useContext(RendererContext);
const {
searchParams,
rendererId,
rendererConnect,
reloadRenderer,
lazyItems,
} = React.useContext(RendererContext);

const { fixtureId: selectedFixtureId } = searchParams;

Expand All @@ -32,10 +37,10 @@ export function RendererSync({ children, fixtures }: Props) {
type: 'fixtureListUpdate',
payload: {
rendererId,
fixtures,
fixtures: { ...fixtures, ...lazyItems },
},
});
}, [fixtures, rendererConnect, rendererId]);
}, [fixtures, lazyItems, rendererConnect, rendererId]);

React.useEffect(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,21 @@ export function FixtureProvider(props: Props) {
syncedFixtureState: {},
});

const { rendererId, rendererConnect } = React.useContext(RendererContext);
const { rendererId, rendererConnect, setLazyItems } =
React.useContext(RendererContext);

React.useEffect(() => {
// Only multi fixtures have extra info that isn't already available in the
// fixture list provided to the Cosmos UI (fixture names, which in lazy mode
// are revealed after importing a fixture module).
if (props.lazy && props.fixtureItem.type === 'multi') {
rendererConnect.postMessage({
type: 'fixtureListItemUpdate',
payload: {
rendererId,
fixturePath: props.fixtureId.path,
fixtureItem: props.fixtureItem,
},
});
if (props.lazy) {
setLazyItems(
props.fixtureItem.type === 'multi'
? { [props.fixtureId.path]: props.fixtureItem }
: noLazyItem
);
}
}, [
props.fixtureId.path,
props.fixtureItem,
props.lazy,
rendererConnect,
rendererId,
]);
}, [props.fixtureId.path, props.fixtureItem, props.lazy, setLazyItems]);

React.useEffect(() => {
if (!isEqual(state.fixtureState, state.syncedFixtureState)) {
Expand Down Expand Up @@ -116,3 +108,5 @@ export function FixtureProvider(props: Props) {
</FixtureContext.Provider>
);
}

const noLazyItem = {};
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import React from 'react';
import { RendererConnect, RendererSearchParams } from 'react-cosmos-core';
import {
FixtureList,
RendererConnect,
RendererSearchParams,
} from 'react-cosmos-core';

export type RendererContextValue = {
rendererId: string;
rendererConnect: RendererConnect;
searchParams: RendererSearchParams;
setSearchParams(nextParams: RendererSearchParams): void;
reloadRenderer(): void;
lazyItems: FixtureList;
setLazyItems: React.Dispatch<React.SetStateAction<FixtureList>>;
};

export const RendererContext = React.createContext<RendererContextValue>({
Expand All @@ -18,4 +24,6 @@ export const RendererContext = React.createContext<RendererContextValue>({
searchParams: {},
setSearchParams: () => {},
reloadRenderer: () => {},
lazyItems: {},
setLazyItems: () => {},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client';
import React from 'react';
import {
FixtureList,
RendererConnect,
RendererSearchParams,
} from 'react-cosmos-core';
import { RendererContext } from './RendererContext.js';

export type Props = {
children: React.ReactNode;
rendererId: string;
rendererConnect: RendererConnect;
searchParams: RendererSearchParams;
setSearchParams(nextParams: RendererSearchParams): void;
reloadRenderer(): void;
};
export function RendererProvider(props: Props) {
const [lazyItems, setLazyItems] = React.useState<FixtureList>({});

const value = React.useMemo(() => {
return {
rendererId: props.rendererId,
rendererConnect: props.rendererConnect,
searchParams: props.searchParams,
setSearchParams: props.setSearchParams,
reloadRenderer: props.reloadRenderer,
lazyItems,
setLazyItems,
};
}, [
lazyItems,
props.reloadRenderer,
props.rendererConnect,
props.rendererId,
props.searchParams,
props.setSearchParams,
]);

return (
<RendererContext.Provider value={value}>
{props.children}
</RendererContext.Provider>
);
}

0 comments on commit 3afe6dd

Please sign in to comment.