agraboso / redux-api-middleware

Redux middleware for calling an API.
MIT License
1.49k stars 195 forks source link

API requests and server-side rendering #70

Open amangeot opened 8 years ago

amangeot commented 8 years ago

Hello people, I am currently looking at how I could implement server-side rendering and I was wondering how redux-api-middleware would play out.

It is very new to me, but If i understand correctly, one way to achieve it is to use async actions via thunks, e.g. redux-thunk, and wait for these actions to build the redux state before serving it to render the html page.

Is it possible to achieve server-side rendering using redux-api-middleware to fetch data ? How would you do it ?

Thank you,

cevou commented 8 years ago

Hey, this is actually possible. You can have an implementation like it is done in https://github.com/caljrimmer/isomorphic-redux-app

First define a static property for your component where you define all actions that need to be executed before the site is rendered.

export class TestComponent extends Component {
  static need = [
    loadData
  ]

  render() {
    // rendering code
  }
}

Then before you render the page make sure that all promises in need are resolved:

match({routes, location: req.url}, (err, redirect, props) => {
  preRenderMiddleware(
    store.dispatch,
    props.components,
    props.params
  ).then(() => {
    const initialState = store.getState();
    const componentHTML = renderToString(
      <Provider store={store}>
        <RouterContext {...props} />
      </Provider>
    );

    // create html

    res.status(200).send(html);
  })
});

With preRenderMiddleware

export default function preRenderMiddleware(dispatch, components) {
  const needs = components.reduce((prev, current) => {
    if (!current) {
      return prev
    }
    const need = 'need' in current ? current.need : []
    const wrappedNeed = 'WrappedComponent' in current &&
      'need' in current.WrappedComponent ? current.WrappedComponent.need : []
    return prev.concat(need, wrappedNeed)
  }, [])
  const promises = needs.map(need => dispatch(need()))
  return Promise.all(promises)
}
scottnonnenberg commented 8 years ago

@cevou that project doesn't use redux-api-middleware so it's probably not a good example. It uses redux-promise to manage promises in a totally different way from this middlware.

@amangeot based on my analysis, redux-api-middleware never surfaces the promise representing the request made to the API. You can see here that it does an await on the promise, waiting until it is resolved: https://github.com/agraboso/redux-api-middleware/blob/4e349645f594cdcc2a5d6d38efdfad36e6a4c86f/src/middleware.js#L103

And that means you won't be able to wait for the completion of a .dispatch() of your 'call API' action, and therefore server rendering really can't be done. You won't know when the API is complete so you can render to string and return the response to the user. Perhaps time for a fork?

cevou commented 8 years ago

@scottnonnenberg It actually works in my project with the implementation shown above. The referenced project was just an example for the static array.

scottnonnenberg commented 8 years ago

@cevou Ah, thanks. Seems I'm still wrapping my head around async/await. Certainly is confusing when combined with next() callbacks. Looks like it will work, after all, @amangeot, since you can wait for your dispatch() calls to resolve.

joe-lloyd commented 7 years ago

@cevou , how are you able to pass params like url to the action in the need array?

joe-lloyd commented 7 years ago

Found it, you need to pass params to the need aswell

/**
 * @description
 * This function checks if a component has the static need array.
 * If it does it will make sure all of the actions listed there are fired before the page is rendered.
 *
 * @param dispatch
 * @param components
 * @param params
 * @returns {*}
 */
preRenderMiddleware(dispatch, components, params) {
    const needs = components.reduce((prev, current) => {
        if (!current) {
            return prev
        }
        const need = 'need' in current ? current.need : [];
        const wrappedNeed = 'WrappedComponent' in current &&
        'need' in current.WrappedComponent ? current.WrappedComponent.need : [];
        return prev.concat(need, wrappedNeed)
    }, []);
    const promises = needs.map(need => dispatch(need(params)));
    return Promise.all(promises)
}
kshreve commented 7 years ago

Anyone able use redux-api-middleware with SSR? Have an example project?