@createstore/createstore 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -8,6 +8,16 @@ npm install @createstore/createstore
8
8
 
9
9
  Requires React 19 or newer. Works in the browser, in Node, and with Next.js Server Components.
10
10
 
11
+ Not related to Redux's `createStore` function: this is a separate library, installed as `@createstore/createstore`.
12
+
13
+ ## When to use it
14
+
15
+ - **Use it** when several components share state and you want the `useState` shape without a provider or reducers,
16
+ and when you also need server data, saved state, undo, tab sync or offline support behind the same API.
17
+ - **It is fastest** when many components read different keys, because only readers of a changed key re-render.
18
+ - **Look elsewhere** if you need to support React 18 or older, or if every component re-renders on every change
19
+ (Zustand is faster there; see `bench/`).
20
+
11
21
  ## Quick start
12
22
 
13
23
  ```tsx
@@ -1070,6 +1070,8 @@ function build(initialState, options, sharedDefinition) {
1070
1070
  }
1071
1071
  };
1072
1072
  let hydrated = !persistStorage;
1073
+ let warnedUnsavable = false;
1074
+ const earlyWrites = /* @__PURE__ */ new Set();
1073
1075
  let resolveHydrated;
1074
1076
  const whenHydrated = new Promise((r) => resolveHydrated = r);
1075
1077
  let storageIsAsync = false;
@@ -1096,7 +1098,26 @@ function build(initialState, options, sharedDefinition) {
1096
1098
  saveTimer = void 0;
1097
1099
  if (!persistStorage || !hydrated) return;
1098
1100
  const data = Object.fromEntries(persistedKeys.map((k) => [k, state[k]]));
1099
- const text = JSON.stringify(storageIsAsync ? { $version: persistVersion, $saved: Date.now(), state: data } : { $version: persistVersion, state: data });
1101
+ let text;
1102
+ try {
1103
+ text = JSON.stringify(storageIsAsync ? { $version: persistVersion, $saved: Date.now(), state: data } : { $version: persistVersion, state: data });
1104
+ } catch (error) {
1105
+ if (!warnedUnsavable) {
1106
+ warnedUnsavable = true;
1107
+ const bad = persistedKeys.find((k) => {
1108
+ try {
1109
+ JSON.stringify(state[k]);
1110
+ return false;
1111
+ } catch {
1112
+ return true;
1113
+ }
1114
+ });
1115
+ console.warn(
1116
+ `[${name}] couldn't save${bad ? ` "${bad}"` : ""}: ${error.message}. Saved state is JSON, so keep persisted keys to strings, numbers, booleans, null, arrays and plain objects, or leave this key out of saving with persist: { exclude: [...] }. Nothing is saved until it is fixed.`
1117
+ );
1118
+ }
1119
+ return;
1120
+ }
1100
1121
  lastSavedText = text;
1101
1122
  quietly(() => {
1102
1123
  const written = persistStorage.setItem(persistKey, text);
@@ -1201,9 +1222,12 @@ function build(initialState, options, sharedDefinition) {
1201
1222
  }
1202
1223
  if (rescued != null && isSavedState(rescued) && savedAt(rescued) > savedAt(stored)) text = rescued;
1203
1224
  const values = trusted(pick(readSaved(text)), "storage");
1225
+ for (const k of earlyWrites) delete values[k];
1226
+ const keptEarly = earlyWrites.size > 0;
1227
+ earlyWrites.clear();
1204
1228
  hydrated = true;
1205
1229
  if (!destroyed) apply(values, "storage", "loaded from storage", false);
1206
- if (!destroyed && text === rescued && rescued != null) saveNow();
1230
+ if (!destroyed && (keptEarly || text === rescued && rescued != null)) saveNow();
1207
1231
  finishHydration();
1208
1232
  },
1209
1233
  (error) => {
@@ -1401,6 +1425,7 @@ function build(initialState, options, sharedDefinition) {
1401
1425
  (k) => isStateKey(k) && !Object.is(state[k], values[k])
1402
1426
  );
1403
1427
  if (keys.length === 0) return;
1428
+ if (!hydrated && source === "local") for (const k of keys) earlyWrites.add(k);
1404
1429
  const prev = {};
1405
1430
  for (const k of keys) {
1406
1431
  prev[k] = state[k];
@@ -2137,11 +2162,15 @@ function build(initialState, options, sharedDefinition) {
2137
2162
  }
2138
2163
  const k = cacheKey(q, params);
2139
2164
  const prev = cache2.get(k)?.data;
2165
+ outdateRequest(k);
2140
2166
  setEntry(k, {
2141
2167
  data: typeof updater === "function" ? updater(prev) : updater,
2142
2168
  status: "success"
2143
2169
  });
2144
2170
  }
2171
+ function outdateRequest(k) {
2172
+ if (inflight.has(k)) versions.set(k, (versions.get(k) ?? 0) + 1);
2173
+ }
2145
2174
  function abort(q) {
2146
2175
  if (isQuery(q)) inlineAborts.get(q)?.abort();
2147
2176
  for (const [k, c] of [...namedAborts])
@@ -2675,6 +2704,7 @@ function build(initialState, options, sharedDefinition) {
2675
2704
  setter = (value, serverCall) => {
2676
2705
  const prev = cache2.get(k)?.data;
2677
2706
  const next = typeof value === "function" ? value(prev) : value;
2707
+ outdateRequest(k);
2678
2708
  setEntry(k, { data: next, status: "success" });
2679
2709
  if (!serverCall) return Promise.resolve();
2680
2710
  const done = Promise.resolve().then(serverCall).then(