Skip to content

Commit

Permalink
proxy state tree green
Browse files Browse the repository at this point in the history
  • Loading branch information
christianalfoni committed Mar 5, 2024
1 parent e0d39f9 commit 7a4bfa2
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 266 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Visit website for more information: [www.overmindjs.org](https://overmindjs.org)

<https://gist.github.com/christianalfoni/f1c4bfe320dcb24c403635d9bca3fa40>

## Contributing

// List all packages and state how to develop and test them

## Release procedure

```sh
Expand Down
2 changes: 2 additions & 0 deletions packages/overmind-react/src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('React', () => {

const FooComponent: React.FunctionComponent = () => {
const state = useState((state) => state.foo[0])
console.log('WTF?')
renderCount++

return <h1>{state.foo}</h1>
Expand All @@ -110,6 +111,7 @@ describe('React', () => {
act(() => {
app.actions.doThat()
})

expect(renderCount).toBe(2)

// This is not showing the expected result, but logging the rendering does, so must be the
Expand Down
159 changes: 23 additions & 136 deletions packages/overmind-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class ReactTrackerV18 {
}
}

const useStateV18 = <Context extends IContext<{ state: {} }>>(
const useState = <Context extends IContext<{ state: {} }>>(
cb?: (state: Context['state']) => any
): Context['state'] => {
const overmind = React.useContext(context) as Overmind<any>
Expand All @@ -134,30 +134,23 @@ const useStateV18 = <Context extends IContext<{ state: {} }>>(
return overmind.state
}

const ref = React.useRef(null)

if (!ref.current) {
// @ts-ignore
ref.current = new ReactTrackerV18(overmind.getTrackStateTree())
}

const tracker = ref.current as any
const { flushId, forceRerender } = useForceRerender()

const snapshot = React.useSyncExternalStore(
tracker.subscribe,
tracker.getState,
tracker.getState
)
const mountedRef = React.useRef<any>(false)
// @ts-ignore
const state = cb ? cb(snapshot.state) : snapshot.state
const trackStateTree = (
overmind as any
).proxyStateTreeInstance.getTrackStateTree()
const state = cb ? cb(trackStateTree.state) : trackStateTree.state

tracker.track()
trackStateTree.track()

if (IS_PRODUCTION) {
React.useLayoutEffect(() => {
tracker.stopTracking()
}, [tracker])
React.useEffect(
() =>
trackStateTree.subscribe((_, __, flushId) => {
forceRerender(flushId)
}),
[]
)
} else {
const component = useCurrentComponent()
const name = getDisplayName(component)
Expand All @@ -170,135 +163,29 @@ const useStateV18 = <Context extends IContext<{ state: {} }>>(
currentComponentInstanceId++
)

React.useLayoutEffect(() => {
mountedRef.current = true
overmind.eventHub.emitAsync(EventType.COMPONENT_ADD, {
componentId: component.__componentId,
componentInstanceId,
name,
paths: Array.from(tracker.tree.pathDependencies) as any,
})

return () => {
mountedRef.current = false
overmind.eventHub.emitAsync(EventType.COMPONENT_REMOVE, {
componentId: component.__componentId,
componentInstanceId,
name,
})
}
}, [])

React.useLayoutEffect(() => {
tracker.stopTracking()

React.useEffect(() => {
overmind.eventHub.emitAsync(EventType.COMPONENT_UPDATE, {
componentId: component.__componentId,
componentInstanceId,
name,
flushId: 0,
paths: Array.from(tracker.tree.pathDependencies) as any,
paths: Array.from(trackStateTree.pathDependencies) as any,
})
}, [tracker])
}

return state
}

const useState = <Context extends IContext<{ state: {} }>>(
cb?: (state: Context['state']) => any
): Context['state'] => {
const overmind = React.useContext(context) as Overmind<any>

if (!(overmind as any).mode) {
throwMissingContextError()
}

if (isNode || (overmind as any).mode.mode === MODE_SSR) {
return overmind.state
}

const mountedRef = React.useRef<any>(false)
const { flushId, forceRerender } = useForceRerender()
const tree = React.useMemo(
() => (overmind as any).proxyStateTreeInstance.getTrackStateTree(),
[flushId]
)

const state = cb ? cb(tree.state) : tree.state

if (IS_PRODUCTION) {
React.useLayoutEffect(() => {
mountedRef.current = true
tree.stopTracking()

return () => {
tree.dispose()
}
}, [tree])

tree.track((_, __, flushId) => {
if (!mountedRef.current) {
// This one is not dealt with by the useLayoutEffect
tree.dispose()
return
}
forceRerender(flushId)
})
} else {
const component = useCurrentComponent()
const name = getDisplayName(component)
component.__componentId =
typeof component.__componentId === 'undefined'
? nextComponentId++
: component.__componentId

const { current: componentInstanceId } = React.useRef<any>(
currentComponentInstanceId++
)

React.useLayoutEffect(() => {
mountedRef.current = true
overmind.eventHub.emitAsync(EventType.COMPONENT_ADD, {
componentId: component.__componentId,
componentInstanceId,
name,
paths: Array.from(tree.pathDependencies) as any,
const dispose = trackStateTree.subscribe((_, __, flushId) => {
forceRerender(flushId)
})

return () => {
mountedRef.current = false
overmind.eventHub.emitAsync(EventType.COMPONENT_REMOVE, {
overmind.eventHub.emitAsync(EventType.COMPONENT_UPDATE, {
componentId: component.__componentId,
componentInstanceId,
name,
paths: [],
})
}
}, [])

React.useLayoutEffect(() => {
tree.stopTracking()

overmind.eventHub.emitAsync(EventType.COMPONENT_UPDATE, {
componentId: component.__componentId,
componentInstanceId,
name,
flushId,
paths: Array.from(tree.pathDependencies) as any,
})

return () => {
tree.dispose()
dispose()
}
}, [tree])
tree.track((_, __, flushId) => {
if (!mountedRef.current) {
// This one is not dealt with by the useLayoutEffect
tree.dispose()
return
}
forceRerender(flushId)
})
}, [])
}

return state
Expand Down Expand Up @@ -349,7 +236,7 @@ export const createStateHook: <
Context extends IContext<{ state: {} }>,
>() => StateHook<Context> = () =>
// eslint-disable-next-line dot-notation
typeof React['useSyncExternalStore'] === 'function' ? useStateV18 : useState
useState

export const createActionsHook: <
Context extends IContext<{ actions: {} }>,
Expand Down
17 changes: 6 additions & 11 deletions packages/overmind/src/Overmind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,21 +894,16 @@ export class Overmind<ThisConfig extends IConfiguration>
} else {
const tree = this.proxyStateTreeInstance.getTrackStateTree()
let returnValue
let disposer
const updateReaction = () => {
tree.trackScope(
() => (returnValue = stateCallback(tree.state as any)),
() => {
updateReaction()
updateCallback(returnValue)
}
)
tree.trackScope(() => (returnValue = stateCallback(tree.state as any)))
disposer = tree.subscribe(() => {
updateReaction()
updateCallback(returnValue)
})
}

updateReaction()

disposer = () => {
tree.dispose()
}
}

if (options.immediate) {
Expand Down
10 changes: 8 additions & 2 deletions packages/overmind/src/operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ export function throttle<T>(ms: number): IOperator<T, T> {
)
}

// waitUntil((state) => state.foo === 'bar')

export function waitUntil<T, C extends IContext<{}>>(
operation: (state: C['state']) => boolean
): IOperator<T, T> {
Expand All @@ -568,13 +570,17 @@ export function waitUntil<T, C extends IContext<{}>>(
if (err) next(err, value)
else {
const tree = context.execution.getTrackStateTree()
let disposer
const test = () => {
disposer?.()

if (operation(tree.state)) {
tree.dispose()
next(null, value)
} else {
disposer = tree.subscribe(test)
}
}
tree.trackScope(test, test)
tree.trackScope(test)
}
}
)
Expand Down
5 changes: 2 additions & 3 deletions packages/proxy-state-tree/src/Proxyfier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ export class Proxifier {
}

if (this.isDefaultProxifier()) {
const trackStateTree = this.tree.root
.currentTree as ITrackStateTree<any>

const trackStateTree = this.tree.root.currentTree as ITrackStateTree<any>
if (!trackStateTree) {
return
}
Expand Down Expand Up @@ -294,6 +292,7 @@ export class Proxifier {
}

const trackingTree = proxifier.getTrackingTree()

const targetValue = target[prop]
const nestedPath = proxifier.concat(path, prop)
const currentTree = trackingTree || proxifier.tree
Expand Down
29 changes: 13 additions & 16 deletions packages/proxy-state-tree/src/TrackStateTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export class TrackStateTree<T extends object> implements ITrackStateTree<T> {
this.state = root.state
}

// Does not seem to be used
/*
trackPaths() {
const paths = new Set<string>()
const listener = (path) => {
Expand All @@ -37,10 +35,11 @@ export class TrackStateTree<T extends object> implements ITrackStateTree<T> {
return paths
}
}
*/

track() {
this.root.changeTrackStateTree(this)

return this
}

canMutate() {
Expand All @@ -57,28 +56,26 @@ export class TrackStateTree<T extends object> implements ITrackStateTree<T> {

subscribe(cb: ITrackCallback) {
this.root.changeTrackStateTree(null)
console.log('Adddig', this.pathDependencies)
for (const path of this.pathDependencies) {
this.root.addPathDependency(path, cb)
}
return () => {
console.log('Removing', this.pathDependencies)
for (const path of this.pathDependencies) {
this.root.removePathDependency(path, cb)
}
}
}

/*
trackScope(scope: ITrackScopedCallback<T>, cb?: ITrackCallback) {
const previousPreviousTree = this.master.previousTree
const previousCurrentTree = this.master.currentTree
this.master.currentTree = this
this.track(cb)
const result = scope(this)
this.master.currentTree = previousCurrentTree
this.master.previousTree = previousPreviousTree
return result
trackScope(scope: ITrackScopedCallback<T>) {
const previousPreviousTree = this.root.previousTree
const previousCurrentTree = this.root.currentTree

this.root.currentTree = this
this.track()
scope(this)
this.root.currentTree = previousCurrentTree
this.root.previousTree = previousPreviousTree

return this
}
*/
}
Loading

0 comments on commit 7a4bfa2

Please sign in to comment.