How to yield put() from inside a callback

Redux-saga put() From Inside a Callback

last updated: May 26th, 2017

When you use Redux-saga for everything (like I do), sooner or later you're going to run into a situation where you've got some external event or nested callback function that you need to integrate with a saga.

You'll quickly find that you can't yield put(action) to your saga from inside a nested function. It's in the wrong function scope.

You also can't put(action) from outside of the saga.

Remember that put() doesn't really do anything by itself. It merely returns a description of your side-effect.

This is very much like how an action creator returns only a description of the action -- it is the reducer that is responsible for doing the real work.

Similarly, it's up to the redux-saga middleware to do the real work of your put() side effect.

The Problem Pattern

function* saga() {    // ...    someObject.callback = function(event) {        yield put({ type: ACTION, event }); // doesn't work    };    // ...}

Can You Refactor?

If you control someObject it may be possible to rewrite it as a generator function and can propagate yielded effects. (i.e. turn it into a saga too)

But sometimes you have no control over the code. For example, callbacks on built-in objects such as window, XMLHttpRequest, etc

You need a different approach.

Redux-Saga Event Channels

Redux-saga has something called an Event Channel that can bridge between the two function scopes.

After creating it, you can pass the channel around and use it to send events back to your saga. Your saga will take() from the channel just as if it were taking actions.

function* saga(someObject) {    const channel = eventChannel(emitter => {        someObject.on('data', function(data) {            // Send the event to our saga           emitter({ data });        });        someObject.on('end', function() {            emitter(END); // Special construct to end the channel        });        // Return an unsubscribe method        return () => {            // Perform any cleanup you need here            someObject.end();        };    });    someObject.begin();    // Process events until operation completes    while (true) {        const { data } = yield take(channel);        if (!data) {            break;        }        // Handle the data...    }}

See a concrete example in the follow-up post,
File Upload Progress with Redux-Saga

Further Reading

Level up Your React + Redux + TypeScript

with articles, tutorials, sample code, and Q&A.