Socket
Socket
Sign inDemoInstall

@shopify/react-effect

Package Overview
Dependencies
Maintainers
12
Versions
75
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@shopify/react-effect - npm Package Compare versions

Comparing version 1.1.0-beta.1 to 2.0.0

dist/context.d.ts

10

CHANGELOG.md

@@ -10,2 +10,12 @@ # Changelog

## [2.0.0]
### Changed
- Removed `react-tree-walker` as a way to process the React element. Instead, the application is rendered to a string repeatedly until no more promises have been queued. For full details on migrating to the new API, please read the [upgrade guide](./documentation/migrating-version-1-to-2.md). [#477](https://github.com/Shopify/quilt/pull/477)
## [1.0.3]
- Manual release
## [1.0.1]

@@ -12,0 +22,0 @@

20

dist/Effect.d.ts

@@ -1,15 +0,11 @@

import * as React from 'react';
import { METHOD_NAME, Extractable } from './extractable';
/// <reference types="react" />
import { Omit } from '@shopify/useful-types';
import { EffectManager } from './context';
import { EffectKind } from './types';
interface Props {
kind?: symbol;
serverOnly?: boolean;
clientOnly?: boolean;
perform(): void;
kind?: EffectKind;
manager?: EffectManager;
perform(): any;
}
export default class Effect extends React.PureComponent<Props> implements Extractable {
componentDidMount(): void;
[METHOD_NAME](include: boolean | symbol[]): void;
render(): {} | null;
private perform;
}
export default function Effect(props: Omit<Props, 'manager'>): JSX.Element | null;
export {};

40

dist/Effect.js

@@ -5,31 +5,27 @@ "use strict";

var React = tslib_1.__importStar(require("react"));
var extractable_1 = require("./extractable");
var Effect = /** @class */ (function (_super) {
tslib_1.__extends(Effect, _super);
function Effect() {
var context_1 = require("./context");
var ConnectedEffect = /** @class */ (function (_super) {
tslib_1.__extends(ConnectedEffect, _super);
function ConnectedEffect() {
return _super !== null && _super.apply(this, arguments) || this;
}
Effect.prototype.componentDidMount = function () {
var serverOnly = this.props.serverOnly;
return this.perform(!serverOnly);
};
Effect.prototype[extractable_1.METHOD_NAME] = function (include) {
var clientOnly = this.props.clientOnly;
return this.perform(clientOnly ? false : include);
};
Effect.prototype.render = function () {
ConnectedEffect.prototype.render = function () {
this.perform();
return this.props.children || null;
};
Effect.prototype.perform = function (include) {
var _a = this.props, kind = _a.kind, perform = _a.perform;
if (!include) {
return undefined;
ConnectedEffect.prototype.perform = function () {
var _a = this.props, kind = _a.kind, manager = _a.manager, perform = _a.perform;
if (manager == null || (kind != null && !manager.shouldPerform(kind))) {
return;
}
if (include === true || (kind != null && include.includes(kind))) {
return perform();
}
return undefined;
manager.add(perform(), kind);
};
return Effect;
return ConnectedEffect;
}(React.PureComponent));
function Effect(props) {
if (typeof window !== 'undefined') {
return null;
}
return (React.createElement(context_1.EffectContext.Consumer, null, function (manager) { return React.createElement(ConnectedEffect, tslib_1.__assign({ manager: manager }, props)); }));
}
exports.default = Effect;
export { default as Effect } from './Effect';
export { Extractable, METHOD_NAME } from './extractable';
export { EffectManager } from './context';
export { EffectKind } from './types';

@@ -5,3 +5,3 @@ "use strict";

exports.Effect = Effect_1.default;
var extractable_1 = require("./extractable");
exports.METHOD_NAME = extractable_1.METHOD_NAME;
var context_1 = require("./context");
exports.EffectManager = context_1.EffectManager;

@@ -1,5 +0,10 @@

import { ReactElement } from 'react';
export interface Middleware {
(instance: any): any;
import * as React from 'react';
interface Options {
include?: symbol[] | boolean;
decorate?(element: React.ReactElement<any>): React.ReactElement<any>;
renderFunction?(element: React.ReactElement<{}>): string;
betweenEachPass?(): any;
afterEachPass?(): any;
}
export declare function extract(app: ReactElement<any>, include?: symbol[] | boolean, middleware?: Middleware[]): Promise<void>;
export declare function extract(app: React.ReactElement<any>, { include, decorate, renderFunction, betweenEachPass, afterEachPass, }?: Options): Promise<string>;
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var react_traverse_nodes_1 = require("@shopify/react-traverse-nodes");
var extractable_1 = require("./extractable");
var defaultContext = {
reactExtractRunning: true,
};
function extract(app, include, middleware) {
if (include === void 0) { include = true; }
if (middleware === void 0) { middleware = []; }
var extractors = tslib_1.__spread([
function (instance) {
return extractable_1.isExtractable(instance) ? instance[extractable_1.METHOD_NAME](include) : undefined;
}
], middleware);
return react_traverse_nodes_1.traverse(app, function (_, instance) {
return Promise.all(extractors.map(function (extractor) { return Promise.resolve(extractor(instance)); }));
}, tslib_1.__assign({}, defaultContext));
var React = tslib_1.__importStar(require("react"));
var server_1 = require("react-dom/server");
var context_1 = require("./context");
function extract(app, _a) {
var _b = _a === void 0 ? {} : _a, include = _b.include, _c = _b.decorate, decorate = _c === void 0 ? identity : _c, _d = _b.renderFunction, renderFunction = _d === void 0 ? server_1.renderToStaticMarkup : _d, betweenEachPass = _b.betweenEachPass, afterEachPass = _b.afterEachPass;
var manager = new context_1.EffectManager({ include: include });
var element = (React.createElement(context_1.EffectContext.Provider, { value: manager }, decorate(app)));
return (function perform() {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var result;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
result = renderFunction(element);
if (!manager.finished) return [3 /*break*/, 4];
return [4 /*yield*/, manager.afterEachPass()];
case 1:
_a.sent();
if (!afterEachPass) return [3 /*break*/, 3];
return [4 /*yield*/, afterEachPass()];
case 2:
_a.sent();
_a.label = 3;
case 3: return [2 /*return*/, result];
case 4: return [4 /*yield*/, manager.resolve()];
case 5:
_a.sent();
return [4 /*yield*/, manager.betweenEachPass()];
case 6:
_a.sent();
if (!betweenEachPass) return [3 /*break*/, 8];
return [4 /*yield*/, betweenEachPass()];
case 7:
_a.sent();
_a.label = 8;
case 8: return [4 /*yield*/, manager.afterEachPass()];
case 9:
_a.sent();
if (!afterEachPass) return [3 /*break*/, 11];
return [4 /*yield*/, afterEachPass()];
case 10:
_a.sent();
_a.label = 11;
case 11: return [2 /*return*/, perform()];
}
});
});
})();
}
exports.extract = extract;
function identity(value) {
return value;
}
{
"name": "@shopify/react-effect",
"version": "1.1.0-beta.1",
"version": "2.0.0",
"license": "MIT",

@@ -26,6 +26,7 @@ "description": "A component and set of utilities for performing effects within a universal React app",

"dependencies": {
"@shopify/react-traverse-nodes": "^0.1.0-beta.2",
"@shopify/useful-types": "^1.1.0",
"tslib": "^1.9.3"
},
"devDependencies": {
"@types/react-dom": "^16.0.11",
"typescript": "~3.0.1"

@@ -32,0 +33,0 @@ },

@@ -28,7 +28,2 @@ # `@shopify/react-effect`

By default, this callback will run in two ways:
- On the client, during `componentDidMount`
- On the server, when called using the [`extract`](#extract) function documented below
This callback can return anything, but returning a promise has a special effect: it will be waited for on the server when calling `extract()`.

@@ -38,5 +33,3 @@

- `kind`: a symbol detailing the "category" of the effect. This will be used to optionally skip some categories when calling `extract()`
- `clientOnly`: will only call the effect in `componentDidMount`, not when extracting on the server
- `serverOnly`: will only call the effect during extraction, not in `componentDidMount`
- `kind`: a description of the effect. This kind, if provided, must have an `id` that is a unique symbol, and can optionally have `betweenEachPass` and `afterEachPass` functions that add additional logic to the `betweenEachPass` and `afterEachPass` options for `extract()`.

@@ -47,6 +40,5 @@ This component also accepts children which are rendered as-is.

You can call `extract()` on a React tree in order to perform all of the effects within that tree. This function uses the [`react-tree-walker`](https://github.com/ctrlplusb/react-tree-walker) package to perform the tree walk, which creates some implications for the return value of your `perform` actions:
You can call `extract()` on a React tree in order to perform all of the effects within that tree. This function repeatedly calls a render function (by default, `react-dom`’s `renderToStaticMarkup`), collects any `Effect` promises and, if there are promises, waits on them before performing another pass. This process ends when no more promises are collected during a pass of your tree.
- Returning a promise will wait on the promise before processing the rest of the tree
- Returning `false` will prevent the rest of the tree from being processed
> **Note**: this flow is significantly different from the previous version, which relied on a custom tree walk. Calling `extract()` no longer waits for promises collected higher in the tree before processing the rest. Instead, it relies on multiple passes, which gives application code the option to process promises at many layers of the app in parallel, rather than in sequence.

@@ -66,41 +58,46 @@ This function returns a promise that resolves when the tree has been fully processed.

You may optionally pass a second argument to this function: an array of symbols representing the categories to include in your processing of the tree (matching the `kind` prop on `Extract` components).
You may optionally pass an options object that contains the following keys (all of which are optional):
```tsx
import {renderToString} from 'react-dom/server';
import {EFFECT_ID as I18N_EFFECT_ID} from '@shopify/react-i18n';
import {extract} from '@shopify/react-effect/server';
- `include`: an array of symbols that should be collected during tree traversal. These IDs must align with the `kind.id` field on `<Extract />` elements in your application.
async function app(ctx) {
const app = <App />;
// will only perform @shopify/react-i18n extraction
await extract(app, [I18N_EFFECT_ID]);
ctx.body = renderToString(app);
}
```
```tsx
import {renderToString} from 'react-dom/server';
import {EFFECT_ID as I18N_EFFECT_ID} from '@shopify/react-i18n';
import {extract} from '@shopify/react-effect/server';
### Custom extractable components
async function app(ctx) {
const app = <App />;
// will only perform @shopify/react-i18n extraction
await extract(app, [I18N_EFFECT_ID]);
ctx.body = renderToString(app);
}
```
Usually, the `Extract` component will do what you need, but you may occasionally need your own component to directly implement the "extractable" part. This can be the case when your component must do something in `extract` that ends up calling `setState`. In these cases, you can use two additional exports from this module, `METHOD_NAME` and the `Extractable` interface, to manually implement a method that will be called during extraction:
- `betweenEachPass`: a function that is called after a pass of your tree that did not "finish" (that is, there were still promises that got collected). This function can return a promise, and it will be waited on before continuing.
```ts
import {METHOD_NAME, Extractable} from '@shopify/react-effect';
- `afterEachPass`: a function that is called after each pass of your tree, regardless of whether traversal is "finished". This function can return a promise, and it will be waited on before continuing.
export const EFFECT_ID = Symbol('MyComponentEffect');
- `decorate`: a function that takes the root React element in your tree and returns a new tree to use. You can use this to wrap your application in context providers that only your server render requires.
class MyComponent extends React.Component implements Extractable {
[METHOD_NAME](include: boolean | symbol[]) {
// When implementing your own version of this, you should
// implement your own check for the effect "kind". The
// Effect component does this automatically.
if (
include === true ||
(Array.isArray(include) && include.includes(EFFECT_ID))
) {
this.setState({extracting: true});
}
```tsx
import {renderToString} from 'react-dom/server';
import {extract} from '@shopify/react-effect/server';
import {createApolloBridge} from '@shopify/react-effect-apollo';
async function app(ctx) {
const ApolloBridge = createApolloBridge();
const app = <App />;
await extract(app, {
decorate(element) {
return <ApolloBridge>{element}</ApolloBridge>;
},
});
ctx.body = renderToString(app);
}
}
```
```
- `renderFunction`: an alternative function to `renderToStaticMarkup` for traversing the tree.
## Gotchas

@@ -107,0 +104,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc