I have a SolidJS application, and I'm storing a large JSON object that nests other objects. I'm using Immer to generate patches for undo and redo actions. (well, technically I'm storing a class with nested classes several layers deep, but Immer handles them just fine, so this seems like a trivial detail.)
I'd like to be able to pass a subset of that big object to components, say bigObject.someProp[0], and allow the component to modify and access that instead of having to access bigObject directly. Something like this:
function createImmerSignal<T>(value: T) {
const [get, set] = createSignal(value);
function newSet(callback: (draft: Draft<T>) => void) {
const newVal = produce(get(), callback, (redo, undo) => {
// Undo and redo logic, would modify some context variable
});
set(newVal as Exclude<T, Function>);
}
// createNested is what I'm looking to implement
return [get, newSet, createNested];
}
type Company = {
name: string;
developers: {
name: string;
someOtherProp: string;
}[];
};
function CompOne(prop: { origCompany: Company }) {
const [company, setCompany, createNested] = createImmerSignal<Company>(origCompany);
// I'm not sure what a good syntax for this would be, or how to even get this functional. This syntax is what I've come up with:
const dev = createNested(company => company.developers[0]);
return <>
<CompTwo dev={dev} />
</>
}
function CompTwo(props) {
// createNested can be used indefinitely
const [dev, setDev, createNested] = props.dev;
setDev(draft => {
// This would update company.developers[0].someProp
draft.name = 'Darles Nemeni';
});
return <></>;
}
I'm not entirely sure how to implement this, especially in a way that would respect types. I've tried using functions (company => company.developers[0]) to select the subset of company:
function newNested<U>(sel: (orig: T | Draft<T>) => U) {
return (fn: (draft: Draft<U>) => void) => set(
produce(get(), draft => {
// vvvvvvv This does not work, as you cannot assign like that
sel(draft) = produce(sel(draft), draft => fn(draft))
}) as Exclude<T, Function>
)
}
newNested(company => company.developers[0])
However, I cannot assign a value to sel(draft), unless I access a property on the parent, and I don't believe there's a clean way to do that.
My only idea for fixing that above (and I'm not sure if it's a good idea or would work) would be to somehow use a proxy to detect which properties are being accessed and recursively create a string of accessors to then use on draft, but that hardly seems efficient or clean.
To reiterate my question: What would be the best and cleanest way of designing this?
Semi-related question: I'm struggling to describe this problem, so if anyone has a better title, please let me know.
Best course of action is getting rid of
createImmerSignaland use a store: https://www.solidjs.com/docs/latest#store-utilities. Store provides Immer like API and uses a Proxy to rewire interactions to provide reactivity. It has many utilities to help you interact with the stored value and since store produces a reactive value, you can derive a subset of it directly without breaking a sweat.If you insist on creating your own store then you can take a look at its implementation to get started.