Komo
Komo

Reputation: 2138

Loading component data asynchronously server side with Mobx

I'm having an issue figuring out how to have a react component have an initial state based on asynchronously fetched data. MyComponent fetches data from an API and sets its internal data property through a Mobx action.

Client side, componentDidMount gets called and data is fetched then set and is properly rendered.

import React from 'react';
import { observer } from 'mobx-react';
import { observable, runInAction } from 'mobx';

@observer
export default class MyComponent extends React.Component {
     @observable data = [];

     async fetchData () {
         loadData()
           .then(results => {
               runInAction( () => {
                   this.data = results;
               });
           });
     }

     componentDidMount () {
         this.fetchData();
     }

     render () {
        // Render this.data
     }
}

I understand that on the server, componentDidMount is not called.

I have something like this for my server:

import React from 'react';
import { renderToString } from 'react-dom/server';
import { useStaticRendering } from 'mobx-react';
import { match, RouterContext } from 'react-router';
import { renderStatic } from 'glamor/server'
import routes from './shared/routes';

useStaticRendering(true);

app.get('*', (req, res) => {
    match({ routes: routes, location: req.url }, (err, redirect, props) => {
        if (err) {
            console.log('Error', err);
            res.status(500).send(err);
        }

        else if (redirect) {
            res.redirect(302, redirect.pathname + redirect.search);
        }

        else if (props) {
            const { html, css, ids } = renderStatic(() => renderToString(<RouterContext { ...props }/>));

            res.render('../build/index', {
                html,
                css
            });
        }

        else {
            res.status(404).send('Not found');
        }
    })
})

I have seen many posts where an initial store is computed and passed through a Provider component. My components are rendered, but their state is not initialized. I do not want to persist this data in a store and want it to be locally scope to a component. How can it be done ?

Upvotes: 1

Views: 3572

Answers (1)

mweststrate
mweststrate

Reputation: 4978

For server side rendering you need to fetch your data first, then render. Components don't have a lifecycle during SSR, there are just render to a string once, but cannot respond to any future change.

Since your datafetch method is async, it means that it cannot ever affect the output, since the component will already have been written. So the answer is to fetch data first, then mount and render components, without using any async mechanism (promises, async etc) in between. I think separating UI and data fetch logic is a good practice for many reasons (SSR, Routing, Testing), see this blog.

Another approach is to create the component tree, but wait with serializing until all your promises have settled. That is the approach that for example mobx-server-wait uses.

Upvotes: 4

Related Questions