svelte: A .svelte.ts file does not create reactivity across numerous $derived updates

Describe the bug

I could not get chained reactivity to work in a .svelte.ts class. The goal is to have all state stored and modified in a single place, with varying views reacting to the updates. Use cases include Svelte components with an AppData instance in their props that rerender when $state and $derived data in AppData are updated, while others only receive updated props from a parent component that holds an AppData.

I tried using a class and a HOF and neither approach was successful. A single $state field can be updated in AppData and reactively rerenders views, but anything more complex than that breaks.

AppData.svelte.ts using a class AppData

export class AppData {
    #currentIndex: number = $state(0)
    #inspections: Array<InspectionState> = $state([])
    #url: string | null = $state(null)

    #currentInspection: InspectionState | null = $derived.by(() => {
        if (this.#inspections[this.#currentIndex]) {
            return this.#inspections[this.#currentIndex]
        } else {
            return null
        }
    })

    get focusedInspection(): InspectionState | null {
        return this.#currentInspection
    }

    get inspections(): Array<InspectionState> {
        return this.#inspections
    }

    set url(url: string) {
        this.#url = url
    }

    get url(): string | null {
        return this.#url
    }

    addInspectResult(result: InspectResult): void {
        this.#inspections.push({
            boundingBoxes: buildBoundingBoxes(result.elements),
            result,
            status: InspectionStatus.retrieved,
            type: result.selector ? InspectionType.selector : InspectionType.point,
        })
        this.#currentIndex = this.#inspections.length - 1
    }
}

AppData.svelte.ts using a HOF

export function createAppData(): AppData {
    let _currentIndex: number = $state(-1)
    let _inspections: Array<InspectionState> = $state([])
    let _url: string | null = $state(null)

    let _currentInspection: InspectionState | null = $derived.by(() => {
        if (_inspections[_currentIndex]) {
            return _inspections[_currentIndex]
        } else {
            return null
        }
    })

    $inspect(_currentIndex, _currentInspection)

    return {
        get focusedInspection(): InspectionState | null {
            return _currentInspection
        },
        get url(): string | null {
            return _url
        },
        set url(url: string | null) {
            _url = url
        },
        addInspectResult(result: InspectResult) {
            _inspections.push({
                boundingBoxes: buildBoundingBoxes(result.elements),
                type: result.selector ? InspectionType.selector : InspectionType.point,
                status: InspectionStatus.retrieved,
                result,
            })
            _currentIndex++
        },
    }
}

There’s also some weird typey things that don’t seem like a TypeScript issue. It seems like a fn like a foo(): SomeType | null always gets type erasured to SomeType, or if there’s a return type with an optional field like type Foo = {someOptional?: {fieldsNested: boolean}} that comes from a function return type in a .svelte.ts, TypeScript will resolve result.someOptional.fieldsNested but understanding the result.someOptional?.fieldsNested breaks resolution.

Are these use cases unreasonable expectations or am I going about it wrong and have some unidentified logic mishap?

Reproduction

AppData.svelte.ts using a HOF

AppData.svelte.ts using a class AppData

Logs

No response

System Info

System:
    OS: macOS 14.4.1
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 1.29 GB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.9.0 - /usr/local/bin/node
    npm: 10.1.0 - /usr/local/bin/npm
    pnpm: 8.6.3 - /usr/local/bin/pnpm
  Browsers:
    Chrome: 124.0.6367.61
    Safari: 17.4.1

Severity

blocking an upgrade

About this issue

  • Original URL
  • State: closed
  • Created 3 months ago
  • Reactions: 1
  • Comments: 28 (14 by maintainers)

Most upvoted comments

toReversed would not do it in place. Dumb that there isn’t a dx catch.

Wdym by this? That’s not something svelte can warn you against. It’s a prop, from what svelte is concerned you might really want to reverse that array whenever you render.

And toReversed works, I just tested it.

toReversed would not do it in place. Dumb that there isn’t a dx catch.