Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

selective subscribe and state await #19

Open
drcmda opened this issue Dec 7, 2017 · 3 comments
Open

selective subscribe and state await #19

drcmda opened this issue Dec 7, 2017 · 3 comments

Comments

@drcmda
Copy link

drcmda commented Dec 7, 2017

I would like to suggest adding these. They are very common use cases and for each and every redux-like lib i use i usually end up having to write it again. But these aren't edge-cases. To get a ping when a certain slice of state changes is something one would use all the time. Basically, if connect can do it, why can't the user?

The other use case would be being able to await state without having to collect and manage unsubscribes.

function subscribe(selector, callback) {
    let state = selector(store.getState())
    return store.subscribe(() => {
        let selected = selector(store.getState()),
            old = state
        if (typeof selected === 'object' && !Array.isArray(selected)) {
            selected = Object.entries(selected).reduce(
                (acc, [key, value]) =>
                    (state[key] !== value ? { ...acc, [key]: value } : acc),
                state,
            )
        }
        if (state !== selected) callback((state = selected), old)
    })
}

function awaitState(selector, condition) {
    return new Promise(resolve => {
        const unsub = subscribe(selector, props => {
            const result = condition(props)
            if (result) unsub() || resolve(result)
        })
    })
}


// Example #1: await a truthy slice of state
const worker = await awaitState(state => 
    state.collections.workers, workers => workers.find(ID))

// Example #2: responding to state changes
const unsub = subscribe(state => 
    state.workers[ID].alive, () => console.log("state changed"))

// Example #3: responding to mapped state changes
const unsub = subscribe(state => 
    ({ alive: state.workers[ID].alive, running: state.globals.running }),
    ({ alive, running }) => console.log(alive, running)
)
@developit
Copy link
Owner

developit commented Dec 7, 2017

Neat! I'm open to adding this if it's plausible without much of a size increase. One issue I can see is that the subscriptions array needs to include the slice. Also, this only works if state is treated as immutable*, which isn't true for the rest of the library.

* unless we only allow selectors at the root like they are in your implementation. Maybe that's the key here.

In your example, it seem like the predicate could just as easily pluck a property off state rather than relying on a selector. Maybe a half-solution here would be to expose selector()?

@drcmda
Copy link
Author

drcmda commented Dec 7, 2017

@developit Yes, it would be able to pluck a property or a keyed mapStateToProps object. mstp goes through a reducer, the prop doesn't need that. Both cases would be tested with reference equality. But either way, i'd use whatever unistore offers. No big deal if direct props wouldn't work.

The other point about the subscription-array, i don't follow. The subscribe simply drops a scope bound state and checks against it. It doesn't need to expose the slice externally. Doesn't connect work in a similar way?

As for size, given that it probably would be feasible to re-use connects subscriber, it would only add a couple of bytes. But then go a long way in non-view related state management.

EDIT:
I added a third example for mstp. This would also work for awaitState.

@developit
Copy link
Owner

If there is a way to extra & reuse the logic from connect so the size stays down I'd be up for it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
2 participants