@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 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 { Component, createElement, createContext, useContext, useState, useRef, useEffect } from 'react';
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$2 = ["testId", "tag"];
178
- const styles$1 = StyleSheet.create({
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$1.default);
197
- const StyledArticle = addStyle("article", styles$1.default);
198
- const StyledAside = addStyle("aside", styles$1.default);
199
- const StyledNav = addStyle("nav", styles$1.default);
200
- const StyledSection = addStyle("section", styles$1.default);
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$2);
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 = useRenderState();
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.Initial || renderState === RenderState.Root && !isMounted) {
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 = useRenderState();
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.Initial || renderState === RenderState.Root && !isMounted) {
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
- export { IDProvider, server as Server, Text, UniqueIDProvider, View, WithSSRPlaceholder, addStyle, useUniqueIdWithMock, useUniqueIdWithoutMock };
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(somewhatabstract, FEI-4174): Update eslint-plugin-import when they
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 _use_render_state_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(10);
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(_use_render_state_js__WEBPACK_IMPORTED_MODULE_3__[/* useRenderState */ "a"])();
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 === _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_4__[/* RenderState */ "a"].Initial || renderState === _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_4__[/* RenderState */ "a"].Root && !isMounted) {
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(_use_render_state_js__WEBPACK_IMPORTED_MODULE_3__[/* useRenderState */ "a"])();
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 === _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_4__[/* RenderState */ "a"].Initial || renderState === _components_render_state_context_js__WEBPACK_IMPORTED_MODULE_4__[/* RenderState */ "a"].Root && !isMounted) {
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
- /* 12 */
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
- /* 13 */
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
- /* 14 */
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__(11);
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__(12);
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__(13);
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__(14);
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.0.0",
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
  }
@@ -1,6 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import {mount, shallow} from "enzyme";
4
+ import "jest-enzyme";
4
5
 
5
6
  import IDProvider from "../id-provider.js";
6
7
 
@@ -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
  });