FormidableLabs / freactal

Clean and robust state management for React and React-like libs.
MIT License
1.65k stars 46 forks source link

How does this compare to redux-saga? #18

Closed onbjerg closed 4 years ago

onbjerg commented 7 years ago

The README states that it could effectively replace redux and redux-saga. The main reason (for me at least) to use redux-saga over redux-thunk is testability.

The testing section is a bit sparse on testing side effects (in the form of e.g. API calls). Could you provide some more examples?

divmain commented 7 years ago

Is there anything in particular you'd like to see? Could you give me a minimal saga example that you'd like to see duplicated in Freactal? That'd give me an awesome place to start.

Also, do you envision this would be part of the guide, or live somewhere else? I didn't want to pack too many examples into the guide, but if you feel it leaves you with questions I definitely want to get that addressed.

Thanks!

eliseumds commented 7 years ago

@divmain I believe a good starting point would be to implement this Friend List. It's interesting because it packs debouncing and cancellation, which are extremely easy to handle with redux-saga.

onbjerg commented 7 years ago

I think I'd just like to compare the testability of freactal and redux-saga. Redux Saga is extremely test friendly, because it's basically just objects passed to a middleware - you know, so you just check if the objects match in order etc. - whereas this seems like it's promises, more a lá redux-thunk?

Also, I think @eliseumds's suggestion is good, too.

divmain commented 7 years ago

@onbjerg, did you read through the testing section of the guide? If there are questions it doesn't answer, I'd love to make improvements.

It might be worth it to implement the Friend List application or something similar, and include a full test suite.

rmoorman commented 7 years ago

@divmain it surely could be a good thing to implement an example along the lines of the friend list to showcase how debouncing and cancellation would work (was thinking the same as @eliseumds apparently :-)). Maybe some other more involved examples from redux-saga (e.g. found in the recipes) could also be worthwhile implementing as freactal promises to be able to replace it.

djhi commented 7 years ago

I was missing the redux-saga way of testing a workflow too. By workflow, I mean a sequence of steps in a specific order.

Solved it by using marmelab/sg (I work at Marmelab). I don't have to test that freactal work as expected anymore, only that my effects follow the sequence of actions I want.

Example:

// In provideConfigState.js
import { provideState, softUpdate } from 'freactal';
import sg from 'sg.js';
import call from 'sg.js/dist/effects/call';
import fetchConfig from './fetchConfig';

export const getConfigSaga = function*(effects, environment) {
    yield call(effects.setLoading, true);
    const config = yield call(fetchConfig, environment);
    yield call(effects.setLoading, false);
    const state = yield call(effects.setConfig, config);
    return state;
};

export const effects = {
    setLoading: softUpdate((state, loading) => ({ loading })),
    setConfig: softUpdate((state, config) => ({ config })),
    getConfig: sg(getConfigSaga),
};

export const state = {
    initialState: ({ environment }) => ({
        config: null,
        environment,
        loading: false,
    }),
    effects,
};

export default provideState(state);

And the tests:

// In provideConfigState.test.js
import expect from 'expect';
import call from 'sg.js/dist/effects/call';
import { getConfigSaga, effects, fetchConfig } from './provideConfigState';

describe('getConfigSaga', () => {
    const saga = getConfigSaga(effects, 'development');

    it('sets the loading state to true', () => {
        const effect = saga.next().value;
        expect(effect).toEqual(call(effects.setLoading, true));
    });

    it('fetches the config', () => {
        const effect = saga.next().value;
        expect(effect).toEqual(call(fetchConfig, 'development'));
    });

    it('sets the loading state to false', () => {
        const effect = saga.next('config').value;
        expect(effect).toEqual(call(effects.setLoading, false));
    });

    it('sets the config state to the fetched config', () => {
        const effect = saga.next().value;
        expect(effect).toEqual(call(effects.setConfig, 'config'));
    });
});

Btw, awesome work on freactal, I love it :)

divmain commented 7 years ago

You can also use async functions. There's a couple of examples of that in master now. One of them.

Doing a deep dive comparison is going to take a significant effort. Hopefully I can chip away at the common questions that come up in the near term. I'll leave this open until I'm satisfied there's enough content out there to support the FAQs.