0.11.0 • Published 6 years ago

feedbacks v0.11.0

Weekly downloads
1
License
MIT
Repository
github
Last release
6 years ago

Feedbacks - Redux extension for pattern-matching and side-effects

Feedbacks:

  • matches actions automatically (powerful DSL with pattern matching capabilities)
  • allows for working on individual properties / slices of state
  • resolves promises and observables and feeds it back to given property in the state
  • enables to return declarative effects from reducers
    • allows you to handle custom effects in effect handlers (onEffect)
    • provides you with some standard effects (like fx.waitFor for waiting for specific actions)

No more wiring manually your actions, reducers, thunks etc.

Just create a "blueprint" which will define a shape of state and define how it should react on incoming actions (pattern-matching: action -> reducer).

You can hook into individual properties even deep in the state (and wire up a property to the reducer via pattern-matching mechanism)

Reducers in Feedbacks can return both normal values and effects. And effects represent future value(s) of property.

Observables and promises are auto-resolved, and owner property is auto-updated.

feedbacks-resolving.png

Blueprints

What are blueprints? Well, if you use Redux you probably are familiar with concept of "initial state" i.e. object which represents how the store state will look in the beginning. Blueprints take this idea further and represent not only initial state, but also all future states of your application. This means you can match specific actions (read more about matching actions) into specific properties of the state and associate them with specific reducer.

This effectively would slice your state into individual properties with own property matchers and own reducers. Something similar to nested combineReducers but with pattern-matching.

example of blueprint:

const blueprint = {
    foo: {
        bar: init(10) // initial state of property foo.bar
            .on({type: 'increment'} /*pattern matching */ , (state, action) => {
                return state + 1;
            })
            .on({type: 'decrement'}, (state, action) => {
                return state - 1;
            })
    },
}

Reducers

In previous example reducers returned just a plain value but they may return various effects as well:

const promiseReducer = (state, action) => () => Promise.resolve('future value');

const rxCounter = (state, action) => Rx.interval(1000);

const generatorCounter = (state, action) => function *() {
    let counter = 0;
    while (true) { 
        yield fx.delay(1000); 
        yield fx.next(counter++)
    }
}

Reducers stay pure because they return only description of "what should happen" and not really change anything.

API

Click this link to read more detailed Feedbacks API guide.

Online examples

If you have a problem feel free to create an issue: https://github.com/hex13/feedbacks/issues

Power of declarativeness

Traditional Redux:

You first think about actions, when to dispatch them (e.g. in thunks), how reducer should change state upon this action:

"I will spawn some side-effects (e.g. API call) then I will dispatch FOO action. I will write reducer which will react to FOO action by changing property foo in store.

So basically half of your code is imperative/procedural, only second half (in reducers) is declarative/functional. This raises some problems (for example boilerplate in your imperative thunks, or proliferation of helper actions which will act only as DTO between imperative side of your app (e.g. thunks) and declarative reducers).

Feedbacks:

You first think about shape of your state how the state will change because of actions:

"I have foo property in my store and I can describe on which actions foo will react and how its value will be changing"

Changes can be immediate, e.g.

(value, action) => value + action.amount
(value, action) => 42

or deferred e.g.

(value, action) => () => Promise.resolve(42)
(value, action) => Rx.interval(1000)
(value, action) => () => somePromiseBasedAPI()

This allows you for conciseness (especially that Feedbacks comes with the nice DSL).

Pattern Matching

Feedbacks allow you also for making some advanced pattern matching. You've seen string based patterns in examples above. But this is not the end. Look something like this:

// ...
{
    foo: init(0)
        .on({
            type: 'response',
            payload: {
                status: value => value >= 400 && value < 500
            }
        }, (value, action) => action.payload.content)
}
// ...

Philosophy of side-effects

Feedbacks address the fact so called "side-effects" are often merely a way to get some data and put it back in some property of the Redux state. Consider this code, written traditionally:

const fetchTodos = () => {
    return dispatch => {
        someAsyncApi().then(data => {
            dispatch({type: 'TODOS_FETCHED', todos: data})
        })
    }
};

const reducer = (state, action) => {
    switch (action.type) {
        case 'TODOS_FETCHED':
            return {
                ...state,
                todos: action.todos
            }
    }
}

There is a problem with that.

There is a ton of indirection and logic is put all over the project (in the example above we have half of logic in thunk - when we fetch data, the other half in reducer when we apply data to the given property in the state).

In Feedbacks library you are encouraged to write more direct code. Let's rewrite previous example to Feedbacks:

{
    todos: on('FETCH_TODOS', (state, action) => () => someAsyncApi())
}

this way in one line of code you express: 1. what should be target property of state change (todos) 2. which action you want to handle (FETCH_TODOS but you're not limited to just matching by type. Read more about advanced pattern matching) 3. how value will change including asynchronous changes via observables or function-wrapped promises. You could also spawn another action (and reducer of the next action could send values back to previous property by using yield statement). TODO: example

Dev tools

Same as Redux Dev Tools but use @hex13/redux-devtools-log-monitor instead of default LogMonitor. Installation process: https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md Here you have practical example of wiring up Redux Dev Tools and Feedbacks:

https://github.com/hex13/feedbacks/blob/8ae44414f3ecbb8cd0c75ac4f927c8f186159071/examples/calendar/src/store.js#L67

Tips & Tricks

Changing more than one property during action:

You can do it in either way:

  1. match on all properties you want to change:
{
    user: {
        name: init('')
            .on('changeUser', (value, action) => action.payload.name),
        city: init('')
            .on('changeUser', (value, action) => action.payload.city),
    },
}
  1. match action at the upper property:
{
    user: init({name: '', city: ''})
        .on('changeUser', (value, action) => action.payload)
}

Whatever will make more sense to what you want to achieve.

Integration with other libraries

React

You can use react-redux (Official React bindings for Redux)

Rx.js

No need for special preparations. Just import Rx.js as usual and at this point you can use it with Feedbacks. For example you can return Observables from reducers.

But Feedbacks is not even correct word...

Maybe. But who cares?

And this is Doge: https://en.wikipedia.org/wiki/Doge_(meme)

Additional reading

Redux documentation: https://redux.js.org/

0.11.0

6 years ago

0.10.0

6 years ago

0.9.0

6 years ago

0.8.1

6 years ago

0.8.0

6 years ago

0.7.0

6 years ago

0.6.1

6 years ago

0.6.0

6 years ago

0.5.0

6 years ago

0.4.0

6 years ago

0.3.0

6 years ago

0.2.3

6 years ago

0.2.2

6 years ago

0.2.1

6 years ago

0.2.0

6 years ago

0.1.10

6 years ago

0.1.9

6 years ago

0.1.8

6 years ago

0.1.7

6 years ago

0.1.6

6 years ago

0.1.5

6 years ago

0.1.4

6 years ago

0.1.3

6 years ago

0.1.2

6 years ago

0.1.1

6 years ago

0.1.0

6 years ago