the following question about the usage of next.js and react to achieve SSR build on top of each other, so I thought I'd write that into a single post. My main question is the third one, but I feel I need to first understand the first two questions in order to get there. So here we go:
1. Am I right that the whole page is always reexecuted from scratch after the client has received it?
Consider this next.js page component:
const Page = () => {
const [state, setState] = useState(getState());
function getState() {
console.log("compute initial state");
return 1;
}
return <>{state}</>;
};
As far as I can tell, getState() is executed both on the server and on the client. If I'd want to exectue that computation only on the server I'd have to do it via getInitialProps()resp. getServersideProps(), right?
2. What value does the prerendered document have for the client if it will be immediately thrown away?
Taking the first question a step further, why is the prerendered document event handed to the client if it will be recalculated from scratch anyway. What advantages does it have for the client to get that initial document? Ok if the case the client can't execute js at all they at least have something. But is that all?
3. Does this mean I unnecessarily have to "double render" some components on the client side?
Let's say parts of my code depend on window and can't be executed on the server. As explained in different articles I found, it can lead to problems (and react warnings) if I rely on checks like typeof window === "undefined" in my code. Instead I think the better way is to execute those functionalities after the first render with a useEffect:
const Page = () => {
const [value, setValue] = useState();
// Effect will be executed after the first render, e.g. never on the server
useEffect(() => {
const value = window.innerWidth; // Some computations or subscriptions that depend on window
setValue(value)
}, []);
return (
<>
{!value && <h1>value pending ...</h1>}
{value && <h1>{value}</h1>}
</>
);
};
Now, with this pattern the app is fine from an SSR perspective. BUT I am introducing additional effort that has do be done on the client side: Even though window is defined on first render, it can only be used on second render.
For this smaller example this doesn't play a big role, but in a larger app, a flickering may be visible because an unnecessary first render is updated some milliseconds later. Furthermore the code becomes harder to read and more error-prone.
The solution I would want here is related to the first question above: Can I somehow avoid the app from starting from scratch but directly start with the second render? Is there some kind of pattern for this that I've missed so far?
Note: Of course I could rewrite the component to simply check if window is defined:
return (
<>
<h1>{window ? window.innerWidth : "value pending ..."}</h1>
</>
);
However this will cause a react warning and cause other problems as described in the article i have liked above. Also see the react docs:
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them.
Thank you a lot for your help!
Am I right that the whole page is always reexecuted from scratch after the client has received it?
Yes and no. The initial html is built on the server and sent to the client as html, and then react is hydrated so that your page becomes interactive. As an example, consider this "page":
export default function MyPage() {
const [greeting, setGreeting] = React.useState("Hello")
React.useEffect(() => {
setGreeting("Goodbye")
}, [])
return (<p>{greeting}</p>)
}
When the page is rendered on the server, it will render as html and send that html to the client. So if you inspect the source code of the page, you'll see:
<p>Hello</p>
Then, on the client, react is hydrated and useEffect runs, so in the browser you'd see a paragraph with the word "Goodbye".
This is true whether you're using SSR (the html is created on demand on the server) or SSG (the html is created at build time into a static html page).
What value does the prerendered document have for the client if it will be immediately thrown away?
The whole document is not thrown away if you see my first point. The value in SSR is if you want the initial greeting text to be something other than "Hello". For example, if you wanted your server to parse an auth token, get the user profile, and seed greeting with "Hello Jim" on page load, you'd prefer SSR. If you don't have any serverside processing prior to sending the html to the client, you could choose SSG.
Consider these two "pages":
// Using SSR
export default function MyPage({customerName}) {
return (<p>{customerName}</p>)
}
// Using SSG
export default function MyPage({customerName}) {
const [greeting, setGreeting] = React.useState("Hello")
React.useEffect(() => {
// Call server to get the customer's name
const name = myApi.get('/name')
setGreeting(`Hello ${name}`)
}, [])
return (<p>{greeting}</p>)
}
In the first example, the server renders the p tag with the customer name (coming from some process on the server) so the html source code will include that customer's name. Nothing is thrown out here.
In the second example, the site is built as html and that source code's p tag says "Hello". When the page is visited, useEffect runs and the p tag is updated when your api responds. So the user will see "Hello" for x microseconds and then it will switch to "Hello Jim".
Does this mean I unnecessarily have to "double render" some components on the client side?
No - you control what you render on the server versus on the client. If you seed a component with data on the server and don't change it on the client, it won't rerender.
In your example, yes - you're double rendering. But you may not need to. As you noted, window doesn't exist on the client. If you absolutely must show your "value pending..." line to your user before you get the window size, then you'll do double rendering - once to populate that string on the server and then once to replace it when react hydrates on the client.
If you don't need to show that pending line and just need to show the value on the client when window actually exists, you could rewrite it like this:
export default function Page() {
const [value, setValue] = React.useState();
React.useEffect(() => {
const newValue = window.innerWidth; // Some computations or subscriptions that depend on window
setValue(newValue)
}, []);
if(value) {
return <h1>{value}</h1>
}
return null
};
In this case, nothing is rendered on the server because there is no value on the server. Only when the client is hydrated will useEffect run, update value, and render your component one time.
Related
I'm tinkering with NextJS' getServerSideProps. I see that when I request a page from scratch, I receive the fully hydrated content. Then when I navigate to a new page, an API call is made, which receives some JSON data that is used to re-populate the page.
What I don't like is that the new API call is actually making two calls. For example my getServerSideProps has an axios.get() call. So on that click to the new page, I'm getting:
a call to something like example.com/_next/data/1231234/....
that call, behind the scenes, must be running my getServerSideProps() with its axios.get() to retrieve the new JSON data.
So is there a way to avoid the double-API call? I'd prefer that after the first page load, clicks to new pages would just skip to step two.
On a non-NextJS app I'd have something like a useEffect() that ran on page load, but obviously then the first run of the page would not return the full content, and for search-engine purposes I'd like to return the full content. I've seen some lectures where Google says they do run javascript and see the full content, but might as well be on the safe side for all other engines.
getServerSideProps will always run at request time--whenever you hit the page (or possibly using prefetch, the default, of next/link) This will result in pre-render of the page using the data from getServerSideProps Side-note: If you using next API middleware, then you can avoid the ajax call and simply import the method to run directly in getServerSideProps.
It sounds like you want to fetch the data at build time and could render the page statically? If so, rather look to use getStaticProps.
You can also avoid both and make an API call in useEffect if you prefer, but code will be run at the client, once the page loads, of course. getServerSideProps will pre-render the page with the data before it renders to the client.
So, the goal is to determine ways of getting props for:
the initial (direct) page request,
in-app navigation request
To solve this we have two options. And unfortunately, both of them are not perfect.
First option:
Check if the request has header x-nextjs-data. NextJS adds this header for fetching data from getServerSideProps:
export const isInitialPageRequest = (req: GsspRequest) => {
return !req.headers['x-nextjs-data'];
}
export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => {
if (isInitialPageRequest(context.req)) {
// this code runs only on the initial request, on the server side
}
return {
props: {
// props
}
}
}
In this case, the request to /_next/data/development/xxx.json?...' is still executed every time. But at least you can control behavior depending on the case (and avoid redundant API calls for example).
Second option:
Use getInitialProps and check if property context.req is defined or not. You already mentioned it in the comments, just added it as an answer option with an example:
page.getInitialProps = async (context: NextPageContext) => {
if (context.req) {
// this code runs only on the initial request, on the server side
}
return {
// props
}
}
The NextJS team is recommending to use getServerSideProps instead
I am new to React, and I'm currently learning about useState and useEffect. I have successfully fetched data from my local api using axios. For development purposes, I'm intentionally changing the data directly from my database, to see if data changes would reflect in my React app. But, whenever I do so, my app breaks. I have included a screenshot of the error.
Here is my code:
function App() {
const[applicant, setApplicant] = useState("");
useEffect(() => {
axios.get("http://localhost:5000/application/1")
.then((res) => {
setApplicant(res.data);
console.log(res.data.personalInfo.firstName);
})
.catch((err) => console.log(err));
}, []);
return (
<div><h1>Welcome {applicant.personalInfo.firstName}</h1></div>
);
}
So here's the weird part (at least for me). On first load, everything works perfectly fine. But when I change first_name field directly in my database, my app breaks. Here's the screenshot:
Obervation 1: if I change the array dependency of my useEffect to always look at changes in applicant state, the error will not occur. But as you might have already guessed, it causes an infinite loop request.
Observation 2: after my app breaks due to changes in database, if I temporarily remove that part in my JSX where I rendered the fetched data in my component: <h1>Welcome {applicant.personalInfo.firstName}</h1>, refresh the page, then paste it back again, and refresh again, the error disappears, and the newly updated first_name renders successfully.
Can someone tell me what's going on here, and mybe a better way of implementing this? Thanks a lot!
Edit: This is a screenshot of my json data:
So I found the problem. Basically, the type of my state is string, but in my setApplicant(), I'm setting its value to the returned json object. So I just changed the default state type to be an empty object, like so:
const[applicant, setApplicant] = useState({});
Then, in my useEffect hook:
setApplicant(res.data.personalInfo); //setting it to personalInfo object
Then, to access its attributes:
<h1>Welcome {applicant.firstName}</h1>
I am beginning to use React (hooks only), and facing a strange issue. I am trying to reproduce the problem in a small test code, but can't get it to happen, except in my full blown app. This leads me to wonder if I'm doing something really wrong.
I have an array of objects, declared as a state. I map this array to display its content. Except that nothing gets displayed (the array is filled, but nothing gets displayed). Now if I declare an un-related state, make it a boolean which flips each time my array gets updated, then my array gets displayed properly. As if, in the render phase itself, React did not detect the array's changes.
A few things:
the array gets updated by a socketIO connection, I simulate it here with a timer
I update my array OUTSIDE of my component function, BUT providing the setter function to the update function
I also create part of the render fields outside my component function (this has no effect, just for readability in my full app)
I essence, this is what I am doing:
const updateArray = (setTestArray, setTestTag, addArray) => {
setTestArray(prevTestArray => {
let newTestArray = prevTestArray.map((data, index) => (data + addArray[index]))
return newTestArray
})
setTestTag(prevTag => {
return (!prevTag)
})
}
const renderArray = (currentTestArray) => {
return currentTestArray.map((data, index) => (
<div>
testArray[{index}]={data}
</div>
))
}
function TestPage(props) {
const [testArray, setTestArray] = useState([])
const [testTag, setTestTag] = useState(false)
useEffect(() => {
let samples = 3
let initArray= []
for (let i=0; i<samples;i++) initArray[i] = Math.random()
setTestArray(initArray)
// In real code: setup socket here...
setInterval(() => {
let addArray= []
for (let i=0; i<samples;i++) addArray[i] = Math.random()
updateArray(setTestArray, setTestTag, addArray)
}, 1000)
return (() => {
// In real code, disconnect socket here...
})
}, []);
return (
<Paper>
Array content:
{renderArray(testArray)}
<br/>
Tag: {(testTag)? 'true' : 'false'}
</Paper>
)
}
This works just fine. But, in my full app, if I comment out everything concerning "testTag", then my array content never displays. testArray's content is as expected, updates just fine, but placing a debugger inside the map section show that array as empty.
Thus my questions:
is my updateArray function a bad idea? From what I read, my prevTestArray input will always reflect the latest state value, and setTestArray is never supposed to change... This is the only way I see to handle the async calls my socket connection generate, without placing "testArray" in my useEffect dependencies (thus avoiding continuously connecting/disconnecting the socket?)
rendering outside the component, in renderArray, doesn't affect my tests (same result if I move the code inside my component), but is there a problem with this?
As a side note, my array's content is actually more complex is the real app (array of objects), I have tried placing this in this test code, it works just fine too...
Thank you!
Edit: Note that moving updateArray inside the useEffect seems to be the recommended pattern. I did that in my full app. The hook linter does not complain about any missing dependency, yet this still doesn't work in my full app.
But the question is still whether what I am doing here is wrong or not: I know it goes against the guidelines as it prevents the linter from doing its job, but it looks to me like this will still work, the previous state being accessible by default in the setter functions.
Edit #2: Shame on me... silly mistake in my real app code, the equivalent of updateArray had a shallow array copy at some place instead of a deep copy.
Why adding the flipping tag made it work is beyond me (knowing the data was then indeed properly displayed and updated), but getting rid of this mistake solved it all.
I will leave this question on, as the question still stand: is placing the state update, and part of the rendering outside the component a functional problem, or just something which might mater on hide dependencies (preventing the react hooks linter from doing its job) and thus simply bad practice?
The fact is that things work just fine now with both functions outside the component function, which makes sense based on what I understand from hooks at this point.
I have been struggling with this issue for quite some time and have failed to find any answers.
I use react-meteor-data to manage my data with react in my meteor application. It is working fine when dealing with data for mongo but I can't make it reactive with props.
Here in App.js, I call my container which I want to be reactive and rerender when the state of App change.
<MyContainer someState={this.state.MyState} />
In MyContainer.js I have a createContainer from react-meteor-data
export default createContainer(params => {
Meteor.subscribe('someCollection');
return {
someCollection: SomeCollection.find({}).fetch(),
stateFromParent: params.someState
};
}, MyContainer);
This worked fine when rendering the component for the first time, MyContainer correctly get MyState.
The thing is, when the MyState from App change, I can see in Chrome Dev React tool that it is indeed updated for the createContainer( ReactMeteorDataComponent has a prop with the right updated state) but the createContainer function is not run, thus the props do not update for MyContainer.
So the props are updated from ReactMeteorDataComponent but not for MyContainer who keeps indefinitely the data. It's like createContainer doesn't consider the update of its prop has a change and thus doesn't run its function.
I really think I'm missing something since that seems pretty basic stuff, thank you for your help.
The OP did not mention how the state was changed, so the original example is incomplete. Therefore, I will try to explain the gist of how the container creation works, in hope that understanding it will be useful.
How does it work?
It uses meteor's Tracker to auto-update the wrapped component when its computation is invalidated (i.e, when one of the reactive data sources, such as reactive variables, subscription handles or fetched MiniMongo cursors, has a new value). To learn more about Tracker, consult the Tracker manual. This is an in-depth resource, and is not necessary to understand how the basics work.
It does so in a way that is different from the way you normally approach reactivity tracking in Meteor, since it also needs to re-run the computation whenever the container's props are changed.
The source code is not very long or complex and can be found on GitHub (currently here).
Tracker.autorun((c) => {
if (c.firstRun) {
//...
data = component.getMeteorData();
} else {
// Stop this computation instead of using the re-run.
// We use a brand-new autorun for each call to getMeteorData
// to capture dependencies on any reactive data sources that
// are accessed. The reason we can't use a single autorun
// for the lifetime of the component is that Tracker only
// re-runs autoruns at flush time, while we need to be able to
// re-call getMeteorData synchronously whenever we want, e.g.
// from componentWillUpdate.
c.stop();
// Calling forceUpdate() triggers componentWillUpdate which
// recalculates getMeteorData() and re-renders the component.
component.forceUpdate();
}
})
Whenever the computation is invalidated (and therefore rerun), it stops the computation and forces a re-render of the container, which will re-create a new computation and have the updated data.
The high-level container functionality is here (some parts were removed for brevity):
export const ReactMeteorData = {
componentWillMount() {
this.data = {};
this._meteorDataManager = new MeteorDataManager(this); // (1)
const newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
},
componentWillUpdate(nextProps, nextState) {
// backup current state and props, assign next ones to components
let newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
// restore backed up data
},
componentWillUnmount() {
this._meteorDataManager.dispose(); // (4)
},
};
The main points are:
- Before being mounted, a new data manager is created (1). It is in charge of running the computation and populating this.data according to data changes.
- At first and whenever the component should update, the computation is run (2) and the data is updated (3). The update happens whenever the component receives new state or props (in this type of container, it should only be props), and, as we saw earlier, also when the Tracker computation is invalidated, due to the call to component.forceUpdate().
The wrapped component receives the parent's props, as well as the Tracker computation's data as props:
return <WrappedComponent {...this.props} {...this.data} />;
Any more points as to how it should be used?
The react-meteor-data has a short section in the meteor guide.
Generally, the simple example in the guide (as well as the OP's example) should work just fine, as long as the state is set appropriately, using setState() (see the "how does it work?" section above).
Also, there is no need to re-map the container state to props sent to the child, as they are passed along (unless there is a very good reason for doing so).
Do consider the point in the preventing re-renders section if you encounter any performance issues.
From the guide:
export default ListPageContainer = withTracker(({ id }) => {
const handle = Meteor.subscribe('todos.inList', id);
const loading = !handle.ready();
const list = Lists.findOne(id);
const listExists = !loading && !!list;
return {
loading,
list,
listExists,
todos: listExists ? list.todos().fetch() : [],
};
})(ListPage);
in this example, note that the container expects an id prop, and it will also be made available to the wrapped component, as well as loading, list, etc (which come from the container's computation in the example).
I have the following code in my render method:
render() {
return (
<div>
{this.props.spatulaReady.ready() ? <p>{this.props.spatula.name}</p> : <p>loading spatula</p>}
</div>
)
}
Which according to my understanding, checks if the subscriptionhandle is ready (data is there) and displays it. If no data is available, it should display a simple loading message. However, when I first load the page this snippet is on, it get's stuck on the loading part. On a page reload the data (usually) displays fine.
If I check the spatulaReady.ready() when the page first loads and while the display is stuck on 'loading spatula', and the data that should be there, the handle reports as ready and the data is there like it is supposed to be. If I refresh the page it all displays fine as well. The problem is, this way of checking for data and rendering if it has arrived has worked fine for me in the past. Is it because the render method is not reactive? Because handle.ready() should be reactive.
What makes it even weirder is that it sometimes DOES correctly display the data on page load, seemingly at random.
CreateContainer code:
export default createContainer(props => {
return {
user: Meteor.user(),
spatulaReady: Meteor.subscribe('spatula.byId', props.deviceId),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
Publication code:
Meteor.publish('spatula.byId', function(deviceId) {
const ERROR_MESSAGE = 'Error while obtaining spatula by id'
if (!this.userId) //Check for login
throw new Meteor.Error('Subscription denied!')
const spatula = SpatulaCollection.findOne({_id: deviceId})
if(!spatula) //No spatula by this id
throw new Meteor.Error(403, ERROR_MESSAGE)
if(spatula.ownedBy != this.userId) //Spatula does not belong to this user
throw new Meteor.Error(403, ERROR_MESSAGE)
return SpatulaCollection.find({_id: deviceId})
})
I know I'm missing a piece of the puzzle here, but I've been unsuccessful at finding it. If you don't know the solution to my specific problem, pointing me in the right direction with another way of waiting for the data to arrive before displaying it is also greatly appreciated.
EDIT: After doing some trial-and-error and reading various other posts somewhat related to my project, I figured out the solution:
export default createContainer(props => {
const sHandle= Meteor.subscribe('spatula.byId', props.deviceId)
return {
user: Meteor.user(),
spatulaReady: sHandle.ready(),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
It still makes no sense to me that moving the ready() call to create container fixed all my problems though.
As you figured out, moving the .ready() call to createContainer fixes the problem. This is because Meteor reactivity only works when you call a reactive data source (a reactive function), such as collection.find() or subscriptionHandle.ready() within a reactive context, such as Tracker.autorun or createContainer. Functions within the React component, including render, are not reactive contexts from Meteor's perspective.
Note that React and Meteor reactivity are two different things. React's reactivity works simply so that whenever a component's props or state change, it's render function is re-run. It does not understand anything about Meteor's reactive data sources. Since createContainer (that is re-run by Meteor reactivity when reactive data sources in it change) simply passes props to the underlying component, the component is re-rendered by React when the props given from createContainer change.