Last active
July 28, 2025 12:58
-
-
Save bolencki13/dcb863292fedfcad86de5f23c94c9be8 to your computer and use it in GitHub Desktop.
Implement a Proxy on an object to allow assignment of values while masking setState/dispatch in react
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useMemo, useReducer } from 'react'; | |
| const isProxy = Symbol('is_proxy'); | |
| function setNestedValue<T extends object>(obj: T, path: string, value: any): T { | |
| const parts = path.split('.'); | |
| let current: any = obj; | |
| for (let i = 0; i < parts.length; i++) { | |
| const part = parts[i]; | |
| const isLastPart = i === parts.length - 1; | |
| const nextPart = parts[i + 1]; | |
| const nextPartIsIndex = nextPart !== undefined && /^\d+$/.test(nextPart); | |
| if (isLastPart) { | |
| // This is the final part of the path, set the value | |
| if (Array.isArray(current) && /^\d+$/.test(part)) { | |
| const index = parseInt(part, 10); | |
| // Ensure array is long enough, fill with undefined if sparse | |
| while (current.length <= index) { | |
| current.push(undefined); | |
| } | |
| current[index] = value; | |
| } else { | |
| // Otherwise, set as a regular object property | |
| current[part] = value; | |
| } | |
| } else { | |
| // Not the last part, navigate or create the next level | |
| if (typeof current[part] !== 'object' || current[part] === null) { | |
| // If the current part does not exist, or is not an object/array, | |
| // decide whether to create an array or an object for the next level. | |
| current[part] = nextPartIsIndex ? [] : {}; | |
| } else if (nextPartIsIndex && !Array.isArray(current[part])) { | |
| // Current[part] exists but is not an array, and the next part implies an array. | |
| // We need to overwrite current[part] with an empty array. | |
| // This is a type coercion/transformation. | |
| // You might want to log a warning here depending on strictness. | |
| // console.warn(`Path segment "${part}" is not an array, but next segment "${nextPart}" suggests an array. Overwriting with empty array.`); | |
| current[part] = []; | |
| } | |
| // If nextPartIsIndex is false and current[part] is an array, we'll proceed | |
| // (allowing properties on arrays, e.g., `arr.myProp`). | |
| // If nextPartIsIndex is true and current[part] is already an array, we'll proceed. | |
| // If nextPartIsIndex is false and current[part] is already an object, we'll proceed. | |
| current = current[part]; | |
| } | |
| } | |
| return obj; // Return the modified object | |
| } | |
| function useDataset<S>(initialState: S | (() => S)): S; | |
| function useDataset<S = undefined>(): S | undefined; | |
| function useDataset<S>(defaultValue?: S) { | |
| const [state, dispatch] = useReducer((_, payload) => { | |
| return structuredClone(payload); | |
| }, defaultValue); | |
| const clone = useMemo(() => { | |
| function getProxy<T extends Record<string, unknown>>(obj: T, parentPath?: string[]): T { | |
| return new Proxy(obj, { | |
| deleteProperty(_, prop) { | |
| throw new Error(`Property [${prop.toString()}] cannot be deleted`); | |
| }, | |
| get: function (target, prop, receiver) { | |
| if (prop === isProxy) { | |
| return true; | |
| } | |
| if ( | |
| typeof (target as any)[prop] === 'object' && | |
| (target as any)[prop] != undefined | |
| ) { | |
| return getProxy( | |
| (target as any)[prop] as any, | |
| [parentPath, prop].filter(Boolean).map((v) => v?.toString()) as typeof parentPath | |
| ); | |
| } | |
| return Reflect.get(target, prop, receiver); | |
| }, | |
| set(target, prop, value) { | |
| (target as any)[prop] = value; | |
| const next = structuredClone(state); | |
| dispatch(setNestedValue(next as any, [...(parentPath ?? []), prop].filter(Boolean).join('.'), value)); | |
| return true; | |
| }, | |
| }); | |
| } | |
| if (!state) { | |
| return state; | |
| } | |
| return getProxy(state); | |
| }, [state, dispatch]); | |
| return clone as S; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment