State Doesn't Get Defaulted And Values Don't Change

So i am making a reactive state system where the developer of a component can supply a default state and other states if they want. The default state is used when nothing is active and regarding the states. The code checks on the array to say which is active, the way the state array is orchestrated, it uses the first active state present in the list Each state has specific values to it, like color & other stuff. The STATE DEFAULTING EFFECT gets triggered and correctly switches the state. However the STATE DEFAULTING MEMO is not triggered. For some reason when i get the style field, even tho it does supply the correct value. The values do not get updated For context getterFromReactive "normalizes" the values into just accessors. It does create a effect for checking whenever the value is changed or not and update the returned value accordingly
export function createStateManager<T extends StateValues>(
defaultState: Required<T>,
...states: State<T>[]
): StateManager<T> {
const defaultCopyState: State<T> = {
debugName: "default",
getIsEnabled: () => true,
values: {...defaultState}
}

// <...>
const [currentState, setCurrentState] = createStore<State<T>>(defaultCopyState);

// STATE DEFAULTING MEMO
createMemo(() => {
const currValues: T = currentState.values;
const newObj: T = Object.assign({}, currValues)
for (const [key, value] of Object.entries(defaultCopyState.values)) {
if (key in newObj && newObj[key] !== undefined) continue;
newObj[key as keyof T] = value;
}
setCurrentState("values", newObj);
})

// STATE CHANGE EFFECT
createRenderEffect(() => {
for (const state of states) {
if (!state.getIsEnabled()) continue;
// <...>
setCurrentState(state)
return
}
// <...>
setCurrentState(defaultCopyState)
})

return {
// <...>
getStyleField: (field: keyof T) => getterFromReactive({ store: currentState.values, field: field }),
}
}
export function createStateManager<T extends StateValues>(
defaultState: Required<T>,
...states: State<T>[]
): StateManager<T> {
const defaultCopyState: State<T> = {
debugName: "default",
getIsEnabled: () => true,
values: {...defaultState}
}

// <...>
const [currentState, setCurrentState] = createStore<State<T>>(defaultCopyState);

// STATE DEFAULTING MEMO
createMemo(() => {
const currValues: T = currentState.values;
const newObj: T = Object.assign({}, currValues)
for (const [key, value] of Object.entries(defaultCopyState.values)) {
if (key in newObj && newObj[key] !== undefined) continue;
newObj[key as keyof T] = value;
}
setCurrentState("values", newObj);
})

// STATE CHANGE EFFECT
createRenderEffect(() => {
for (const state of states) {
if (!state.getIsEnabled()) continue;
// <...>
setCurrentState(state)
return
}
// <...>
setCurrentState(defaultCopyState)
})

return {
// <...>
getStyleField: (field: keyof T) => getterFromReactive({ store: currentState.values, field: field }),
}
}
17 Replies
McBrincie212
McBrincie212OP6d ago
tho idk if i explained it good enough tbh do yall point out any sort of confusions oh wait pretty odd so the first bug was eliminated for the second when i use getStyleField and the state changes for some odd reason
const currentBackgroundColor: Accessor<string> = toggleableStyleManager.getStyleField(
'currentBackgroundColor'
)

// DOESN'T WORK
createEffect(() => {
console.log(currentBackgroundColor())
})

// WORKS
createEffect(() => {
console.log(toggleableStyleManager.getStyleField('currentBackgroundColor')())
})
const currentBackgroundColor: Accessor<string> = toggleableStyleManager.getStyleField(
'currentBackgroundColor'
)

// DOESN'T WORK
createEffect(() => {
console.log(currentBackgroundColor())
})

