To those using Meteor SSR, what are you using to fetch your async data from your Meteor methods e.g. data from DB?
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' });
}
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);
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}
}
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
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:
- (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) - 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)
- 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.
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.