Skip to content

Commit

Permalink
Merge pull request #28 from webkom/purge
Browse files Browse the repository at this point in the history
  • Loading branch information
eikhr committed Dec 1, 2022
2 parents 4f8dbf0 + 62c978c commit f3b49d9
Show file tree
Hide file tree
Showing 13 changed files with 76 additions and 610 deletions.
44 changes: 4 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,45 +135,10 @@ If no `depsFn` is provided, an empty dependency-array will be used, and the side

Available `opts` are the same as in `usePreparedEffect`.

### `dispatched(sideEffect: async(props, dispatch), opts)(Component)`

Helper to use `prepared` more simply if your side effects consists mostly of dispatching redux actions.

In the body of the `sideEffect` function, you can use the `dispatch` function to dispatch redux actions, typically
requesting data from an asynchronous source (API server, etc.).
For example, let's assume you have defined an async action creator `fetchTodoItems(userName)` that fetches the todo-items from a REST API,
and that you are defining a component with a `userName` prop. To decorate your component, your code would look like:

```js
class TodoItems extends React.PureComponent { ... }

const DispatchedTodoItems = dispatched(
({ userName }, dispatch) => dispatch(fetchTodoItems(userName))
)(TodoItems);
```

The decorated component will have the following behavior:

- when server-side rendered using `prepare`, `sideEffect` will be run and awaited before the component is rendered; if `sideEffect` throws, `prepare` will also throw.
- when client-side rendered, `sideEffect` will be called on `componentDidMount` and `componentWillReceiveProps`.

`opts` is an optional configuration object passed directly to the underlying `prepared` decorator (see below).

### `prepared(sideEffect: async(props, context), opts)(Component)`

Decorates `Component` so that when `prepare` is called, `sideEffect` is called (and awaited) before continuing the rendering traversal.

Available `opts` is an optional configuration object:

- `opts.pure` (default: `true`): the decorated component extends `PureComponent` instead of `Component`.
- `opts.componentDidMount` (default: `true`): on the client, `sideEffect` is called when the component is mounted.
- `opts.componentWillReceiveProps` (default: `true`): on the client, `sideEffect` is called again whenever the component receive props.
- `opts.awaitOnSsr` (default: `true`): on the server, should `prepare` await `sideEffect` before traversing further down the tree. When `false` the promise will be awaited before `prepare` returns.

### `async prepare(Element, ?opts) => string`

Recursively traverses the element rendering tree, collecting all promises from `usePreparedEffect`, `withPreparedEffect`, `prepared` (or `dispatched`) and awaits them as defined (either after traversing the tree, or before traversing further with `awaitOnSsr` or `runSync`).
It should be used (and `await`-ed) _before_ calling `renderToString` on the server. If any of the side effects throws, and an `errorHandler` has not been provided, `prepare` will also throw.
Recursively traverses the element rendering tree, collecting all promises from `usePreparedEffect` and `withPreparedEffect` and awaits them as defined (either after traversing the tree, or before traversing further with `runSync=true`).
It should be used (and `await`-ed) _before_ calling `renderToString` on the server. If any of the side effects throws, `prepare` will also throw.

The return value is a string containing js-code to set a key on the `window` object, that will be used in the client-side rendering to avoid re-running any side effects.

Expand All @@ -184,6 +149,5 @@ Available `opts` is an optional configuration object:

### Notes

`react-prepare` tries hard to avoid object keys conflicts, but since React isn't very friendly with `Symbol`, it uses a special key for its internal use.
The single polluted key in the components key namespace is `@__REACT_PREPARE__@`, which shouldn't be an issue.
This key is also used in the `window` object to store what side-effects are prepared.
`react-prepare` tries hard to avoid object keys conflicts, but it does require setting a variable on the `window` object to store what side-effects are prepared.
The single polluted key on the `window` object is `@__REACT_PREPARE__@`, which shouldn't be an issue.
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@webkom/react-prepare",
"version": "0.10.3",
"version": "1.0.0",
"description": "Prepare you app state for async server-side rendering and more!",
"type": "module",
"main": "./dist/react-prepare.umd.cjs",
Expand Down Expand Up @@ -48,9 +48,6 @@
"prop-types": "^15.8.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-redux": "^5.1.2",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"rimraf": "^3.0.2",
"sinon": "^13.0.1",
"typescript": "^4.7.4",
Expand Down
32 changes: 0 additions & 32 deletions src/dispatched.ts

