@khanacademy/wonder-blocks-core 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/es/index.js +70 -65
- package/dist/index.js +66 -59
- package/package.json +2 -3
- package/src/components/__tests__/id-provider.test.js +1 -0
- package/src/components/__tests__/render-state-root.test.js +72 -0
- package/src/components/__tests__/unique-id-provider.test.js +23 -0
- package/src/components/__tests__/with-ssr-placeholder.test.js +71 -0
- package/src/components/render-state-root.js +38 -0
- package/src/components/unique-id-provider.js +1 -0
- package/src/components/with-ssr-placeholder.js +1 -0
- package/src/hooks/__tests__/use-unique-id.test.js +199 -50
- package/src/hooks/use-unique-id.js +23 -27
- package/src/hooks/use-unique-id.stories.mdx +32 -4
- package/src/index.js +1 -0
- package/src/util/__tests__/add-style.test.js +1 -0
- package/src/util/__tests__/enumerate-scroll-ancestors.test.js +1 -0
- package/LICENSE +0 -21
- package/src/hooks/__tests__/use-render-state.test.js +0 -16
- package/src/hooks/use-render-state.js +0 -24
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @khanacademy/wonder-blocks-core
|
|
2
|
+
|
|
3
|
+
## 4.1.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- 45588e5f: Fix an issue with `useUniqueIdWithMock`/`useUniqueIdWithoutMock`
|
|
7
|
+
rerender more than was needed. The fix introduces `<RenderStateRoot>`
|
|
8
|
+
which must be an ancestor to all components uses these hooks.
|
|
9
|
+
- 875b7893: Nesting of `RenderStateRoot`s inside each other can result in extra renders
|
|
10
|
+
and potentially incorrect behavior. `RenderStateRoot` now throws if it
|
|
11
|
+
appears as a descendent of another `RenderStateRoot`.
|
package/dist/es/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _extends from '@babel/runtime/helpers/extends';
|
|
2
2
|
import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
|
|
3
|
-
import
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useContext as useContext$1, useRef } from 'react';
|
|
4
5
|
import { StyleSheet, css } from 'aphrodite';
|
|
5
6
|
|
|
6
7
|
function flatten(list) {
|
|
@@ -76,9 +77,9 @@ function processStyleList(style) {
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
const _excluded = ["children", "style", "tag", "testId"];
|
|
80
|
+
const _excluded$2 = ["children", "style", "tag", "testId"];
|
|
80
81
|
const isHeaderRegex = /^h[1-6]$/;
|
|
81
|
-
const styles = StyleSheet.create({
|
|
82
|
+
const styles$1 = StyleSheet.create({
|
|
82
83
|
text: {
|
|
83
84
|
// Disable subpixel antialiasing on Mac desktop for consistency of
|
|
84
85
|
// rendering with mobile and Sketch (neither of which support it).
|
|
@@ -106,7 +107,7 @@ const styles = StyleSheet.create({
|
|
|
106
107
|
* - An array combining the above
|
|
107
108
|
*/
|
|
108
109
|
|
|
109
|
-
class Text extends Component {
|
|
110
|
+
class Text extends React.Component {
|
|
110
111
|
render() {
|
|
111
112
|
const _this$props = this.props,
|
|
112
113
|
{
|
|
@@ -115,11 +116,11 @@ class Text extends Component {
|
|
|
115
116
|
tag: Tag,
|
|
116
117
|
testId
|
|
117
118
|
} = _this$props,
|
|
118
|
-
otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
|
|
119
|
+
otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
|
|
119
120
|
|
|
120
121
|
const isHeader = isHeaderRegex.test(Tag);
|
|
121
|
-
const styleAttributes = processStyleList([styles.text, isHeader && styles.header, style]);
|
|
122
|
-
return /*#__PURE__*/createElement(Tag, _extends({}, otherProps, {
|
|
122
|
+
const styleAttributes = processStyleList([styles$1.text, isHeader && styles$1.header, style]);
|
|
123
|
+
return /*#__PURE__*/React.createElement(Tag, _extends({}, otherProps, {
|
|
123
124
|
style: styleAttributes.style,
|
|
124
125
|
className: styleAttributes.className,
|
|
125
126
|
"data-test-id": testId
|
|
@@ -149,7 +150,7 @@ function addStyle(Component, defaultStyle) {
|
|
|
149
150
|
className: aphroditeClassName,
|
|
150
151
|
style: inlineStyles
|
|
151
152
|
} = processStyleList([reset, defaultStyle, style]);
|
|
152
|
-
return /*#__PURE__*/createElement(Component, _extends({}, otherProps, {
|
|
153
|
+
return /*#__PURE__*/React.createElement(Component, _extends({}, otherProps, {
|
|
153
154
|
className: [aphroditeClassName, className].filter(Boolean).join(" "),
|
|
154
155
|
style: inlineStyles
|
|
155
156
|
}));
|
|
@@ -174,8 +175,8 @@ const overrides = StyleSheet.create({
|
|
|
174
175
|
}
|
|
175
176
|
});
|
|
176
177
|
|
|
177
|
-
const _excluded
|
|
178
|
-
const styles
|
|
178
|
+
const _excluded = ["testId", "tag"];
|
|
179
|
+
const styles = StyleSheet.create({
|
|
179
180
|
// https://github.com/facebook/css-layout#default-values
|
|
180
181
|
default: {
|
|
181
182
|
alignItems: "stretch",
|
|
@@ -193,11 +194,11 @@ const styles$1 = StyleSheet.create({
|
|
|
193
194
|
minWidth: 0
|
|
194
195
|
}
|
|
195
196
|
});
|
|
196
|
-
const StyledDiv = addStyle("div", styles
|
|
197
|
-
const StyledArticle = addStyle("article", styles
|
|
198
|
-
const StyledAside = addStyle("aside", styles
|
|
199
|
-
const StyledNav = addStyle("nav", styles
|
|
200
|
-
const StyledSection = addStyle("section", styles
|
|
197
|
+
const StyledDiv = addStyle("div", styles.default);
|
|
198
|
+
const StyledArticle = addStyle("article", styles.default);
|
|
199
|
+
const StyledAside = addStyle("aside", styles.default);
|
|
200
|
+
const StyledNav = addStyle("nav", styles.default);
|
|
201
|
+
const StyledSection = addStyle("section", styles.default);
|
|
201
202
|
/**
|
|
202
203
|
* View is a building block for constructing other components. `View` roughly
|
|
203
204
|
* maps to `div` and `Text` roughly maps to `span`. You can override which tag
|
|
@@ -212,14 +213,14 @@ const StyledSection = addStyle("section", styles$1.default);
|
|
|
212
213
|
* - An array combining the above
|
|
213
214
|
*/
|
|
214
215
|
|
|
215
|
-
class View extends Component {
|
|
216
|
+
class View extends React.Component {
|
|
216
217
|
render() {
|
|
217
218
|
const _this$props = this.props,
|
|
218
219
|
{
|
|
219
220
|
testId,
|
|
220
221
|
tag
|
|
221
222
|
} = _this$props,
|
|
222
|
-
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded
|
|
223
|
+
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
|
|
223
224
|
|
|
224
225
|
const props = _extends({}, restProps, {
|
|
225
226
|
"data-test-id": testId
|
|
@@ -227,19 +228,19 @@ class View extends Component {
|
|
|
227
228
|
|
|
228
229
|
switch (tag) {
|
|
229
230
|
case "article":
|
|
230
|
-
return /*#__PURE__*/createElement(StyledArticle, props);
|
|
231
|
+
return /*#__PURE__*/React.createElement(StyledArticle, props);
|
|
231
232
|
|
|
232
233
|
case "aside":
|
|
233
|
-
return /*#__PURE__*/createElement(StyledAside, props);
|
|
234
|
+
return /*#__PURE__*/React.createElement(StyledAside, props);
|
|
234
235
|
|
|
235
236
|
case "nav":
|
|
236
|
-
return /*#__PURE__*/createElement(StyledNav, props);
|
|
237
|
+
return /*#__PURE__*/React.createElement(StyledNav, props);
|
|
237
238
|
|
|
238
239
|
case "section":
|
|
239
|
-
return /*#__PURE__*/createElement(StyledSection, props);
|
|
240
|
+
return /*#__PURE__*/React.createElement(StyledSection, props);
|
|
240
241
|
|
|
241
242
|
case "div":
|
|
242
|
-
return /*#__PURE__*/createElement(StyledDiv, props);
|
|
243
|
+
return /*#__PURE__*/React.createElement(StyledDiv, props);
|
|
243
244
|
|
|
244
245
|
default:
|
|
245
246
|
throw Error(`${tag} is not an allowed value for the 'tag' prop`);
|
|
@@ -272,7 +273,7 @@ const RenderState = require("flow-enums-runtime")({
|
|
|
272
273
|
* means that we're all now doing non-SSR rendering
|
|
273
274
|
*/
|
|
274
275
|
|
|
275
|
-
const RenderStateContext = /*#__PURE__*/createContext(RenderState.Root);
|
|
276
|
+
const RenderStateContext = /*#__PURE__*/React.createContext(RenderState.Root);
|
|
276
277
|
|
|
277
278
|
/**
|
|
278
279
|
* We use render functions so that we don't do any work unless we need to.
|
|
@@ -303,7 +304,7 @@ const RenderStateContext = /*#__PURE__*/createContext(RenderState.Root);
|
|
|
303
304
|
* </WithSSRPlaceholder>
|
|
304
305
|
* ```
|
|
305
306
|
*/
|
|
306
|
-
class WithSSRPlaceholder extends Component {
|
|
307
|
+
class WithSSRPlaceholder extends React.Component {
|
|
307
308
|
constructor(...args) {
|
|
308
309
|
super(...args);
|
|
309
310
|
this.state = {
|
|
@@ -339,7 +340,7 @@ class WithSSRPlaceholder extends Component {
|
|
|
339
340
|
if (mounted) {
|
|
340
341
|
// This is our second non-SSR render, so let's tell everyone to
|
|
341
342
|
// do their thing.
|
|
342
|
-
return /*#__PURE__*/createElement(RenderStateContext.Provider, {
|
|
343
|
+
return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
|
|
343
344
|
value: RenderState.Standard
|
|
344
345
|
}, children());
|
|
345
346
|
} // OK, this is the very first render.
|
|
@@ -349,7 +350,7 @@ class WithSSRPlaceholder extends Component {
|
|
|
349
350
|
|
|
350
351
|
|
|
351
352
|
if (placeholder) {
|
|
352
|
-
return /*#__PURE__*/createElement(RenderStateContext.Provider, {
|
|
353
|
+
return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
|
|
353
354
|
value: RenderState.Initial
|
|
354
355
|
}, placeholder());
|
|
355
356
|
} // Otherwise, we return nothing.
|
|
@@ -414,7 +415,7 @@ class WithSSRPlaceholder extends Component {
|
|
|
414
415
|
}
|
|
415
416
|
|
|
416
417
|
render() {
|
|
417
|
-
return /*#__PURE__*/createElement(RenderStateContext.Consumer, null, value => this._maybeRender(value));
|
|
418
|
+
return /*#__PURE__*/React.createElement(RenderStateContext.Consumer, null, value => this._maybeRender(value));
|
|
418
419
|
}
|
|
419
420
|
|
|
420
421
|
}
|
|
@@ -520,7 +521,7 @@ var SsrIDFactory$1 = SsrIDFactory.Default;
|
|
|
520
521
|
* `get("test2")` will always equal `get("test2")`, but `get("test")` will
|
|
521
522
|
* never equal `get("test2")`.
|
|
522
523
|
*/
|
|
523
|
-
class UniqueIDProvider extends Component {
|
|
524
|
+
class UniqueIDProvider extends React.Component {
|
|
524
525
|
_performRender(firstRender) {
|
|
525
526
|
const {
|
|
526
527
|
children,
|
|
@@ -553,7 +554,7 @@ class UniqueIDProvider extends Component {
|
|
|
553
554
|
// Here we use the WithSSRPlaceholder component to control
|
|
554
555
|
// when we render and whether we provide a mock or real
|
|
555
556
|
// identifier factory.
|
|
556
|
-
return /*#__PURE__*/createElement(WithSSRPlaceholder, {
|
|
557
|
+
return /*#__PURE__*/React.createElement(WithSSRPlaceholder, {
|
|
557
558
|
placeholder: () => this._performRender(true)
|
|
558
559
|
}, () => this._performRender(false));
|
|
559
560
|
}
|
|
@@ -574,7 +575,7 @@ class UniqueIDProvider extends Component {
|
|
|
574
575
|
* e.g. It uses the same generated id to connect a Dialog with its main title, or form label
|
|
575
576
|
* with the associated input element, etc.
|
|
576
577
|
*/
|
|
577
|
-
class IDProvider extends Component {
|
|
578
|
+
class IDProvider extends React.Component {
|
|
578
579
|
renderChildren(ids) {
|
|
579
580
|
const {
|
|
580
581
|
id,
|
|
@@ -600,7 +601,7 @@ class IDProvider extends Component {
|
|
|
600
601
|
// need it.
|
|
601
602
|
return this.renderChildren();
|
|
602
603
|
} else {
|
|
603
|
-
return /*#__PURE__*/createElement(UniqueIDProvider, {
|
|
604
|
+
return /*#__PURE__*/React.createElement(UniqueIDProvider, {
|
|
604
605
|
scope: scope,
|
|
605
606
|
mockOnFirstRender: true
|
|
606
607
|
}, ids => this.renderChildren(ids));
|
|
@@ -628,25 +629,6 @@ var server = {
|
|
|
628
629
|
}
|
|
629
630
|
};
|
|
630
631
|
|
|
631
|
-
/**
|
|
632
|
-
* This is a thin wrapper around React.useContext(RenderStateContext).
|
|
633
|
-
*
|
|
634
|
-
* RenderStateContext is used by WithSSRPlaceholder to hide a component from
|
|
635
|
-
* rendered during SSR. It's also used by useUniqueId(With|Without)Mock to
|
|
636
|
-
* to return a UniqueIDFactory instance in a SSR-safe way.
|
|
637
|
-
*
|
|
638
|
-
* This hook isn't part of wonder-blocks-core's public interface since we still
|
|
639
|
-
* need to use WithSSRPlaceholder to update the value in the context.
|
|
640
|
-
*
|
|
641
|
-
* TODO(FEI-4153): Figure out whether we should always render a WithSSRPlaceholder
|
|
642
|
-
* at the root of our render tree just to update the value of the context. Once
|
|
643
|
-
* we figure this out we can make `useRenderState` public and create some proper
|
|
644
|
-
* documentation for it.
|
|
645
|
-
*/
|
|
646
|
-
const useRenderState = () => {
|
|
647
|
-
return useContext(RenderStateContext);
|
|
648
|
-
};
|
|
649
|
-
|
|
650
632
|
/**
|
|
651
633
|
* Returns a unique identifier factory. If the parent component hasn't
|
|
652
634
|
* been mounted yet, the global SsrIDFactory will be returned until the
|
|
@@ -656,15 +638,14 @@ const useRenderState = () => {
|
|
|
656
638
|
* @returns {IIdentifierFactory}
|
|
657
639
|
*/
|
|
658
640
|
const useUniqueIdWithMock = scope => {
|
|
659
|
-
const renderState =
|
|
660
|
-
const [isMounted, setIsMounted] = useState(false);
|
|
641
|
+
const renderState = useContext$1(RenderStateContext);
|
|
661
642
|
const idFactory = useRef(null);
|
|
662
|
-
useEffect(() => {
|
|
663
|
-
// triggers a re-render now that the component is mounted
|
|
664
|
-
setIsMounted(true);
|
|
665
|
-
}, []);
|
|
666
643
|
|
|
667
|
-
if (renderState === RenderState.
|
|
644
|
+
if (renderState === RenderState.Root) {
|
|
645
|
+
throw new Error("Components using useUniqueIdWithMock() should be descendants of <RenderStateRoot>");
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (renderState === RenderState.Initial) {
|
|
668
649
|
return SsrIDFactory$1;
|
|
669
650
|
}
|
|
670
651
|
|
|
@@ -683,15 +664,14 @@ const useUniqueIdWithMock = scope => {
|
|
|
683
664
|
*/
|
|
684
665
|
|
|
685
666
|
const useUniqueIdWithoutMock = scope => {
|
|
686
|
-
const renderState =
|
|
687
|
-
const [isMounted, setIsMounted] = useState(false);
|
|
667
|
+
const renderState = useContext$1(RenderStateContext);
|
|
688
668
|
const idFactory = useRef(null);
|
|
689
|
-
useEffect(() => {
|
|
690
|
-
// triggers a re-render now that the component is mounted
|
|
691
|
-
setIsMounted(true);
|
|
692
|
-
}, []);
|
|
693
669
|
|
|
694
|
-
if (renderState === RenderState.
|
|
670
|
+
if (renderState === RenderState.Root) {
|
|
671
|
+
throw new Error("Components using useUniqueIdWithoutMock() should be descendants of <RenderStateRoot>");
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (renderState === RenderState.Initial) {
|
|
695
675
|
return null;
|
|
696
676
|
}
|
|
697
677
|
|
|
@@ -702,4 +682,29 @@ const useUniqueIdWithoutMock = scope => {
|
|
|
702
682
|
return idFactory.current;
|
|
703
683
|
};
|
|
704
684
|
|
|
705
|
-
|
|
685
|
+
const {
|
|
686
|
+
useContext,
|
|
687
|
+
useEffect,
|
|
688
|
+
useState
|
|
689
|
+
} = React;
|
|
690
|
+
const RenderStateRoot = ({
|
|
691
|
+
children
|
|
692
|
+
}) => {
|
|
693
|
+
const [firstRender, setFirstRender] = useState(true);
|
|
694
|
+
const contextValue = useContext(RenderStateContext);
|
|
695
|
+
|
|
696
|
+
if (contextValue !== RenderState.Root) {
|
|
697
|
+
throw new Error("There's already a <RenderStateRoot> above this instance in " + "the render tree. This instance should be removed.");
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
useEffect(() => {
|
|
701
|
+
setFirstRender(false);
|
|
702
|
+
}, []); // This effect will only run once.
|
|
703
|
+
|
|
704
|
+
const value = firstRender ? RenderState.Initial : RenderState.Standard;
|
|
705
|
+
return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
|
|
706
|
+
value: value
|
|
707
|
+
}, children);
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
export { IDProvider, RenderStateRoot, server as Server, Text, UniqueIDProvider, View, WithSSRPlaceholder, addStyle, useUniqueIdWithMock, useUniqueIdWithoutMock };
|
package/dist/index.js
CHANGED
|
@@ -267,7 +267,8 @@ UniqueIDFactory._factoryUniquenessCounter = 0;
|
|
|
267
267
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
|
|
268
268
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
269
269
|
/* harmony import */ var _render_state_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
|
|
270
|
-
// TODO(
|
|
270
|
+
// TODO(FEI-4202): update to use `useContext(RenderStateContext)`
|
|
271
|
+
// TODO(somewhatabstract, FEI-4174): Update eslint-plugin-import when they
|
|
271
272
|
// have fixed:
|
|
272
273
|
// https://github.com/import-js/eslint-plugin-import/issues/2073
|
|
273
274
|
// eslint-disable-next-line import/named
|
|
@@ -610,16 +611,10 @@ SsrIDFactory.Default = new SsrIDFactory();
|
|
|
610
611
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
611
612
|
/* harmony import */ var _util_ssr_id_factory_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8);
|
|
612
613
|
/* harmony import */ var _util_unique_id_factory_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
|
|
613
|
-
/* harmony import */ var
|
|
614
|
-
/* harmony import */ var _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(1);
|
|
614
|
+
/* harmony import */ var _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1);
|
|
615
615
|
|
|
616
616
|
|
|
617
617
|
|
|
618
|
-
// TODO(somewhatabstract, FEI-4174): Update eslint-plugin-import when they
|
|
619
|
-
// have fixed:
|
|
620
|
-
// https://github.com/import-js/eslint-plugin-import/issues/2073
|
|
621
|
-
// eslint-disable-next-line import/named
|
|
622
|
-
|
|
623
618
|
|
|
624
619
|
|
|
625
620
|
/**
|
|
@@ -631,15 +626,14 @@ SsrIDFactory.Default = new SsrIDFactory();
|
|
|
631
626
|
* @returns {IIdentifierFactory}
|
|
632
627
|
*/
|
|
633
628
|
const useUniqueIdWithMock = scope => {
|
|
634
|
-
const renderState = Object(
|
|
635
|
-
const [isMounted, setIsMounted] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(false);
|
|
629
|
+
const renderState = Object(react__WEBPACK_IMPORTED_MODULE_0__["useContext"])(_components_render_state_context_js__WEBPACK_IMPORTED_MODULE_3__[/* RenderStateContext */ "b"]);
|
|
636
630
|
const idFactory = Object(react__WEBPACK_IMPORTED_MODULE_0__["useRef"])(null);
|
|
637
|
-
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
|
|
638
|
-
// triggers a re-render now that the component is mounted
|
|
639
|
-
setIsMounted(true);
|
|
640
|
-
}, []);
|
|
641
631
|
|
|
642
|
-
if (renderState ===
|
|
632
|
+
if (renderState === _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_3__[/* RenderState */ "a"].Root) {
|
|
633
|
+
throw new Error("Components using useUniqueIdWithMock() should be descendants of <RenderStateRoot>");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (renderState === _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_3__[/* RenderState */ "a"].Initial) {
|
|
643
637
|
return _util_ssr_id_factory_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"];
|
|
644
638
|
}
|
|
645
639
|
|
|
@@ -658,15 +652,14 @@ const useUniqueIdWithMock = scope => {
|
|
|
658
652
|
*/
|
|
659
653
|
|
|
660
654
|
const useUniqueIdWithoutMock = scope => {
|
|
661
|
-
const renderState = Object(
|
|
662
|
-
const [isMounted, setIsMounted] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(false);
|
|
655
|
+
const renderState = Object(react__WEBPACK_IMPORTED_MODULE_0__["useContext"])(_components_render_state_context_js__WEBPACK_IMPORTED_MODULE_3__[/* RenderStateContext */ "b"]);
|
|
663
656
|
const idFactory = Object(react__WEBPACK_IMPORTED_MODULE_0__["useRef"])(null);
|
|
664
|
-
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
|
|
665
|
-
// triggers a re-render now that the component is mounted
|
|
666
|
-
setIsMounted(true);
|
|
667
|
-
}, []);
|
|
668
657
|
|
|
669
|
-
if (renderState ===
|
|
658
|
+
if (renderState === _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_3__[/* RenderState */ "a"].Root) {
|
|
659
|
+
throw new Error("Components using useUniqueIdWithoutMock() should be descendants of <RenderStateRoot>");
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (renderState === _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_3__[/* RenderState */ "a"].Initial) {
|
|
670
663
|
return null;
|
|
671
664
|
}
|
|
672
665
|
|
|
@@ -681,36 +674,6 @@ const useUniqueIdWithoutMock = scope => {
|
|
|
681
674
|
/* 10 */
|
|
682
675
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
683
676
|
|
|
684
|
-
"use strict";
|
|
685
|
-
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useRenderState; });
|
|
686
|
-
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
|
|
687
|
-
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
688
|
-
/* harmony import */ var _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
|
|
689
|
-
/**
|
|
690
|
-
* This is a thin wrapper around React.useContext(RenderStateContext).
|
|
691
|
-
*
|
|
692
|
-
* RenderStateContext is used by WithSSRPlaceholder to hide a component from
|
|
693
|
-
* rendered during SSR. It's also used by useUniqueId(With|Without)Mock to
|
|
694
|
-
* to return a UniqueIDFactory instance in a SSR-safe way.
|
|
695
|
-
*
|
|
696
|
-
* This hook isn't part of wonder-blocks-core's public interface since we still
|
|
697
|
-
* need to use WithSSRPlaceholder to update the value in the context.
|
|
698
|
-
*
|
|
699
|
-
* TODO(FEI-4153): Figure out whether we should always render a WithSSRPlaceholder
|
|
700
|
-
* at the root of our render tree just to update the value of the context. Once
|
|
701
|
-
* we figure this out we can make `useRenderState` public and create some proper
|
|
702
|
-
* documentation for it.
|
|
703
|
-
*/
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const useRenderState = () => {
|
|
707
|
-
return Object(react__WEBPACK_IMPORTED_MODULE_0__["useContext"])(_components_render_state_context_js__WEBPACK_IMPORTED_MODULE_1__[/* RenderStateContext */ "b"]);
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
/***/ }),
|
|
711
|
-
/* 11 */
|
|
712
|
-
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
713
|
-
|
|
714
677
|
"use strict";
|
|
715
678
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Text; });
|
|
716
679
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
|
|
@@ -776,7 +739,7 @@ Text.defaultProps = {
|
|
|
776
739
|
};
|
|
777
740
|
|
|
778
741
|
/***/ }),
|
|
779
|
-
/*
|
|
742
|
+
/* 11 */
|
|
780
743
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
781
744
|
|
|
782
745
|
"use strict";
|
|
@@ -866,7 +829,7 @@ View.defaultProps = {
|
|
|
866
829
|
};
|
|
867
830
|
|
|
868
831
|
/***/ }),
|
|
869
|
-
/*
|
|
832
|
+
/* 12 */
|
|
870
833
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
871
834
|
|
|
872
835
|
"use strict";
|
|
@@ -928,7 +891,7 @@ class IDProvider extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
928
891
|
IDProvider.defaultId = "wb-id";
|
|
929
892
|
|
|
930
893
|
/***/ }),
|
|
931
|
-
/*
|
|
894
|
+
/* 13 */
|
|
932
895
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
933
896
|
|
|
934
897
|
"use strict";
|
|
@@ -950,22 +913,62 @@ let serverSide = false;
|
|
|
950
913
|
}
|
|
951
914
|
});
|
|
952
915
|
|
|
916
|
+
/***/ }),
|
|
917
|
+
/* 14 */
|
|
918
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
919
|
+
|
|
920
|
+
"use strict";
|
|
921
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RenderStateRoot; });
|
|
922
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
|
|
923
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
924
|
+
/* harmony import */ var _render_state_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
|
|
925
|
+
// TODO(somewhatabstract, FEI-4174): Update eslint-plugin-import when they
|
|
926
|
+
// have fixed:
|
|
927
|
+
// https://github.com/import-js/eslint-plugin-import/issues/2073
|
|
928
|
+
// eslint-disable-next-line import/named
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
const {
|
|
932
|
+
useContext,
|
|
933
|
+
useEffect,
|
|
934
|
+
useState
|
|
935
|
+
} = react__WEBPACK_IMPORTED_MODULE_0__;
|
|
936
|
+
const RenderStateRoot = ({
|
|
937
|
+
children
|
|
938
|
+
}) => {
|
|
939
|
+
const [firstRender, setFirstRender] = useState(true);
|
|
940
|
+
const contextValue = useContext(_render_state_context_js__WEBPACK_IMPORTED_MODULE_1__[/* RenderStateContext */ "b"]);
|
|
941
|
+
|
|
942
|
+
if (contextValue !== _render_state_context_js__WEBPACK_IMPORTED_MODULE_1__[/* RenderState */ "a"].Root) {
|
|
943
|
+
throw new Error("There's already a <RenderStateRoot> above this instance in " + "the render tree. This instance should be removed.");
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
useEffect(() => {
|
|
947
|
+
setFirstRender(false);
|
|
948
|
+
}, []); // This effect will only run once.
|
|
949
|
+
|
|
950
|
+
const value = firstRender ? _render_state_context_js__WEBPACK_IMPORTED_MODULE_1__[/* RenderState */ "a"].Initial : _render_state_context_js__WEBPACK_IMPORTED_MODULE_1__[/* RenderState */ "a"].Standard;
|
|
951
|
+
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_render_state_context_js__WEBPACK_IMPORTED_MODULE_1__[/* RenderStateContext */ "b"].Provider, {
|
|
952
|
+
value: value
|
|
953
|
+
}, children);
|
|
954
|
+
};
|
|
955
|
+
|
|
953
956
|
/***/ }),
|
|
954
957
|
/* 15 */
|
|
955
958
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
956
959
|
|
|
957
960
|
"use strict";
|
|
958
961
|
__webpack_require__.r(__webpack_exports__);
|
|
959
|
-
/* harmony import */ var _components_text_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
962
|
+
/* harmony import */ var _components_text_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(10);
|
|
960
963
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Text", function() { return _components_text_js__WEBPACK_IMPORTED_MODULE_0__["a"]; });
|
|
961
964
|
|
|
962
|
-
/* harmony import */ var _components_view_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
965
|
+
/* harmony import */ var _components_view_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
|
|
963
966
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "View", function() { return _components_view_js__WEBPACK_IMPORTED_MODULE_1__["a"]; });
|
|
964
967
|
|
|
965
968
|
/* harmony import */ var _components_with_ssr_placeholder_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
|
|
966
969
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "WithSSRPlaceholder", function() { return _components_with_ssr_placeholder_js__WEBPACK_IMPORTED_MODULE_2__["a"]; });
|
|
967
970
|
|
|
968
|
-
/* harmony import */ var _components_id_provider_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
|
|
971
|
+
/* harmony import */ var _components_id_provider_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(12);
|
|
969
972
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "IDProvider", function() { return _components_id_provider_js__WEBPACK_IMPORTED_MODULE_3__["a"]; });
|
|
970
973
|
|
|
971
974
|
/* harmony import */ var _components_unique_id_provider_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6);
|
|
@@ -974,7 +977,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
974
977
|
/* harmony import */ var _util_add_style_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(3);
|
|
975
978
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "addStyle", function() { return _util_add_style_js__WEBPACK_IMPORTED_MODULE_5__["a"]; });
|
|
976
979
|
|
|
977
|
-
/* harmony import */ var _util_server_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(
|
|
980
|
+
/* harmony import */ var _util_server_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(13);
|
|
978
981
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Server", function() { return _util_server_js__WEBPACK_IMPORTED_MODULE_6__["a"]; });
|
|
979
982
|
|
|
980
983
|
/* harmony import */ var _hooks_use_unique_id_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(9);
|
|
@@ -982,6 +985,10 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
982
985
|
|
|
983
986
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useUniqueIdWithoutMock", function() { return _hooks_use_unique_id_js__WEBPACK_IMPORTED_MODULE_7__["b"]; });
|
|
984
987
|
|
|
988
|
+
/* harmony import */ var _components_render_state_root_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(14);
|
|
989
|
+
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "RenderStateRoot", function() { return _components_render_state_root_js__WEBPACK_IMPORTED_MODULE_8__["a"]; });
|
|
990
|
+
|
|
991
|
+
|
|
985
992
|
|
|
986
993
|
|
|
987
994
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,6 +27,5 @@
|
|
|
27
27
|
"wb-dev-build-settings": "^0.2.0"
|
|
28
28
|
},
|
|
29
29
|
"author": "",
|
|
30
|
-
"license": "MIT"
|
|
31
|
-
"gitHead": "9ebea88533e702011165072f090a377e02fa3f0f"
|
|
30
|
+
"license": "MIT"
|
|
32
31
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {render} from "@testing-library/react";
|
|
4
|
+
|
|
5
|
+
import {RenderStateRoot} from "../render-state-root.js";
|
|
6
|
+
// TODO(somewhatabstract, FEI-4174): Update eslint-plugin-import when they
|
|
7
|
+
// have fixed:
|
|
8
|
+
// https://github.com/import-js/eslint-plugin-import/issues/2073
|
|
9
|
+
// eslint-disable-next-line import/named
|
|
10
|
+
import {RenderState, RenderStateContext} from "../render-state-context.js";
|
|
11
|
+
|
|
12
|
+
const {useContext} = React;
|
|
13
|
+
|
|
14
|
+
describe("RenderStateRoot", () => {
|
|
15
|
+
test("the first render should set context's value to RenderState.Initial", () => {
|
|
16
|
+
// Arrange
|
|
17
|
+
const values = [];
|
|
18
|
+
const TestComponent = () => {
|
|
19
|
+
const value = useContext(RenderStateContext);
|
|
20
|
+
|
|
21
|
+
values.push(value);
|
|
22
|
+
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Act
|
|
27
|
+
render(
|
|
28
|
+
<RenderStateRoot>
|
|
29
|
+
<TestComponent />
|
|
30
|
+
</RenderStateRoot>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Assert
|
|
34
|
+
expect(values[0]).toEqual(RenderState.Initial);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("the second render should set context's value to RenderState.Standard", () => {
|
|
38
|
+
// Arrange
|
|
39
|
+
const values = [];
|
|
40
|
+
const TestComponent = () => {
|
|
41
|
+
const value = useContext(RenderStateContext);
|
|
42
|
+
|
|
43
|
+
values.push(value);
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Act
|
|
49
|
+
render(
|
|
50
|
+
<RenderStateRoot>
|
|
51
|
+
<TestComponent />
|
|
52
|
+
</RenderStateRoot>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Assert
|
|
56
|
+
expect(values[1]).toEqual(RenderState.Standard);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should not allow nesting of <RenderStateRoot>", () => {
|
|
60
|
+
// Act
|
|
61
|
+
const underTest = () =>
|
|
62
|
+
render(
|
|
63
|
+
<RenderStateRoot>
|
|
64
|
+
<RenderStateRoot>Hello, world!</RenderStateRoot>
|
|
65
|
+
</RenderStateRoot>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
69
|
+
`"There's already a <RenderStateRoot> above this instance in the render tree. This instance should be removed."`,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import * as ReactDOMServer from "react-dom/server.js";
|
|
4
4
|
import {mount} from "enzyme";
|
|
5
|
+
import "jest-enzyme";
|
|
5
6
|
|
|
6
7
|
import View from "../view.js";
|
|
7
8
|
|
|
@@ -9,6 +10,7 @@ import SsrIDFactory from "../../util/ssr-id-factory.js";
|
|
|
9
10
|
import UniqueIDFactory from "../../util/unique-id-factory.js";
|
|
10
11
|
import UniqueIDProvider from "../unique-id-provider.js";
|
|
11
12
|
import WithSSRPlaceholder from "../with-ssr-placeholder.js";
|
|
13
|
+
import {RenderStateRoot} from "../render-state-root.js";
|
|
12
14
|
|
|
13
15
|
describe("UniqueIDProvider", () => {
|
|
14
16
|
describe("mockOnFirstRender is default (false)", () => {
|
|
@@ -155,4 +157,25 @@ describe("UniqueIDProvider", () => {
|
|
|
155
157
|
expect(foo.mock.calls[0][0]).toBeTruthy();
|
|
156
158
|
});
|
|
157
159
|
});
|
|
160
|
+
|
|
161
|
+
describe("inside a RenderStateRoot", () => {
|
|
162
|
+
test("it should pass an id to its children", () => {
|
|
163
|
+
// Arrange
|
|
164
|
+
const foo = jest.fn(() => null);
|
|
165
|
+
const nodes = (
|
|
166
|
+
<RenderStateRoot>
|
|
167
|
+
<UniqueIDProvider mockOnFirstRender={false}>
|
|
168
|
+
{(ids) => foo(ids.get(""))}
|
|
169
|
+
</UniqueIDProvider>
|
|
170
|
+
</RenderStateRoot>
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Act
|
|
174
|
+
mount(nodes);
|
|
175
|
+
|
|
176
|
+
// Assert
|
|
177
|
+
expect(foo).toHaveBeenCalled();
|
|
178
|
+
expect(foo.mock.calls[0][0]).toBeTruthy();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
158
181
|
});
|