cusX
cusX

Reputation: 500

Using redux-saga and redial Server Side

Right now, I am trying to get the initial state of my application server side through Redial.

Redial triggers a pure object action, and redux-saga listens/awaits for that action, and then launches the async request.

But the problem is, Redial has no promises to resolve to when redux-saga is completed because it is dispatching a pure object.

Component

const redial = {
   fetch: ({ dispatch }) => dispatch({ type: actionTypes.FETCH_START }),
};

export default class PostList extends Component {
    render() {
        const { posts } = this.props;
        return (
            <div>
                {posts.map(post => <ListItem key={post.id} post={post} />)}
            </div>
        );
    }
}

PostList.propTypes = {
    posts: PropTypes.array.isRequired,
};

export default provideHooks(redial)(connect(mapStateToProps)(PostList));

Saga

export function *fetch() {
    try {
        yield put({ type: actionTypes.FETCH_START });
        const response = yield call(fakeData);
        yield put({ type: actionTypes.FETCH_SUCCESS, data: response.data });
        yield put({ type: actionTypes.FETCH_PENDING });
    } catch (e) {
        yield put({ type: actionTypes.FETCH_FAIL });
    }
}

export default function *loadPost() {
    yield * takeLatest(actionTypes.FETCH_START, fetch);
}

export default function *rootSaga() {
    yield [
        fork(loadPost),
    ];
}

Is there a way to connect redial to redux-saga ?

Upvotes: 1

Views: 821

Answers (2)

Johannes Lumpe
Johannes Lumpe

Reputation: 1762

There is a rather elegant way of doing this. First of all you need to create a registry for your saga tasks (remember that running the middleware's .run method returns a task descriptor):

export default class SagaTaskRegistry {
  constructor() {
    this._taskPromises = [];
  }

  addTask(task) {
    if (!this._taskPromises) {
      this._taskPromises = [];
    }
    this._taskPromises.push(task.done);
  }

  getPromise() {
    return new Promise((resolve) => {
      const promises = this._taskPromises;
      if (!promises) {
        resolve();
        return;
      }
      this._taskPromises = undefined;
      Promise.all(promises).then(resolve).catch(resolve);
    }).then(() => {
      const promises = this._taskPromises;
      if (promises) {
        return this.getPromise();
      }
      return undefined;
    });
  }
}

When you add new tasks to the saga middleware using .run, you will then call registryInstance.add(taskDescriptor). The SagaTaskRegistry will grab the promise for that task and add it to an array.

By calling getPromise, you will receive a promise which will resolve when all added tasks are finished. It will never be rejected, as you most likely wouldn't want failed fetches to result in a rejection - you still want to render your application with the error state.

And this is how you can combine it with redial:

import createSagaMiddleware from 'redux-saga';
import { applyMiddleware, createStore } from 'redux';
import rootReducer from 'your/root/reducer';
import yourSaga from 'your/saga';

const sagaMiddleware = createSagaMiddleware();
const middleWare = [sagaMiddleware];
const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
const store = createStoreWithMiddleware(rootReducer);
const sagaTaskRegistry = new SagaTaskRegistry();
const sagaTask = sagaMiddleware.run(yourSaga);
sagaTaskRegistry.addTask(sagaTask);

match({ routes, history }, (error, redirectLocation, renderProps) => {
  const locals = {
    path: renderProps.location.pathname,
    query: renderProps.location.query,
    params: renderProps.params,
    dispatch: store.dispatch,
  };

  trigger('fetch', components, locals);

  // Dispatching `END` will force watcher-sagas to terminate,
  // which is required for the task promises to resolve.
  // Without this the server would never render anything.
  // import this from the `redux-saga` package
  store.dispatch(END);

  // The `SagaTaskRegistry` keeps track of the promises we have to resolve
  // before we can render
  sagaTaskRegistry.getPromise().then(...)
});

A component can now be decorated with a simple hook:

const hooks = {
  fetch: ({ dispatch }) => {
    dispatch(yourAction());
  },
};

From here on out you can just use sagas as usual. This should give you the ability to do what you are trying. You can further abstract this to allow for dynamic registration of sagas across code-split chunks and other things. The task registry already works for these use-cases by checking for newly registered tasks since the last call to getPromise before actually resolving the promise.

Upvotes: 0

Shawn Hu
Shawn Hu

Reputation: 11

I think it can be done in this way:

firstly, you need to add store in locals. (codes are taken from redial README)

const locals = {
  path: renderProps.location.pathname,
  query: renderProps.location.query,
  params: renderProps.params,

  // Allow lifecycle hooks to dispatch Redux actions:
  dispatch,
  store
};

Then you can create a Promise manually like this:

const redial = {
   fetch: ({ store, dispatch }) => {
       return new Promise((resolve, reject) => {
           const unsubscribe = store.subscribe(()=>{
               if (store.getState()...) { // monitor store state changing by your saga
                   resolve(...) //you probably dont need any result since your container can read them from store directly
                   unsubscribe();
               }
               if (store.getState()....error) {
                   reject(...)
                   unsubscribe();
               }
           });
           dispatch({ type: actionTypes.FETCH_START }),
       }
   }
};

Those codes are just for demonstration, don't use them in production without proper testing.

I think there might be a more elegant way to monitor saga execution results than checking redux store state over and over until the state matches those if(...) statements, maybe you can run saga with redux store and external listeners, then those redial hooks wont need to know about your store structure.

Upvotes: 1

Related Questions