This file was deleted.

4 changes: 1 addition & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import usePreparedEffect from './usePreparedEffect';
import withPreparedEffect from './withPreparedEffect';
import dispatched from './dispatched';
import prepare from './prepare';
import prepared from './prepared';
import type { PrepareOptions } from './prepare';
import type { PreparedEffectOptions } from './usePreparedEffect';

export { dispatched, prepare, prepared, usePreparedEffect, withPreparedEffect };
export { prepare, usePreparedEffect, withPreparedEffect };
export type { PrepareOptions, PreparedEffectOptions };

export default prepare;
49 changes: 13 additions & 36 deletions src/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import React, {
ReactNode,
} from 'react';

import isThenable from './utils/isThenable';
import { isPrepared, getPrepare, shouldAwaitOnSsr } from './prepared';
import getElementType, { ELEMENT_TYPE } from './utils/getElementType';
import getContextValue from './utils/getContextValue';
import {
Expand Down Expand Up @@ -88,33 +86,12 @@ function renderCompositeElementInstance<P>(
return [instance.render(), childContext];
}

async function prepareCompositeElement<P>(
element: ClassicElement<P>,
errorHandler: (error: unknown) => void,
context: PrepareContext,
): Promise<[ReactNode, PrepareContext, Promise<void>?]> {
const { type, props } = element;
let preparePromise;

if (isPrepared(type)) {
const prepareResult = getPrepare(type)(props, context);
if (isThenable(prepareResult)) {
preparePromise = prepareResult.catch(errorHandler);
if (shouldAwaitOnSsr(type)) {
await preparePromise;
}
}
}
const instance = createCompositeElementInstance(element, context);
return [...renderCompositeElementInstance(instance, context), preparePromise];
}

async function prepareElement(
element: ReactNode,
errorHandler: (error: unknown) => void,
context: PrepareContext,
dispatcher: Dispatcher,
): Promise<[ReactNode, PrepareContext, Promise<void>?]> {
): Promise<[ReactNode, PrepareContext]> {
switch (getElementType(element)) {
case ELEMENT_TYPE.NOTHING:
case ELEMENT_TYPE.TEXT_NODE: {
Expand Down Expand Up @@ -168,11 +145,9 @@ async function prepareElement(
return [children, context];
}
case ELEMENT_TYPE.CLASS_COMPONENT: {
return prepareCompositeElement(
element as ClassicElement<unknown>,
errorHandler,
context,
);
const classElement = element as ClassicElement<unknown>;
const instance = createCompositeElementInstance(classElement, context);
return [...renderCompositeElementInstance(instance, context)];
}
default: {
throw new Error(`Unsupported element type: ${element}`);
Expand All @@ -190,20 +165,22 @@ async function internalPrepare(
context: PrepareContext = {},
dispatcher: Dispatcher,
): Promise<unknown> {
const [children, childContext, preparePromise] = await prepareElement(
const [children, childContext] = await prepareElement(
element,
options.errorHandler,
context,
dispatcher,
);

// Recursively run prepare on children, and return a promise that resolves once all children are prepared.
return Promise.all(
React.Children.toArray(children)
.map((child) => internalPrepare(child, options, childContext, dispatcher))
.concat(preparePromise || [])
.concat(popHookPromises(dispatcher)),
// Recursively run prepare on children, awaiting any runSync=true promises and collecting any preparedEffect-promises in the dispatcher
await Promise.all(
React.Children.toArray(children).map((child) =>
internalPrepare(child, options, childContext, dispatcher),
),
);

// Return a promise that resolves once all collected runSync=false promises are prepared
return Promise.all(popHookPromises(dispatcher));
}

async function prepare(
Expand Down
102 changes: 0 additions & 102 deletions src/prepared.tsx

This file was deleted.

Loading

0 comments on commit f3b49d9

Please sign in to comment.