@croct/plug-react 0.8.1 → 0.10.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.
@@ -6,6 +6,7 @@ export type UseContentOptions<I, F> = FetchOptions & {
6
6
  initial?: I;
7
7
  cacheKey?: string;
8
8
  expiration?: number;
9
+ staleWhileLoading?: boolean;
9
10
  };
10
11
  type UseContentHook = {
11
12
  <P extends JsonObject, I = P, F = P>(id: keyof VersionedSlotMap extends never ? string : never, options?: UseContentOptions<I, F>): P | I | F;
@@ -1,25 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useContent = void 0;
4
+ const react_1 = require("react");
4
5
  const useLoader_1 = require("./useLoader");
5
6
  const useCroct_1 = require("./useCroct");
6
7
  const ssr_polyfills_1 = require("../ssr-polyfills");
7
8
  const hash_1 = require("../hash");
8
9
  function useCsrContent(id, options = {}) {
9
10
  var _a;
10
- const { fallback, initial, cacheKey, expiration, ...fetchOptions } = options;
11
+ const { fallback, cacheKey, expiration, initial: initialContent, staleWhileLoading = false, ...fetchOptions } = options;
12
+ const [initial, setInitial] = (0, react_1.useState)(initialContent);
11
13
  const { preferredLocale } = fetchOptions;
12
14
  const croct = (0, useCroct_1.useCroct)();
13
- return (0, useLoader_1.useLoader)({
15
+ const result = (0, useLoader_1.useLoader)({
14
16
  cacheKey: (0, hash_1.hash)(`useContent:${cacheKey !== null && cacheKey !== void 0 ? cacheKey : ''}`
15
17
  + `:${id}`
16
18
  + `:${preferredLocale !== null && preferredLocale !== void 0 ? preferredLocale : ''}`
17
- + `:${JSON.stringify((_a = fetchOptions.attributes) !== null && _a !== void 0 ? _a : '')}`),
19
+ + `:${JSON.stringify((_a = fetchOptions.attributes) !== null && _a !== void 0 ? _a : {})}`),
18
20
  loader: () => croct.fetch(id, fetchOptions).then(({ content }) => content),
19
21
  initial: initial,
20
22
  fallback: fallback,
21
23
  expiration: expiration,
22
24
  });
25
+ (0, react_1.useEffect)(() => {
26
+ if (staleWhileLoading) {
27
+ setInitial(current => {
28
+ if (current !== result) {
29
+ return result;
30
+ }
31
+ return current;
32
+ });
33
+ }
34
+ }, [result, staleWhileLoading]);
35
+ return result;
23
36
  }
24
37
  function useSsrContent(_, { initial } = {}) {
25
38
  if (initial === undefined) {
@@ -1 +1 @@
1
- {"version":3,"file":"useContent.js","sourceRoot":"","sources":["../src/hooks/useContent.ts"],"names":[],"mappings":";;;AAGA,2CAAsC;AACtC,yCAAoC;AACpC,oDAAuC;AACvC,kCAA6B;AAS7B,SAAS,aAAa,CAClB,EAAmB,EACnB,UAAmC,EAAE;;IAErC,MAAM,EAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,YAAY,EAAC,GAAG,OAAO,CAAC;IAC3E,MAAM,EAAC,eAAe,EAAC,GAAG,YAAY,CAAC;IACvC,MAAM,KAAK,GAAG,IAAA,mBAAQ,GAAE,CAAC;IAEzB,OAAO,IAAA,qBAAS,EAAC;QACb,QAAQ,EAAE,IAAA,WAAI,EACV,cAAc,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,EAAE,EAAE;cAC5B,IAAI,EAAE,EAAE;cACR,IAAI,eAAe,aAAf,eAAe,cAAf,eAAe,GAAI,EAAE,EAAE;cAC3B,IAAI,IAAI,CAAC,SAAS,CAAC,MAAA,YAAY,CAAC,UAAU,mCAAI,EAAE,CAAC,EAAE,CACxD;QACD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,EAAC,OAAO,EAAC,EAAE,EAAE,CAAC,OAAO,CAAC;QACxE,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,QAAQ;QAClB,UAAU,EAAE,UAAU;KACzB,CAAC,CAAC;AACP,CAAC;AAED,SAAS,aAAa,CAClB,CAAkB,EAClB,EAAC,OAAO,KAA6B,EAAE;IAEvC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACX,mEAAmE;cACjE,iEAAiE,CACtE,CAAC;IACN,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AA6BY,QAAA,UAAU,GAAmB,IAAA,qBAAK,GAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC"}
1
+ {"version":3,"file":"useContent.js","sourceRoot":"","sources":["../src/hooks/useContent.ts"],"names":[],"mappings":";;;AAGA,iCAA0C;AAC1C,2CAAsC;AACtC,yCAAoC;AACpC,oDAAuC;AACvC,kCAA6B;AAU7B,SAAS,aAAa,CAClB,EAAmB,EACnB,UAAmC,EAAE;;IAErC,MAAM,EACF,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,OAAO,EAAE,cAAc,EACvB,iBAAiB,GAAG,KAAK,EACzB,GAAG,YAAY,EAClB,GAAG,OAAO,CAAC;IAEZ,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,IAAA,gBAAQ,EAAkC,cAAc,CAAC,CAAC;IACxF,MAAM,EAAC,eAAe,EAAC,GAAG,YAAY,CAAC;IACvC,MAAM,KAAK,GAAG,IAAA,mBAAQ,GAAE,CAAC;IAEzB,MAAM,MAAM,GAAwB,IAAA,qBAAS,EAAC;QAC1C,QAAQ,EAAE,IAAA,WAAI,EACV,cAAc,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,EAAE,EAAE;cAC5B,IAAI,EAAE,EAAE;cACR,IAAI,eAAe,aAAf,eAAe,cAAf,eAAe,GAAI,EAAE,EAAE;cAC3B,IAAI,IAAI,CAAC,SAAS,CAAC,MAAA,YAAY,CAAC,UAAU,mCAAI,EAAE,CAAC,EAAE,CACxD;QACD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,EAAC,OAAO,EAAC,EAAE,EAAE,CAAC,OAAO,CAAC;QACxE,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,QAAQ;QAClB,UAAU,EAAE,UAAU;KACzB,CAAC,CAAC;IAEH,IAAA,iBAAS,EACL,GAAG,EAAE;QACD,IAAI,iBAAiB,EAAE,CAAC;YACpB,UAAU,CAAC,OAAO,CAAC,EAAE;gBACjB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;oBACrB,OAAO,MAAM,CAAC;gBAClB,CAAC;gBAED,OAAO,OAAO,CAAC;YACnB,CAAC,CAAC,CAAC;QACP,CAAC;IACL,CAAC,EACD,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAC9B,CAAC;IAEF,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CAClB,CAAkB,EAClB,EAAC,OAAO,KAA6B,EAAE;IAEvC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACX,mEAAmE;cACjE,iEAAiE,CACtE,CAAC;IACN,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AA6BY,QAAA,UAAU,GAAmB,IAAA,qBAAK,GAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC"}
@@ -5,6 +5,7 @@ export type UseEvaluationOptions<I, F> = EvaluationOptions & {
5
5
  fallback?: F;
6
6
  cacheKey?: string;
7
7
  expiration?: number;
8
+ staleWhileLoading?: boolean;
8
9
  };
9
10
  type UseEvaluationHook = <T extends JsonValue, I = T, F = T>(query: string, options?: UseEvaluationOptions<I, F>) => T | I | F;
10
11
  export declare const useEvaluation: UseEvaluationHook;
@@ -1,32 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useEvaluation = void 0;
4
+ const react_1 = require("react");
4
5
  const useLoader_1 = require("./useLoader");
5
6
  const useCroct_1 = require("./useCroct");
6
7
  const ssr_polyfills_1 = require("../ssr-polyfills");
7
8
  const hash_1 = require("../hash");
8
- function cleanEvaluationOptions(options) {
9
- const result = {};
10
- for (const [key, value] of Object.entries(options)) {
11
- if (value !== undefined) {
12
- result[key] = value;
13
- }
14
- }
15
- return result;
16
- }
17
9
  function useCsrEvaluation(query, options = {}) {
18
10
  var _a;
19
- const { cacheKey, fallback, initial, expiration, ...evaluationOptions } = options;
11
+ const { cacheKey, fallback, expiration, staleWhileLoading = false, initial: initialValue, ...evaluationOptions } = options;
12
+ const [initial, setInitial] = (0, react_1.useState)(initialValue);
20
13
  const croct = (0, useCroct_1.useCroct)();
21
- return (0, useLoader_1.useLoader)({
14
+ const result = (0, useLoader_1.useLoader)({
22
15
  cacheKey: (0, hash_1.hash)(`useEvaluation:${cacheKey !== null && cacheKey !== void 0 ? cacheKey : ''}`
23
16
  + `:${query}`
24
- + `:${JSON.stringify((_a = options.attributes) !== null && _a !== void 0 ? _a : '')}`),
17
+ + `:${JSON.stringify((_a = options.attributes) !== null && _a !== void 0 ? _a : {})}`),
25
18
  loader: () => croct.evaluate(query, cleanEvaluationOptions(evaluationOptions)),
26
19
  initial: initial,
27
20
  fallback: fallback,
28
21
  expiration: expiration,
29
22
  });
23
+ (0, react_1.useEffect)(() => {
24
+ if (staleWhileLoading) {
25
+ setInitial(current => {
26
+ if (current !== result) {
27
+ return result;
28
+ }
29
+ return current;
30
+ });
31
+ }
32
+ }, [result, staleWhileLoading]);
33
+ return result;
34
+ }
35
+ function cleanEvaluationOptions(options) {
36
+ const result = {};
37
+ for (const [key, value] of Object.entries(options)) {
38
+ if (value !== undefined) {
39
+ result[key] = value;
40
+ }
41
+ }
42
+ return result;
30
43
  }
31
44
  function useSsrEvaluation(_, { initial } = {}) {
32
45
  if (initial === undefined) {
@@ -1 +1 @@
1
- {"version":3,"file":"useEvaluation.js","sourceRoot":"","sources":["../src/hooks/useEvaluation.ts"],"names":[],"mappings":";;;AAEA,2CAAsC;AACtC,yCAAoC;AACpC,oDAAuC;AACvC,kCAA6B;AAE7B,SAAS,sBAAsB,CAAC,OAA0B;IACtD,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAA0C,EAAE,CAAC;QAC1F,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACxB,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAcD,SAAS,gBAAgB,CACrB,KAAa,EACb,UAAsC,EAAE;;IAExC,MAAM,EAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,iBAAiB,EAAC,GAAG,OAAO,CAAC;IAChF,MAAM,KAAK,GAAG,IAAA,mBAAQ,GAAE,CAAC;IAEzB,OAAO,IAAA,qBAAS,EAAY;QACxB,QAAQ,EAAE,IAAA,WAAI,EACV,iBAAiB,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,EAAE,EAAE;cAC/B,IAAI,KAAK,EAAE;cACX,IAAI,IAAI,CAAC,SAAS,CAAC,MAAA,OAAO,CAAC,UAAU,mCAAI,EAAE,CAAC,EAAE,CACnD;QACD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAgB,KAAK,EAAE,sBAAsB,CAAC,iBAAiB,CAAC,CAAC;QAC7F,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,QAAQ;QAClB,UAAU,EAAE,UAAU;KACzB,CAAC,CAAC;AACP,CAAC;AAED,SAAS,gBAAgB,CACrB,CAAS,EACT,EAAC,OAAO,KAAgC,EAAE;IAE1C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACX,iEAAiE;cAC/D,sEAAsE,CAC3E,CAAC;IACN,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAEY,QAAA,aAAa,GAAsB,IAAA,qBAAK,GAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,gBAAgB,CAAC"}
1
+ {"version":3,"file":"useEvaluation.js","sourceRoot":"","sources":["../src/hooks/useEvaluation.ts"],"names":[],"mappings":";;;AAEA,iCAA0C;AAC1C,2CAAsC;AACtC,yCAAoC;AACpC,oDAAuC;AACvC,kCAA6B;AAe7B,SAAS,gBAAgB,CACrB,KAAa,EACb,UAAsC,EAAE;;IAExC,MAAM,EACF,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,iBAAiB,GAAG,KAAK,EACzB,OAAO,EAAE,YAAY,EACrB,GAAG,iBAAiB,EACvB,GAAG,OAAO,CAAC;IAEZ,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,IAAA,gBAAQ,EAAwB,YAAY,CAAC,CAAC;IAC5E,MAAM,KAAK,GAAG,IAAA,mBAAQ,GAAE,CAAC;IAEzB,MAAM,MAAM,GAAG,IAAA,qBAAS,EAAY;QAChC,QAAQ,EAAE,IAAA,WAAI,EACV,iBAAiB,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,EAAE,EAAE;cAC/B,IAAI,KAAK,EAAE;cACX,IAAI,IAAI,CAAC,SAAS,CAAC,MAAA,OAAO,CAAC,UAAU,mCAAI,EAAE,CAAC,EAAE,CACnD;QACD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAgB,KAAK,EAAE,sBAAsB,CAAC,iBAAiB,CAAC,CAAC;QAC7F,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,QAAQ;QAClB,UAAU,EAAE,UAAU;KACzB,CAAC,CAAC;IAEH,IAAA,iBAAS,EACL,GAAG,EAAE;QACD,IAAI,iBAAiB,EAAE,CAAC;YACpB,UAAU,CAAC,OAAO,CAAC,EAAE;gBACjB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;oBACrB,OAAO,MAAM,CAAC;gBAClB,CAAC;gBAED,OAAO,OAAO,CAAC;YACnB,CAAC,CAAC,CAAC;QACP,CAAC;IACL,CAAC,EACD,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAC9B,CAAC;IAEF,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,SAAS,sBAAsB,CAAC,OAA0B;IACtD,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAA0C,EAAE,CAAC;QAC1F,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACxB,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,SAAS,gBAAgB,CACrB,CAAS,EACT,EAAC,OAAO,KAAgC,EAAE;IAE1C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACX,iEAAiE;cAC/D,sEAAsE,CAC3E,CAAC;IACN,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAEY,QAAA,aAAa,GAAsB,IAAA,qBAAK,GAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,gBAAgB,CAAC"}
@@ -6,35 +6,58 @@ const Cache_1 = require("./Cache");
6
6
  const cache = new Cache_1.Cache(60 * 1000);
7
7
  function useLoader({ initial, ...options }) {
8
8
  var _a;
9
- const loadedValue = (_a = cache.get(options.cacheKey)) === null || _a === void 0 ? void 0 : _a.result;
9
+ const { cacheKey } = options;
10
+ const loadedValue = (_a = cache.get(cacheKey)) === null || _a === void 0 ? void 0 : _a.result;
10
11
  const [value, setValue] = (0, react_1.useState)(loadedValue !== undefined ? loadedValue : initial);
11
12
  const mountedRef = (0, react_1.useRef)(true);
12
- const optionsRef = (0, react_1.useRef)(initial !== undefined ? options : undefined);
13
- (0, react_1.useEffect)(() => {
14
- if (optionsRef.current !== undefined) {
15
- try {
16
- setValue(cache.load(optionsRef.current));
17
- }
18
- catch (result) {
19
- if (result instanceof Promise) {
20
- result.then((resolvedValue) => {
21
- if (mountedRef.current) {
22
- setValue(resolvedValue);
23
- }
24
- });
25
- return;
26
- }
27
- setValue(undefined);
13
+ const initialRef = (0, react_1.useRef)(initial);
14
+ const previousCacheKey = (0, react_1.useRef)(cacheKey);
15
+ const load = useStableCallback(() => {
16
+ try {
17
+ setValue(cache.load(options));
18
+ }
19
+ catch (result) {
20
+ if (result instanceof Promise) {
21
+ result.then((resolvedValue) => {
22
+ if (mountedRef.current) {
23
+ setValue(resolvedValue);
24
+ }
25
+ });
28
26
  return;
29
27
  }
28
+ setValue(undefined);
29
+ }
30
+ });
31
+ const reset = useStableCallback(() => {
32
+ var _a;
33
+ const newLoadedValue = (_a = cache.get(cacheKey)) === null || _a === void 0 ? void 0 : _a.result;
34
+ setValue(newLoadedValue !== undefined ? newLoadedValue : initial);
35
+ load();
36
+ });
37
+ (0, react_1.useEffect)(() => {
38
+ if (previousCacheKey.current !== cacheKey) {
39
+ reset();
40
+ previousCacheKey.current = cacheKey;
41
+ }
42
+ }, [reset, cacheKey]);
43
+ (0, react_1.useEffect)(() => {
44
+ if (initialRef.current !== undefined) {
45
+ load();
30
46
  }
31
47
  return () => {
32
48
  mountedRef.current = false;
33
49
  };
34
- }, []);
50
+ }, [load]);
35
51
  if (value === undefined) {
36
52
  return cache.load(options);
37
53
  }
38
54
  return value;
39
55
  }
56
+ function useStableCallback(callback) {
57
+ const ref = (0, react_1.useRef)(undefined);
58
+ (0, react_1.useEffect)(() => {
59
+ ref.current = callback;
60
+ });
61
+ return (0, react_1.useCallback)(() => { var _a; (_a = ref.current) === null || _a === void 0 ? void 0 : _a.call(ref); }, []);
62
+ }
40
63
  //# sourceMappingURL=useLoader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useLoader.js","sourceRoot":"","sources":["../src/hooks/useLoader.ts"],"names":[],"mappings":";;AASA,8BAwCC;AAjDD,iCAAkD;AAClD,mCAA4C;AAE5C,MAAM,KAAK,GAAG,IAAI,aAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;AAMnC,SAAgB,SAAS,CAAI,EAAC,OAAO,EAAE,GAAG,OAAO,EAAkB;;IAC/D,MAAM,WAAW,GAAgB,MAAA,KAAK,CAAC,GAAG,CAAI,OAAO,CAAC,QAAQ,CAAC,0CAAE,MAAM,CAAC;IACxE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACtF,MAAM,UAAU,GAAG,IAAA,cAAM,EAAC,IAAI,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,IAAA,cAAM,EAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAEvE,IAAA,iBAAS,EACL,GAAG,EAAE;QACD,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACnC,IAAI,CAAC;gBACD,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,MAAe,EAAE,CAAC;gBACvB,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;oBAC5B,MAAM,CAAC,IAAI,CAAC,CAAC,aAAgB,EAAE,EAAE;wBAC7B,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;4BACrB,QAAQ,CAAC,aAAa,CAAC,CAAC;wBAC5B,CAAC;oBACL,CAAC,CAAC,CAAC;oBAEH,OAAO;gBACX,CAAC;gBAED,QAAQ,CAAC,SAAS,CAAC,CAAC;gBAEpB,OAAO;YACX,CAAC;QACL,CAAC;QAED,OAAO,GAAG,EAAE;YACR,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC;QAC/B,CAAC,CAAC;IACN,CAAC,EACD,EAAE,CACL,CAAC;IAEF,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"useLoader.js","sourceRoot":"","sources":["../src/hooks/useLoader.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@croct/plug-react",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "React components and hooks to plug your React applications into Croct.",
5
5
  "author": {
6
6
  "name": "Croct",
@@ -28,17 +28,15 @@
28
28
  "lint": "eslint 'src/**/*.ts' 'src/**/*.tsx'",
29
29
  "test": "jest -c jest.config.js --coverage",
30
30
  "validate": "tsc --noEmit",
31
- "build": "tsc -p tsconfig.build.json",
32
- "storybook": "start-storybook -s ./.storybook/static -p 6006 --no-manager-cache",
33
- "build-storybook": "build-storybook -s ./.storybook/static"
31
+ "build": "tsc -p tsconfig.build.json"
34
32
  },
35
33
  "peerDependencies": {
36
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
37
- "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
34
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
35
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
38
36
  },
39
37
  "dependencies": {
40
38
  "@croct/plug": "^0.16.2",
41
- "@croct/sdk": "^0.17.2"
39
+ "@croct/sdk": "^0.17.6"
42
40
  },
43
41
  "devDependencies": {
44
42
  "@babel/core": "^7.25.2",
@@ -46,18 +44,12 @@
46
44
  "@babel/preset-react": "^7.24.7",
47
45
  "@babel/preset-typescript": "^7.24.7",
48
46
  "@croct/eslint-plugin": "^0.7.1",
49
- "@storybook/addon-actions": "^8.2.9",
50
- "@storybook/addon-essentials": "^8.2.9",
51
- "@storybook/addon-links": "^8.2.9",
52
- "@storybook/builder-webpack5": "^8.2.9",
53
- "@storybook/manager-webpack5": "^6.5.16",
54
- "@storybook/react": "^8.2.9",
55
- "@testing-library/jest-dom": "^6.5.0",
56
- "@testing-library/react": "^16.0.1",
47
+ "@testing-library/jest-dom": "^6.6.3",
48
+ "@testing-library/react": "^16.1.0",
57
49
  "@types/jest": "^29.5.12",
58
50
  "@types/node": "^22.5.4",
59
- "@types/react": "^18.3.5",
60
- "@types/react-dom": "^18.3.0",
51
+ "@types/react": "^19.0.0",
52
+ "@types/react-dom": "^19.0.0",
61
53
  "@typescript-eslint/eslint-plugin": "^7.18.0",
62
54
  "@typescript-eslint/parser": "^7.18.0",
63
55
  "babel-loader": "^9.1.3",
@@ -65,8 +57,8 @@
65
57
  "jest": "^29.7.0",
66
58
  "jest-environment-jsdom": "^29.7.0",
67
59
  "jest-environment-node": "^29.7.0",
68
- "react": "^18.3.1",
69
- "react-dom": "^18.3.1",
60
+ "react": "^19.0.0",
61
+ "react-dom": "^19.0.0",
70
62
  "ts-node": "^10.9.2",
71
63
  "typescript": "^5.6.2",
72
64
  "webpack": "^5.94.0"
@@ -1,4 +1,4 @@
1
- import {renderHook} from '@testing-library/react';
1
+ import {renderHook, waitFor} from '@testing-library/react';
2
2
  import {Plug} from '@croct/plug';
3
3
  import {useCroct} from './useCroct';
4
4
  import {useLoader} from './useLoader';
@@ -20,15 +20,19 @@ jest.mock(
20
20
  );
21
21
 
22
22
  describe('useContent (CSR)', () => {
23
- it('should evaluate fetch the content', () => {
23
+ beforeEach(() => {
24
+ jest.resetAllMocks();
25
+ });
26
+
27
+ it('should fetch the content', () => {
24
28
  const fetch: Plug['fetch'] = jest.fn().mockResolvedValue({
25
- payload: {
26
- title: 'loaded',
27
- },
29
+ content: {},
28
30
  });
29
31
 
30
32
  jest.mocked(useCroct).mockReturnValue({fetch: fetch} as Plug);
31
- jest.mocked(useLoader).mockReturnValue('foo');
33
+ jest.mocked(useLoader).mockReturnValue({
34
+ title: 'foo',
35
+ });
32
36
 
33
37
  const slotId = 'home-banner@1';
34
38
  const preferredLocale = 'en';
@@ -67,6 +71,113 @@ describe('useContent (CSR)', () => {
67
71
  attributes: attributes,
68
72
  });
69
73
 
70
- expect(result.current).toBe('foo');
74
+ expect(result.current).toEqual({title: 'foo'});
75
+ });
76
+
77
+ it('should use the initial value when the cache key changes if the stale-while-loading flag is false', async () => {
78
+ const key = {
79
+ current: 'initial',
80
+ };
81
+
82
+ const fetch: Plug['fetch'] = jest.fn().mockResolvedValue({content: {}});
83
+
84
+ jest.mocked(useCroct).mockReturnValue({fetch: fetch} as Plug);
85
+
86
+ jest.mocked(useLoader).mockImplementation(
87
+ () => ({title: key.current === 'initial' ? 'first' : 'second'}),
88
+ );
89
+
90
+ const slotId = 'home-banner@1';
91
+
92
+ const {result, rerender} = renderHook(
93
+ () => useContent<{title: string}>(slotId, {
94
+ cacheKey: key.current,
95
+ initial: {
96
+ title: 'initial',
97
+ },
98
+ }),
99
+ );
100
+
101
+ expect(useCroct).toHaveBeenCalled();
102
+
103
+ expect(useLoader).toHaveBeenCalledWith(expect.objectContaining({
104
+ cacheKey: hash(`useContent:${key.current}:${slotId}::${JSON.stringify({})}`),
105
+ initial: {
106
+ title: 'initial',
107
+ },
108
+ }));
109
+
110
+ await waitFor(() => expect(result.current).toEqual({title: 'first'}));
111
+
112
+ key.current = 'next';
113
+
114
+ rerender();
115
+
116
+ expect(useLoader).toHaveBeenCalledWith(expect.objectContaining({
117
+ cacheKey: hash(`useContent:${key.current}:${slotId}::${JSON.stringify({})}`),
118
+ initial: {
119
+ title: 'initial',
120
+ },
121
+ }));
122
+
123
+ await waitFor(() => expect(result.current).toEqual({title: 'second'}));
124
+ });
125
+
126
+ it('should use the last fetched content as initial value if the stale-while-loading flag is true', async () => {
127
+ const key = {
128
+ current: 'initial',
129
+ };
130
+
131
+ const fetch: Plug['fetch'] = jest.fn().mockResolvedValue({content: {}});
132
+
133
+ jest.mocked(useCroct).mockReturnValue({fetch: fetch} as Plug);
134
+
135
+ const firstResult = {
136
+ title: 'first',
137
+ };
138
+
139
+ const secondResult = {
140
+ title: 'second',
141
+ };
142
+
143
+ jest.mocked(useLoader).mockImplementation(
144
+ () => (key.current === 'initial' ? firstResult : secondResult),
145
+ );
146
+
147
+ const slotId = 'home-banner@1';
148
+
149
+ const {result, rerender} = renderHook(
150
+ () => useContent<{title: string}>(slotId, {
151
+ cacheKey: key.current,
152
+ initial: {
153
+ title: 'initial',
154
+ },
155
+ staleWhileLoading: true,
156
+ }),
157
+ );
158
+
159
+ expect(useCroct).toHaveBeenCalled();
160
+
161
+ expect(useLoader).toHaveBeenCalledWith(expect.objectContaining({
162
+ cacheKey: hash(`useContent:${key.current}:${slotId}::${JSON.stringify({})}`),
163
+ initial: {
164
+ title: 'initial',
165
+ },
166
+ }));
167
+
168
+ await waitFor(() => expect(result.current).toEqual({title: 'first'}));
169
+
170
+ key.current = 'next';
171
+
172
+ rerender();
173
+
174
+ expect(useLoader).toHaveBeenCalledWith(expect.objectContaining({
175
+ cacheKey: hash(`useContent:${key.current}:${slotId}::${JSON.stringify({})}`),
176
+ initial: {
177
+ title: 'first',
178
+ },
179
+ }));
180
+
181
+ await waitFor(() => expect(result.current).toEqual({title: 'second'}));
71
182
  });
72
183
  });
@@ -1,6 +1,7 @@
1
1
  import {SlotContent, VersionedSlotId, VersionedSlotMap} from '@croct/plug/slot';
2
2
  import {JsonObject} from '@croct/plug/sdk/json';
3
3
  import {FetchOptions} from '@croct/plug/plug';
4
+ import {useEffect, useState} from 'react';
4
5
  import {useLoader} from './useLoader';
5
6
  import {useCroct} from './useCroct';
6
7
  import {isSsr} from '../ssr-polyfills';
@@ -11,28 +12,55 @@ export type UseContentOptions<I, F> = FetchOptions & {
11
12
  initial?: I,
12
13
  cacheKey?: string,
13
14
  expiration?: number,
15
+ staleWhileLoading?: boolean,
14
16
  };
15
17
 
16
18
  function useCsrContent<I, F>(
17
19
  id: VersionedSlotId,
18
20
  options: UseContentOptions<I, F> = {},
19
21
  ): SlotContent | I | F {
20
- const {fallback, initial, cacheKey, expiration, ...fetchOptions} = options;
22
+ const {
23
+ fallback,
24
+ cacheKey,
25
+ expiration,
26
+ initial: initialContent,
27
+ staleWhileLoading = false,
28
+ ...fetchOptions
29
+ } = options;
30
+
31
+ const [initial, setInitial] = useState<SlotContent | I | F | undefined>(initialContent);
21
32
  const {preferredLocale} = fetchOptions;
22
33
  const croct = useCroct();
23
34
 
24
- return useLoader({
35
+ const result: SlotContent | I | F = useLoader({
25
36
  cacheKey: hash(
26
37
  `useContent:${cacheKey ?? ''}`
27
38
  + `:${id}`
28
39
  + `:${preferredLocale ?? ''}`
29
- + `:${JSON.stringify(fetchOptions.attributes ?? '')}`,
40
+ + `:${JSON.stringify(fetchOptions.attributes ?? {})}`,
30
41
  ),
31
42
  loader: () => croct.fetch(id, fetchOptions).then(({content}) => content),
32
43
  initial: initial,
33
44
  fallback: fallback,
34
45
  expiration: expiration,
35
46
  });
47
+
48
+ useEffect(
49
+ () => {
50
+ if (staleWhileLoading) {
51
+ setInitial(current => {
52
+ if (current !== result) {
53
+ return result;
54
+ }
55
+
56
+ return current;
57
+ });
58
+ }
59
+ },
60
+ [result, staleWhileLoading],
61
+ );
62
+
63
+ return result;
36
64
  }
37
65
 
38
66
  function useSsrContent<I, F>(
@@ -1,4 +1,4 @@
1
- import {renderHook} from '@testing-library/react';
1
+ import {renderHook, waitFor} from '@testing-library/react';
2
2
  import {EvaluationOptions} from '@croct/sdk/facade/evaluatorFacade';
3
3
  import {Plug} from '@croct/plug';
4
4
  import {useEvaluation} from './useEvaluation';
@@ -22,7 +22,6 @@ jest.mock(
22
22
 
23
23
  describe('useEvaluation', () => {
24
24
  beforeEach(() => {
25
- jest.resetModules();
26
25
  jest.resetAllMocks();
27
26
  });
28
27
 
@@ -91,4 +90,91 @@ describe('useEvaluation', () => {
91
90
 
92
91
  expect(evaluate).toHaveBeenCalledWith(query, {});
93
92
  });
93
+
94
+ it('should use the initial value when the cache key changes if the stale-while-loading flag is false', async () => {
95
+ const key = {
96
+ current: 'initial',
97
+ };
98
+
99
+ const evaluate: Plug['evaluate'] = jest.fn();
100
+
101
+ jest.mocked(useCroct).mockReturnValue({evaluate: evaluate} as Plug);
102
+
103
+ jest.mocked(useLoader).mockImplementation(
104
+ () => (key.current === 'initial' ? 'first' : 'second'),
105
+ );
106
+
107
+ const query = 'location';
108
+
109
+ const {result, rerender} = renderHook(
110
+ () => useEvaluation(query, {
111
+ cacheKey: key.current,
112
+ initial: 'initial',
113
+ }),
114
+ );
115
+
116
+ expect(useCroct).toHaveBeenCalled();
117
+
118
+ expect(useLoader).toHaveBeenCalledWith(expect.objectContaining({
119
+ cacheKey: hash(`useEvaluation:${key.current}:${query}:${JSON.stringify({})}`),
120
+ initial: 'initial',
121
+ }));
122
+
123
+ await waitFor(() => expect(result.current).toEqual('first'));
124
+
125
+ key.current = 'next';
126
+
127
+ rerender();
128
+
129
+ expect(useLoader).toHaveBeenCalledWith(expect.objectContaining({
130
+ cacheKey: hash(`useEvaluation:${key.current}:${query}:${JSON.stringify({})}`),
131
+ initial: 'initial',
132
+ }));
133
+
134
+ await waitFor(() => expect(result.current).toEqual('second'));
135
+ });
136
+
137
+ it('should use the last evaluation result if the stale-while-loading flag is true', async () => {
138
+ const key = {
139
+ current: 'initial',
140
+ };
141
+
142
+ const evaluate: Plug['evaluate'] = jest.fn();
143
+
144
+ jest.mocked(useCroct).mockReturnValue({evaluate: evaluate} as Plug);
145
+
146
+ jest.mocked(useLoader).mockImplementation(
147
+ () => (key.current === 'initial' ? 'first' : 'second'),
148
+ );
149
+
150
+ const query = 'location';
151
+
152
+ const {result, rerender} = renderHook(
153
+ () => useEvaluation(query, {
154
+ cacheKey: key.current,
155
+ initial: 'initial',
156
+ staleWhileLoading: true,
157
+ }),
158
+ );
159
+
160
+ expect(useCroct).toHaveBeenCalled();
161
+
162
+ expect(useLoader).toHaveBeenCalledWith(expect.objectContaining({
163
+ cacheKey: hash(`useEvaluation:${key.current}:${query}:${JSON.stringify({})}`),
164
+ initial: 'initial',
165
+ }));
166
+
167
+ await waitFor(() => expect(result.current).toEqual('first'));
168
+
169
+ key.current = 'next';
170
+
171
+ rerender();
172
+
173
+ expect(useLoader).toHaveBeenCalledWith(expect.objectContaining({
174
+ cacheKey: hash(`useEvaluation:${key.current}:${query}:${JSON.stringify({})}`),
175
+ initial: 'first',
176
+ }));
177
+
178
+ await waitFor(() => expect(result.current).toEqual('second'));
179
+ });
94
180
  });