Meteor SSR React - how do you fetch your data?

To those using Meteor SSR, what are you using to fetch your async data from your Meteor methods e.g. data from DB?

1 Like

Your code have to decide what to do.

if (Meteor.isClient) {
  Meteor.call('some.method', { some: 'data' }, (error, result) => {
    // working with result
  });
} else {
  const result = Meteor.call('some.method', { some: 'data' });
}
1 Like

@minhna, are you using React? Are you calling the methods from the constructor for this case?

If you are using Meteor’s pub/sub system, you can use react-meteor-data.

import { withTracker } from 'meteor/react-meteor-data';

// React component
function Foo({ currentUser, listLoading, tasks }) {
  // ...
}

export default withTracker(props => {
  // Do all your reactive data access in this method.
  // Note that this subscription will get cleaned up when your component is unmounted
  const handle = Meteor.subscribe('todoList', props.id);

  return {
    currentUser: Meteor.user(),
    listLoading: !handle.ready(),
    tasks: Tasks.find({ listId: props.id }).fetch(),
  };
})(Foo);

@captainn, yep, using withTracker(). Saw your posts regarding SSR. Are you able to make withTracker() work with your SSR?

withTracker works fine with SSR.
Yes, to call the method, you need to call it in constructor, because on the server side, componentDidMount won’t work.

Yes, withTracker works by default in SSR, but you have to detect if you are on the server, and skip the subscribe part (unless you have another solution in place for that, like fast-render).

Example of using withTracker

export default withTracker(() => {
  const returnObj = {
    loading: true,
    data: null,
  };

  let sub;
  if (Meteor.isClient) {
    sub = Meteor.subscribe('somePublication', { some: 'data' });
  }
  if ((sub && sub.ready()) || Meteor.isServer) {
    returnObj.loading = false;
    returnObj.data = SomeCollection.findOne({ some: 'condition' });
  }

  return returnObj;
})(SomeComponent);
4 Likes

These are very useful. Thanks @minhna and @captainn.

Although the constructor calls don’t look elegant, I guess there is no way around it.

in react component, you create a function to fetch data. In constructor, if it’s server, call that function.
in componentDidMount (only works on client), call that function.

maybe you could use useEffect, i guess that is called on component mount also on server (but i might be wrong)

EDIT: maybe something like (untested):

const useMethodCall = (method, args, rerenderDeps = []) => {
    const [error, setError] = useState();
    const [data, setData] = useState();
    const [loading, setLoading] = useState(false)

    useEffect(() => {
        setLoading(true);
        Meteor.call(method, args, (error, result) => {
            setLoading(false);
            setError(error);
            setData(result);
        })
    }, rerenderDeps);

    return {error, data, loading}
}

btw. this mimicks the api of https://github.com/trojanowski/react-apollo-hooks

Worth a try. I remember reading that hooks work in the server. Although the async method might not as nothing will suspend the method call before the data comes in. So that part still needs the sync version in server

the async method should work just fine, no supspending needed. There is a loading state in my example. This will be true while data is loading and false when data should be there.

Have you tried server-side rendering? Without suspend or a prepass, how can renderToString() or renderToNodeStream() get the results from the async method?

aah yeah, of course you are right.

there is no suspense support yet on ssr: https://www.reddit.com/r/reactjs/comments/b762ua/data_fetching_with_ssr_with_react_hooks/

but in meteor we have fibers, so we can cheat:

const useMethodCall = (method, args, rerenderDeps = []) => {
    if(Meteor.isServer) {
       let error = null;
       let data = null;
       try {
            // this is async, but thanks to fibers, its sync on the server
           data = Meteor.call(method, args);
       } catch(e) {
           error = e
       }
       return {
         data, 
         error,
         loading: false
       }
    }
    const [error, setError] = useState();
    const [data, setData] = useState();
    const [loading, setLoading] = useState(false)

    useEffect(() => {
        setLoading(true);
        Meteor.call(method, args, (error, result) => {
            setLoading(false);
            setError(error);
            setData(result);
        })
    }, rerenderDeps);

    return {error, data, loading}
}
2 Likes

Yep. Same thing as @minhna mentioned above. Thanks for the idea of using hooks. Will try this later :+1:

yes, similar. i think its always good to remove state- and data-fetching logic from the components, so that you can replace the underlying mechanism.

hooks are a good way to do so

2 Likes

I guess this is a good enough reason to start shifting our projects. Thanks

I just started a new project and needed a boilerplate so I updated an old one I had up to the latest versions of Meteor and NPM packages. It SSRs the entire app without changing the way you write your components. The server will send the HTML with data as the user would see it (including data private to the user).

There are a few caveats tho:

  1. (See Update) The publication main has to return all the data the user needs to see the pages. Should be easy to map routes to a list of collections tho (haven’t done it yet).
  2. There’s a window, between the server sending the HTML and the client fetching the data, where the data the client fetches is different than the one the server fetched. I’m not concerned about this because if it happens React would just reconcile the screen (only happens once on load, and if there’s a conflict)
  3. It uses ViewModel because it’s the sugar I put on top of React. You can just remove the package and use vanilla React components.

Hope it helps.

Update: The boilerplate now subscribes to publications depending on the route.

5 Likes

the hooks also has the same api as https://github.com/trojanowski/react-apollo-hooks, so its very easy to transition to that instead of methods.

in general, i would recommend to go with apollo for new projects.

2 Likes