// WORKS
createEffect(() => {
console.log(toggleableStyleManager.getStyleField('currentBackgroundColor')())
})
even tho they access the same thing
Random Mesh
Random Mesh6d ago
.getStyleField('...') must be reading/unboxing the memo early (out side of the first // DOESN'T WORK effect). You'll have to share your getterFromReactive code for closer inspection. Is getterFromReactive something like this?
getterFromReactive<A extends object, K extends keyof A>(params: { store: Store<A>, field: K, }): Accessor<A[K]> {
return () => params.store[params.field];
}
getterFromReactive<A extends object, K extends keyof A>(params: { store: Store<A>, field: K, }): Accessor<A[K]> {
return () => params.store[params.field];
}
https://playground.solidjs.com/anonymous/fb6c4743-abea-4b5f-882b-826d99e84db0
McBrincie212
McBrincie212OP6d ago
fixed it kind of i took the "lazy" approach of making the values as store and the name as a signal instead of making the the entire object here is the code for the getterFromReactive
export function getterFromReactive<T>(
value: ReactiveTypeGetter<T>,
func?: Function,
): Accessor<any> {
if (typeof value === 'function') {
// @ts-ignore
return func === undefined ? value : () => func(value())
} else if (value instanceof Object && 'store' in value && 'field' in value) {
const [valueGetter, valueSetter] = createSignal(
func === undefined ? () => value.store[value.field] : () => func(value.store[value.field]),
)
createRenderEffect(() => {
const val: T[keyof T] = value.store[value.field]
valueSetter(func === undefined ? val : func(val))
})
return valueGetter
}
return func === undefined ? () => value : () => func(value)
}
export function getterFromReactive<T>(
value: ReactiveTypeGetter<T>,
func?: Function,
): Accessor<any> {
if (typeof value === 'function') {
// @ts-ignore
return func === undefined ? value : () => func(value())
} else if (value instanceof Object && 'store' in value && 'field' in value) {
const [valueGetter, valueSetter] = createSignal(
func === undefined ? () => value.store[value.field] : () => func(value.store[value.field]),
)
createRenderEffect(() => {
const val: T[keyof T] = value.store[value.field]
valueSetter(func === undefined ? val : func(val))
})
return valueGetter
}
return func === undefined ? () => value : () => func(value)
}
and for ReactiveTypeGetter
export type StoreField<T> = { store: Store<T>; field: keyof T }

export type ReactiveTypeGetter<T> = T | Accessor<T> | StoreField<T>
export type StoreField<T> = { store: Store<T>; field: keyof T }

export type ReactiveTypeGetter<T> = T | Accessor<T> | StoreField<T>
Random Mesh
Random Mesh6d ago
What is the intended type signature of the inner createSignal ? Its like a store of a lambda, but then it is set by a potentially non-lambda in createRenderEffect. There might be a cleaner way to implement getterFromReactive without requiring createRenderEffect
McBrincie212
McBrincie212OP6d ago
hmm thats a good question, idk why i putted createSignal from before thats the issue
Random Mesh
Random Mesh6d ago
func is a utility to map the value on the way out?
McBrincie212
McBrincie212OP6d ago
yeh i use it sometimes for getting a unit for example. The parseUnit func is used which just converts it into a unit representation
Random Mesh
Random Mesh6d ago
return func === undefined ? () => value.store[value.field] ? () => func(value.store[value.field]);
return func === undefined ? () => value.store[value.field] ? () => func(value.store[value.field]);
Maybe (inside else-if branch)
McBrincie212
McBrincie212OP6d ago
tho what happens if the value changes the new value has to be reflected so the function has to be executed again
Random Mesh
Random Mesh6d ago
It will
McBrincie212
McBrincie212OP6d ago
oh
Random Mesh
Random Mesh6d ago
But... not optimimal, because the func will execute always even if read again with no change.
McBrincie212
McBrincie212OP6d ago
mhm\
Random Mesh
Random Mesh6d ago
U could use a createMemo there, but the reactive lifespan of the memo will only last the lifespan of the reactive scope it was called with. Thats why I have that ReactiveCache utility.
McBrincie212
McBrincie212OP6d ago
ok
Random Mesh
Random Mesh6d ago
Gotta go... been cool catching up.
McBrincie212
McBrincie212OP6d ago
yeh gtg too cya

Did you find this page helpful?