Fetching data before rendering server side

4.5k views Asked by At

Right now I'm discovering Este.js and I have a little issue with isomorphic apps. I don't understand how to make api call before rendering server side with renderToString().

One solution consists in doing all the data fetching at the router level using React Router. Depending on the top level route, I can predict which data will be needed, make the api call, and then call React.renderToString.

Great, but I still have to declare the data dependencies at the component level AND in the router level. I end up writing the same code twice, and I don't believe it's the best way to do it.

EDIT : Ok, for now I'm able to do somewhat what I want. Using React-Router and this link I've been able to made the following :

Giving this global app state, I want to prefetch todos when pointing /todos

initialstate.js

{
  auth: {
    data: null,
    form: null
  },
  examples: {
    editable: {
      state: null,
      text: 'Some inline-editable text.'
    }
  },
  i18n: {
    formats: {},
    locales: initialLocale,
    messages: messages[initialLocale]
  },
  pendingActions: {},
  todos: {
    editables: {},
    newTodo: {
      title: ''
    },
    list: [{
      id: 1,
      title: 'first todo yipiyo'
    }]
  },
  users: {
    viewer: null
  }
}

todos.react.js

In todo component I declare a static function fetchData. Because I want to retrieve the correct key in my appState, I pass 'list' as a param. Feels dirty.

class Todos extends Component {

  static fetchData(){
    return actions.loadAllTodos('list');
  }

  componentDidMount(){
    Todos.fetchData();
  }

  render() {
    ...
  }

}

actions.js

Api call and stuff, I pass the key to the promise - Feels hacky

export function loadAllTodos(key) {

  const promise = new Promise((resolve, reject) => {

    Api.get()
    .then(res => {
      res.key = key; //hacky time
      resolve(res)
    })
    .catch(err => {
      reject(err);
    })

  });

  return dispatch(loadAllTodos, promise);

}

render.js

router.run((Handler, routerState) => {

  var promise = Promise.all(routerState.routes
        .filter(route => route.handler.fetchData)
        .map(route => {
          return route.handler.fetchData();
        })
      );

  promise.then(resp => {

    console.log(resp);

    //Displays : 
    [ { id: 2, title: 'Im a second todo' },{ id: 3, title: 'I like todo' },
    cursor: 'list' ]

    //Some stuff to add resp to appState, using the correct key, yey iso+api=win
    appState = mergeThisWithMagic(appState, resp);

    const html = preloadAppStateThenRenderHtml(Handler, appState);
    const notFound = routerState.routes.some(route => route.name ===
      'not-found');
    const status = notFound ? 404 : 200;
    res.status(status).send(html);
    resolve();

  });


});

As you can see, I'll create a function to update appState with the updated todoList.

Is this ok to do all of this ? I would like to have some feedback please, because I feel like I'm going in a dark path :(.

1

There are 1 answers

2
BradByte On BEST ANSWER

I accomplished this in my isomorphic server side app by putting my fetchData functions in the statics object of the component(s) and using promises that wait until all the data is returned before rendering the app to string.

Then you would pass the returned data down to the rendered component via props. This example was instrumental in my development of this app. React Router Mega Demo.