@tanstack/react-db 0.5.10 → 0.5.12
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/dist/cjs/usePacedMutations.cjs +4 -2
- package/dist/cjs/usePacedMutations.cjs.map +1 -1
- package/dist/cjs/usePacedMutations.d.cts +2 -0
- package/dist/esm/usePacedMutations.d.ts +2 -0
- package/dist/esm/usePacedMutations.js +5 -3
- package/dist/esm/usePacedMutations.js.map +1 -1
- package/package.json +2 -2
- package/src/usePacedMutations.ts +10 -5
|
@@ -4,9 +4,11 @@ const react = require("react");
|
|
|
4
4
|
const db = require("@tanstack/db");
|
|
5
5
|
function usePacedMutations(config) {
|
|
6
6
|
const onMutateRef = react.useRef(config.onMutate);
|
|
7
|
-
onMutateRef.current = config.onMutate;
|
|
8
7
|
const mutationFnRef = react.useRef(config.mutationFn);
|
|
9
|
-
|
|
8
|
+
react.useInsertionEffect(() => {
|
|
9
|
+
onMutateRef.current = config.onMutate;
|
|
10
|
+
mutationFnRef.current = config.mutationFn;
|
|
11
|
+
}, [config.onMutate, config.mutationFn]);
|
|
10
12
|
const stableOnMutate = react.useCallback((variables) => {
|
|
11
13
|
return onMutateRef.current(variables);
|
|
12
14
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePacedMutations.cjs","sources":["../../src/usePacedMutations.ts"],"sourcesContent":["import { useCallback, useMemo, useRef } from 'react'\nimport { createPacedMutations } from '@tanstack/db'\nimport type { PacedMutationsConfig, Transaction } from '@tanstack/db'\n\n/**\n * React hook for managing paced mutations with timing strategies.\n *\n * Provides optimistic mutations with pluggable strategies like debouncing,\n * queuing, or throttling. The optimistic updates are applied immediately via\n * `onMutate`, and the actual persistence is controlled by the strategy.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A mutate function that accepts variables and returns Transaction objects\n *\n * @example\n * ```tsx\n * // Debounced auto-save\n * function AutoSaveForm({ formId }: { formId: string }) {\n * const mutate = usePacedMutations<string>({\n * onMutate: (value) => {\n * // Apply optimistic update immediately\n * formCollection.update(formId, draft => {\n * draft.content = value\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * const handleChange = async (value: string) => {\n * const tx = mutate(value)\n *\n * // Optional: await persistence or handle errors\n * try {\n * await tx.isPersisted.promise\n * console.log('Saved!')\n * } catch (error) {\n * console.error('Save failed:', error)\n * }\n * }\n *\n * return <textarea onChange={e => handleChange(e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Throttled slider updates\n * function VolumeSlider() {\n * const mutate = usePacedMutations<number>({\n * onMutate: (volume) => {\n * settingsCollection.update('volume', draft => {\n * draft.value = volume\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateVolume(transaction.mutations)\n * },\n * strategy: throttleStrategy({ wait: 200 })\n * })\n *\n * return <input type=\"range\" onChange={e => mutate(+e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Debounce with leading/trailing for color picker (persist first + final only)\n * function ColorPicker() {\n * const mutate = usePacedMutations<string>({\n * onMutate: (color) => {\n * themeCollection.update('primary', draft => {\n * draft.color = color\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateTheme(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 0, leading: true, trailing: true })\n * })\n *\n * return (\n * <input\n * type=\"color\"\n * onChange={e => mutate(e.target.value)}\n * />\n * )\n * }\n * ```\n */\nexport function usePacedMutations<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n>(\n config: PacedMutationsConfig<TVariables, T>,\n): (variables: TVariables) => Transaction<T> {\n //
|
|
1
|
+
{"version":3,"file":"usePacedMutations.cjs","sources":["../../src/usePacedMutations.ts"],"sourcesContent":["import { useCallback, useInsertionEffect, useMemo, useRef } from 'react'\nimport { createPacedMutations } from '@tanstack/db'\nimport type { PacedMutationsConfig, Transaction } from '@tanstack/db'\n\n/**\n * React hook for managing paced mutations with timing strategies.\n *\n * Provides optimistic mutations with pluggable strategies like debouncing,\n * queuing, or throttling. The optimistic updates are applied immediately via\n * `onMutate`, and the actual persistence is controlled by the strategy.\n * Callbacks follow the latest committed render, so a suspended render cannot\n * replace the callbacks used by active mutations.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A mutate function that accepts variables and returns Transaction objects\n *\n * @example\n * ```tsx\n * // Debounced auto-save\n * function AutoSaveForm({ formId }: { formId: string }) {\n * const mutate = usePacedMutations<string>({\n * onMutate: (value) => {\n * // Apply optimistic update immediately\n * formCollection.update(formId, draft => {\n * draft.content = value\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * const handleChange = async (value: string) => {\n * const tx = mutate(value)\n *\n * // Optional: await persistence or handle errors\n * try {\n * await tx.isPersisted.promise\n * console.log('Saved!')\n * } catch (error) {\n * console.error('Save failed:', error)\n * }\n * }\n *\n * return <textarea onChange={e => handleChange(e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Throttled slider updates\n * function VolumeSlider() {\n * const mutate = usePacedMutations<number>({\n * onMutate: (volume) => {\n * settingsCollection.update('volume', draft => {\n * draft.value = volume\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateVolume(transaction.mutations)\n * },\n * strategy: throttleStrategy({ wait: 200 })\n * })\n *\n * return <input type=\"range\" onChange={e => mutate(+e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Debounce with leading/trailing for color picker (persist first + final only)\n * function ColorPicker() {\n * const mutate = usePacedMutations<string>({\n * onMutate: (color) => {\n * themeCollection.update('primary', draft => {\n * draft.color = color\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateTheme(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 0, leading: true, trailing: true })\n * })\n *\n * return (\n * <input\n * type=\"color\"\n * onChange={e => mutate(e.target.value)}\n * />\n * )\n * }\n * ```\n */\nexport function usePacedMutations<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n>(\n config: PacedMutationsConfig<TVariables, T>,\n): (variables: TVariables) => Transaction<T> {\n // Publish callbacks only for committed renders, before descendant layout\n // effects can mutate. Updating during render leaks suspended callbacks.\n const onMutateRef = useRef(config.onMutate)\n const mutationFnRef = useRef(config.mutationFn)\n\n useInsertionEffect(() => {\n onMutateRef.current = config.onMutate\n mutationFnRef.current = config.mutationFn\n }, [config.onMutate, config.mutationFn])\n\n // Create stable wrappers that always call the latest version\n const stableOnMutate = useCallback<typeof config.onMutate>((variables) => {\n return onMutateRef.current(variables)\n }, [])\n\n const stableMutationFn = useCallback<typeof config.mutationFn>((params) => {\n return mutationFnRef.current(params)\n }, [])\n\n // Create paced mutations instance with proper dependency tracking\n // Serialize strategy for stable comparison since strategy objects are recreated on each render\n const mutate = useMemo(() => {\n return createPacedMutations<TVariables, T>({\n ...config,\n onMutate: stableOnMutate,\n mutationFn: stableMutationFn,\n })\n }, [\n stableOnMutate,\n stableMutationFn,\n config.metadata,\n // Serialize strategy to avoid recreating when object reference changes but values are same\n JSON.stringify({\n type: config.strategy._type,\n options: config.strategy.options,\n }),\n ])\n\n // Return stable mutate callback\n const stableMutate = useCallback(mutate, [mutate])\n\n return stableMutate\n}\n"],"names":["useRef","useInsertionEffect","useCallback","useMemo","createPacedMutations"],"mappings":";;;;AA8FO,SAAS,kBAId,QAC2C;AAG3C,QAAM,cAAcA,MAAAA,OAAO,OAAO,QAAQ;AAC1C,QAAM,gBAAgBA,MAAAA,OAAO,OAAO,UAAU;AAE9CC,QAAAA,mBAAmB,MAAM;AACvB,gBAAY,UAAU,OAAO;AAC7B,kBAAc,UAAU,OAAO;AAAA,EACjC,GAAG,CAAC,OAAO,UAAU,OAAO,UAAU,CAAC;AAGvC,QAAM,iBAAiBC,kBAAoC,CAAC,cAAc;AACxE,WAAO,YAAY,QAAQ,SAAS;AAAA,EACtC,GAAG,CAAA,CAAE;AAEL,QAAM,mBAAmBA,kBAAsC,CAAC,WAAW;AACzE,WAAO,cAAc,QAAQ,MAAM;AAAA,EACrC,GAAG,CAAA,CAAE;AAIL,QAAM,SAASC,MAAAA,QAAQ,MAAM;AAC3B,WAAOC,wBAAoC;AAAA,MACzC,GAAG;AAAA,MACH,UAAU;AAAA,MACV,YAAY;AAAA,IAAA,CACb;AAAA,EACH,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,OAAO;AAAA;AAAA,IAEP,KAAK,UAAU;AAAA,MACb,MAAM,OAAO,SAAS;AAAA,MACtB,SAAS,OAAO,SAAS;AAAA,IAAA,CAC1B;AAAA,EAAA,CACF;AAGD,QAAM,eAAeF,MAAAA,YAAY,QAAQ,CAAC,MAAM,CAAC;AAEjD,SAAO;AACT;;"}
|
|
@@ -5,6 +5,8 @@ import { PacedMutationsConfig, Transaction } from '@tanstack/db';
|
|
|
5
5
|
* Provides optimistic mutations with pluggable strategies like debouncing,
|
|
6
6
|
* queuing, or throttling. The optimistic updates are applied immediately via
|
|
7
7
|
* `onMutate`, and the actual persistence is controlled by the strategy.
|
|
8
|
+
* Callbacks follow the latest committed render, so a suspended render cannot
|
|
9
|
+
* replace the callbacks used by active mutations.
|
|
8
10
|
*
|
|
9
11
|
* @param config - Configuration including onMutate, mutationFn and strategy
|
|
10
12
|
* @returns A mutate function that accepts variables and returns Transaction objects
|
|
@@ -5,6 +5,8 @@ import { PacedMutationsConfig, Transaction } from '@tanstack/db';
|
|
|
5
5
|
* Provides optimistic mutations with pluggable strategies like debouncing,
|
|
6
6
|
* queuing, or throttling. The optimistic updates are applied immediately via
|
|
7
7
|
* `onMutate`, and the actual persistence is controlled by the strategy.
|
|
8
|
+
* Callbacks follow the latest committed render, so a suspended render cannot
|
|
9
|
+
* replace the callbacks used by active mutations.
|
|
8
10
|
*
|
|
9
11
|
* @param config - Configuration including onMutate, mutationFn and strategy
|
|
10
12
|
* @returns A mutate function that accepts variables and returns Transaction objects
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { useRef, useCallback, useMemo } from "react";
|
|
1
|
+
import { useRef, useInsertionEffect, useCallback, useMemo } from "react";
|
|
2
2
|
import { createPacedMutations } from "@tanstack/db";
|
|
3
3
|
function usePacedMutations(config) {
|
|
4
4
|
const onMutateRef = useRef(config.onMutate);
|
|
5
|
-
onMutateRef.current = config.onMutate;
|
|
6
5
|
const mutationFnRef = useRef(config.mutationFn);
|
|
7
|
-
|
|
6
|
+
useInsertionEffect(() => {
|
|
7
|
+
onMutateRef.current = config.onMutate;
|
|
8
|
+
mutationFnRef.current = config.mutationFn;
|
|
9
|
+
}, [config.onMutate, config.mutationFn]);
|
|
8
10
|
const stableOnMutate = useCallback((variables) => {
|
|
9
11
|
return onMutateRef.current(variables);
|
|
10
12
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePacedMutations.js","sources":["../../src/usePacedMutations.ts"],"sourcesContent":["import { useCallback, useMemo, useRef } from 'react'\nimport { createPacedMutations } from '@tanstack/db'\nimport type { PacedMutationsConfig, Transaction } from '@tanstack/db'\n\n/**\n * React hook for managing paced mutations with timing strategies.\n *\n * Provides optimistic mutations with pluggable strategies like debouncing,\n * queuing, or throttling. The optimistic updates are applied immediately via\n * `onMutate`, and the actual persistence is controlled by the strategy.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A mutate function that accepts variables and returns Transaction objects\n *\n * @example\n * ```tsx\n * // Debounced auto-save\n * function AutoSaveForm({ formId }: { formId: string }) {\n * const mutate = usePacedMutations<string>({\n * onMutate: (value) => {\n * // Apply optimistic update immediately\n * formCollection.update(formId, draft => {\n * draft.content = value\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * const handleChange = async (value: string) => {\n * const tx = mutate(value)\n *\n * // Optional: await persistence or handle errors\n * try {\n * await tx.isPersisted.promise\n * console.log('Saved!')\n * } catch (error) {\n * console.error('Save failed:', error)\n * }\n * }\n *\n * return <textarea onChange={e => handleChange(e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Throttled slider updates\n * function VolumeSlider() {\n * const mutate = usePacedMutations<number>({\n * onMutate: (volume) => {\n * settingsCollection.update('volume', draft => {\n * draft.value = volume\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateVolume(transaction.mutations)\n * },\n * strategy: throttleStrategy({ wait: 200 })\n * })\n *\n * return <input type=\"range\" onChange={e => mutate(+e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Debounce with leading/trailing for color picker (persist first + final only)\n * function ColorPicker() {\n * const mutate = usePacedMutations<string>({\n * onMutate: (color) => {\n * themeCollection.update('primary', draft => {\n * draft.color = color\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateTheme(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 0, leading: true, trailing: true })\n * })\n *\n * return (\n * <input\n * type=\"color\"\n * onChange={e => mutate(e.target.value)}\n * />\n * )\n * }\n * ```\n */\nexport function usePacedMutations<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n>(\n config: PacedMutationsConfig<TVariables, T>,\n): (variables: TVariables) => Transaction<T> {\n //
|
|
1
|
+
{"version":3,"file":"usePacedMutations.js","sources":["../../src/usePacedMutations.ts"],"sourcesContent":["import { useCallback, useInsertionEffect, useMemo, useRef } from 'react'\nimport { createPacedMutations } from '@tanstack/db'\nimport type { PacedMutationsConfig, Transaction } from '@tanstack/db'\n\n/**\n * React hook for managing paced mutations with timing strategies.\n *\n * Provides optimistic mutations with pluggable strategies like debouncing,\n * queuing, or throttling. The optimistic updates are applied immediately via\n * `onMutate`, and the actual persistence is controlled by the strategy.\n * Callbacks follow the latest committed render, so a suspended render cannot\n * replace the callbacks used by active mutations.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A mutate function that accepts variables and returns Transaction objects\n *\n * @example\n * ```tsx\n * // Debounced auto-save\n * function AutoSaveForm({ formId }: { formId: string }) {\n * const mutate = usePacedMutations<string>({\n * onMutate: (value) => {\n * // Apply optimistic update immediately\n * formCollection.update(formId, draft => {\n * draft.content = value\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * const handleChange = async (value: string) => {\n * const tx = mutate(value)\n *\n * // Optional: await persistence or handle errors\n * try {\n * await tx.isPersisted.promise\n * console.log('Saved!')\n * } catch (error) {\n * console.error('Save failed:', error)\n * }\n * }\n *\n * return <textarea onChange={e => handleChange(e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Throttled slider updates\n * function VolumeSlider() {\n * const mutate = usePacedMutations<number>({\n * onMutate: (volume) => {\n * settingsCollection.update('volume', draft => {\n * draft.value = volume\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateVolume(transaction.mutations)\n * },\n * strategy: throttleStrategy({ wait: 200 })\n * })\n *\n * return <input type=\"range\" onChange={e => mutate(+e.target.value)} />\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Debounce with leading/trailing for color picker (persist first + final only)\n * function ColorPicker() {\n * const mutate = usePacedMutations<string>({\n * onMutate: (color) => {\n * themeCollection.update('primary', draft => {\n * draft.color = color\n * })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.updateTheme(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 0, leading: true, trailing: true })\n * })\n *\n * return (\n * <input\n * type=\"color\"\n * onChange={e => mutate(e.target.value)}\n * />\n * )\n * }\n * ```\n */\nexport function usePacedMutations<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n>(\n config: PacedMutationsConfig<TVariables, T>,\n): (variables: TVariables) => Transaction<T> {\n // Publish callbacks only for committed renders, before descendant layout\n // effects can mutate. Updating during render leaks suspended callbacks.\n const onMutateRef = useRef(config.onMutate)\n const mutationFnRef = useRef(config.mutationFn)\n\n useInsertionEffect(() => {\n onMutateRef.current = config.onMutate\n mutationFnRef.current = config.mutationFn\n }, [config.onMutate, config.mutationFn])\n\n // Create stable wrappers that always call the latest version\n const stableOnMutate = useCallback<typeof config.onMutate>((variables) => {\n return onMutateRef.current(variables)\n }, [])\n\n const stableMutationFn = useCallback<typeof config.mutationFn>((params) => {\n return mutationFnRef.current(params)\n }, [])\n\n // Create paced mutations instance with proper dependency tracking\n // Serialize strategy for stable comparison since strategy objects are recreated on each render\n const mutate = useMemo(() => {\n return createPacedMutations<TVariables, T>({\n ...config,\n onMutate: stableOnMutate,\n mutationFn: stableMutationFn,\n })\n }, [\n stableOnMutate,\n stableMutationFn,\n config.metadata,\n // Serialize strategy to avoid recreating when object reference changes but values are same\n JSON.stringify({\n type: config.strategy._type,\n options: config.strategy.options,\n }),\n ])\n\n // Return stable mutate callback\n const stableMutate = useCallback(mutate, [mutate])\n\n return stableMutate\n}\n"],"names":[],"mappings":";;AA8FO,SAAS,kBAId,QAC2C;AAG3C,QAAM,cAAc,OAAO,OAAO,QAAQ;AAC1C,QAAM,gBAAgB,OAAO,OAAO,UAAU;AAE9C,qBAAmB,MAAM;AACvB,gBAAY,UAAU,OAAO;AAC7B,kBAAc,UAAU,OAAO;AAAA,EACjC,GAAG,CAAC,OAAO,UAAU,OAAO,UAAU,CAAC;AAGvC,QAAM,iBAAiB,YAAoC,CAAC,cAAc;AACxE,WAAO,YAAY,QAAQ,SAAS;AAAA,EACtC,GAAG,CAAA,CAAE;AAEL,QAAM,mBAAmB,YAAsC,CAAC,WAAW;AACzE,WAAO,cAAc,QAAQ,MAAM;AAAA,EACrC,GAAG,CAAA,CAAE;AAIL,QAAM,SAAS,QAAQ,MAAM;AAC3B,WAAO,qBAAoC;AAAA,MACzC,GAAG;AAAA,MACH,UAAU;AAAA,MACV,YAAY;AAAA,IAAA,CACb;AAAA,EACH,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,OAAO;AAAA;AAAA,IAEP,KAAK,UAAU;AAAA,MACb,MAAM,OAAO,SAAS;AAAA,MACtB,SAAS,OAAO,SAAS;AAAA,IAAA,CAC1B;AAAA,EAAA,CACF;AAGD,QAAM,eAAe,YAAY,QAAQ,CAAC,MAAM,CAAC;AAEjD,SAAO;AACT;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-db",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"description": "React integration for @tanstack/db",
|
|
5
5
|
"author": "Kyle Mathews",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"skills"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@tanstack/db": "0.15.
|
|
43
|
+
"@tanstack/db": "0.15.2"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"react": ">=18.0.0"
|
package/src/usePacedMutations.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useMemo, useRef } from 'react'
|
|
1
|
+
import { useCallback, useInsertionEffect, useMemo, useRef } from 'react'
|
|
2
2
|
import { createPacedMutations } from '@tanstack/db'
|
|
3
3
|
import type { PacedMutationsConfig, Transaction } from '@tanstack/db'
|
|
4
4
|
|
|
@@ -8,6 +8,8 @@ import type { PacedMutationsConfig, Transaction } from '@tanstack/db'
|
|
|
8
8
|
* Provides optimistic mutations with pluggable strategies like debouncing,
|
|
9
9
|
* queuing, or throttling. The optimistic updates are applied immediately via
|
|
10
10
|
* `onMutate`, and the actual persistence is controlled by the strategy.
|
|
11
|
+
* Callbacks follow the latest committed render, so a suspended render cannot
|
|
12
|
+
* replace the callbacks used by active mutations.
|
|
11
13
|
*
|
|
12
14
|
* @param config - Configuration including onMutate, mutationFn and strategy
|
|
13
15
|
* @returns A mutate function that accepts variables and returns Transaction objects
|
|
@@ -96,12 +98,15 @@ export function usePacedMutations<
|
|
|
96
98
|
>(
|
|
97
99
|
config: PacedMutationsConfig<TVariables, T>,
|
|
98
100
|
): (variables: TVariables) => Transaction<T> {
|
|
99
|
-
//
|
|
101
|
+
// Publish callbacks only for committed renders, before descendant layout
|
|
102
|
+
// effects can mutate. Updating during render leaks suspended callbacks.
|
|
100
103
|
const onMutateRef = useRef(config.onMutate)
|
|
101
|
-
onMutateRef.current = config.onMutate
|
|
102
|
-
|
|
103
104
|
const mutationFnRef = useRef(config.mutationFn)
|
|
104
|
-
|
|
105
|
+
|
|
106
|
+
useInsertionEffect(() => {
|
|
107
|
+
onMutateRef.current = config.onMutate
|
|
108
|
+
mutationFnRef.current = config.mutationFn
|
|
109
|
+
}, [config.onMutate, config.mutationFn])
|
|
105
110
|
|
|
106
111
|
// Create stable wrappers that always call the latest version
|
|
107
112
|
const stableOnMutate = useCallback<typeof config.onMutate>((variables) => {
|