Reputation: 1430
In my database I have an array of animals that I would like to render into a nice little list. To improve the user experience, I would like to render it on the server (using the new server-render
package) and then subscribe to any changes using react-meteor-data
(withTracker
).
Right now, this is working except for one thing. The server renders the content as expected (including the data), which is then sent to the client. The problem is on the client.
Once the page loads, meteor sets up the data connection, then renders the page. This first rendering occurs before the data connection has returned any data, so it renders an empty list of animals (overwriting the list rendered on the server and causing a warning). Then once data arrives the list is fully (re-)rendered.
This leads to a pretty bad user experience as the list blinks out and then returns. I would like to postpone the client-rendering until the data is available. Is this possible?
My code is really simple and looks like this:
List Component:
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { AnimalsData } from '../api/animals';
class Animals extends Component {
render() {
const {animals} = this.props;
console.log(animals);
return <ul>
{animals.map(animal =>
<li key={animal._id}>
{animal.name}
</li>)
}
</ul>
}
};
// Load data into props, subscribe to changes on the client
export default withTracker(params => {
if (Meteor.isClient) {
// No need to subscribe on server (this would cause an error)
Meteor.subscribe('animals');
}
return {
animals: AnimalsData.find({}).fetch()
};
})(Animals);
Server:
import React from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";
import Animals from '../imports/ui/Animals';
import '../imports/api/animals';
onPageLoad((sink) => {
sink.renderIntoElementById('app', renderToString(<Animals />));
});
Client:
import React from 'react';
import ReactDOM from "react-dom";
import { onPageLoad } from "meteor/server-render";
import AnimalList from '../imports/ui/Animals';
onPageLoad(sink => {
ReactDOM.hydrate(
<AnimalList />,
document.getElementById("app")
);
});
Database:
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
export const AnimalsData = new Mongo.Collection('animals');
if (Meteor.isServer) {
Meteor.publish('animals', () => {
return AnimalsData.find({});
});
}
What happens (console.log in Animals.jsx):
Upvotes: 3
Views: 794
Reputation: 3839
You can delaying hydrating your page until your subscription is ready.
For example, lets say you have a collection of links
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
export default Links = new Mongo.Collection('links');
if(Meteor.isServer) {
Meteor.publish('links', () => {
return Links.find({});
});
}
In client/main.js
, you would subscribe to the publication, and wait for it to be ready before continuing with your hydration. You can do that with meteor/tracker
, since ready()
is an observable.
import React from 'react';
import ReactDOM from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { onPageLoad } from "meteor/server-render";
import App from '../imports/ui/entry_points/ClientEntryPoint';
import { Tracker } from 'meteor/tracker';
onPageLoad(async sink => {
Tracker.autorun(computation => {
if(Meteor.subscribe('links').ready()) {
ReactDOM.hydrate(
<App />,
document.getElementById("react-target")
);
computation.stop();
}
})
});
Obviously that requires subscribing to everything in the entire app, but you could add additional logic to subscribe to different things depending on the routes.
Upvotes: 1
Reputation: 2969
I have created package for your that prevents components rerender at page load.
Check it here https://github.com/pravdomil/Meteor-React-SSR-and-CSR-with-loadable-and-subscriptions.
Upvotes: 0
Reputation: 747
You can use .ready()
for it not to render when the subscription isn't ready yet, example:
const animalSub = Meteor.subscribe('animals')
if (animalSub.ready()) {
return AnimalsData.find().fetch()
}
Upvotes: -1