I'm using next.js and apollo with react hooks.
For one page, I am server-side rendering the first X "posts" like so:
// pages/topic.js
const Page = ({ posts }) => <TopicPage posts={posts} />;
Page.getInitialProps = async (context) => {
const { apolloClient } = context;
const posts = await apolloClient.query(whatever);
return { posts };
};
export default Page;
And then in the component I want to use the useQuery hook:
// components/TopicPage.js
import { useQuery } from '#apollo/react-hooks';
export default ({ posts }) => {
const { loading, error, data, fetchMore } = useQuery(whatever);
// go on to render posts and/or data, and i have a button that does fetchMore
};
Note that the useQuery here executes essentially the same query as the one I did server-side to get posts for the topic.
The problem here is that in the component, I already have the first batch of posts passed in from the server, so I don't actually want to fetch that first batch of posts again, but I do still want to support the functionality of a user clicking a button to load more posts.
I considered the option of calling useQuery here so that it starts at the second "page" of posts with its query, but I don't actually want that. I want the topic page to be fully loaded with the posts that I want (i.e. the posts that come from the server) as soon as the page loads.
Is it possible to make useQuery work in this situation? Or do I need to eschew it for some custom logic around manual query calls to the apollo client (from useApolloClient)?
Turns out this was just a misunderstanding on my part of how server side rendering with nextjs works. It does a full render of the React tree before sending the resulting html to the client. Thus, there is no need to do the "first" useQuery call in getInitialProps or anything of the sort. It can just be used in the component alone and everything will work fine as long as getDataFromTree is being utilized properly in the server side configuration.
Related
In my react app I use the following pattern quite a bit:
export default function Profile() {
const [username, setUsername] = React.useState<string | null>(null);
React.useEffect(()=>{
fetch(`/api/userprofiles?username=myuser`)
.then(res=>res.json())
.then(data => setUsername(data.username))
},[])
return(
<div>
{username}'s profile
</div>
)
}
When the page loads, some user data is fetched from the server, and then the page updates with that user data.
One thing I notice is that I only really need to call setUsername() once on load, which makes using state seem kinda excessive. I can't shake the feeling that there must be a better way to do this in react, but I couldn't really find an alternative when googling. Is there a more efficient way to do this without using state? Or is this the generally agreed upon way to load data when it only needs to be done once on page load
Without using any external libraries, no - that is the way to do it.
It would be possible to remove the state in Profile and have it render the username from a prop, but that would require adding the state into the parent component and making the asynchronous request there. State will be needed somewhere in the app pertaining to this data.
The logic can be abstracted behind a custom hook. For example, one library has useFetch where you could do
export default function Profile() {
const { data, error } = useFetch('/api/userprofiles?username=myuser');
// you can check for errors if desired...
return(
<div>
{data.username}'s profile
</div>
)
}
Now the state is inside useFetch instead of in your components, but it's still there.
Below I am pulling all the data from the database. But let's say I deleted one piece of data. How can I retrieve the renewed data? How can I run it again?
export async function getServerSideProps() {
const res = await fetch(`http://localhost:8000/api/getAllShipmentTypes`);
const shipmentTypes = await res.json();
return {
props: { shipmentTypes } // will be passed to the page component as props
};
}
But let's say I deleted one piece of data. How can I retrieve the renewed data?
I think you will need to define what is the trigger for the deletion, I can think of these two.
Another action user performs on a page.
Some other system modifying the database that this client application shows.
For #1, To the action, say a button click you can use a router object to set the same route again which will run getServerSideProps again
When you request this page on client-side page transitions through next/link or next/router, Next.js sends an API request to the server, which runs getServerSideProps
For #2 - this would be handled by giving the user an option to refetch the data from the server again using a link or router component
You can do something like:
import { useRouter } from 'next/router';
function SomePage(props) {
const router = useRouter();
// Call this function whenever you want to
// refresh props!
const refreshData = () => {
router.replace(router.asPath);
}
}
I have a static website made with react that requests data from the backend in the useEffect() hook:
export default const App = () => {
const [data, setData] = useState("");
useEffect(() => {
server.get().then(data => {
setData(data)
})
})
return(
<title>{data}</title>
<h1>{data}</h1>
)
}
However, when Bing crawls the webpage, the following problem occurs:
Bing Screenshot:
<title></title>
<h1></h1>
How can I solve this issue?
React isn't used for static sites. If you'd like to have better SEO and server-side rendering you can use nextjs.
The way your app is setup currently will only return some HTML with and empty body to a GET request to / (which is what I suppose crawlers like the one you mentioned use) and starts rendering components after the JavaScript is loaded.
But if you decide on a server-side rendering approach, whenever a request is made to your app the server will first render the app on it's side and the return an HTML string with the rendered components.
Did you check if your server.get() is returning some data? I can't see any url here, so maybe it's actually returning nothing.
Even so, maybe you forgot to pass the second argument of useEffect, which is an array of arguments, which this hooks uses to trigger itself. For example, if you want to trigger only once, when component is mounted, you need to pass [] as second argument of useEffect.
I'm using next-redux-wrapper and dispatching actions from getServerSideProps from individual pages. But I realized that I can't access the populated store state from another page. If I try to, in either client-side or server-side, the state returns empty in the other pages.
So, I heard that using getInitialProps is required to share state among all pages. Since I'm getting confused with all these I want to have some doubts cleared. I want to know:
When is it necessary, if at all, to use getInitialProps in the _app.js file when using redux with next-redux-wrapper? I heard that need to use getInitialProps inside _app.js in order to make the state accessible to every pages. But it's not working for me. Maybe due to wrong implementation!
If I use getInitialProps in _app.js then, is it not required to use getServerSideProps or getStaticProps in individual pages?
After populating state with getServerSideProps, can I share the state to every page without using getInitialProps in _app.js or if nneded can I pass the fetched state to getInitialProps in _app.js?
Yes, You have to use getIntitprops in the APP component to provide store in all pages in this case all page will run on a server which huge downfall, if you have lots of static pages,
or you can use this code on each page according to your needs but your dispatch will change server-side state only!!!, which means you can access them on the client-side.
export const getServerSideProps = wrapper.getServerSideProps(async ({ store, query }) => {
try {
const { id } = query
const res = await api.get('/abc', { params: { id } })
await store.dispatch(action)
return {
props: {
id,
data: res.data,
isServer: typeof window === 'undefined',
}
}
} catch (error) {
return {
props: {
errorCode: 409,
message: "Data Unavailable"
}
}
}
})
In the end, I ditched both options because it provides a bad user experience.
My recommendation is to use getInitProps and check if the page is rendering on the server then call API and save props in client-side, otherwise call API in the client a and save it.
Edit: SOLVED! Please see below.
I want my Blog component to fire the fetchBlog action creator every time the browser requests its URL, be it via a link or a refresh. I'd like to do it with the React useEffect Hook and with the React-Redux useDispatch and useSelector Hooks. However, my action only fires when following the link to the page; I do not understand why, even after reading several explanations (like the official docs).
Here is the code:
// Everything duly imported, or else VSC would yell at me
export default function Blog() {
const dispatch = useDispatch();
// slug is set here with useSelector, this always works
useEffect(() => {
dispatch(fetchBlog(slug))
}, [slug, dispatch]);
const blog = useSelector((state) => state.blogs[0]);
// return renders the blog information from the blog constant
// since the action does not fire, blog is undefined because state.blogs is an empty array
}
I know that, on refresh, fetchBlog does not fire because of Redux DevTools and also because I put a debugger there. (And the back-end logs don't show the request coming in.) The action creator itself and the reducer must be working; if they weren't, the page would not load correctly when visited through a link.
Edit: I have determined useSelector and useDispatch are not the root cause of the problem, as changing the code to use connect with mapStateToProps and mapDispatchToProps gives the same result. The issue seems to be with useEffect.
I think the problem is you are returning the call to dispatch. Functions returned from useEffect are clean up functions, so I don't think this would run on mount, or update - only before unmount. Try this:
export default function Blog() {
// ...
// Don't return from useEffect. Just call dispatch within the body.
useEffect(() => {
dispatch(fetchBlog(slug);
}, [slug, dispatch]);
// ...
}
https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect
I'd like to clarify what the issue was, which #Trace guided me to finding.
useEffect wasn't being called on refresh because it gets called after the component renders/returns. When refreshing, the state - including the blog data - is lost; instead of returning, a TypeError is thrown because data.title doesn't exist. So useEffect never gets the chance of being called and fetch the blog's content.
The solution to that goes like this:
export default function Blog() {
// ...
useEffect(/* ... */)
const blog = useSelector((state) => state.blogs[0]);
if (!blog) {
return <p>Loading...</p>
}
// return actual blog contents here
}
So now fetchBlog does get called, updating blog and rendering the content.
It isn't clear to me where the slug comes from.
In theory useEffect runs after every render. In case of multiple parameters it will run the callback when one of the array parameters passed in the second argument changes.
Either create a useEffect with empty array as second argument to run it 'once' (e.g. when you refresh) or check the slug value.
Edits after checking the repo:
It's not going to work because useEffect is run AFTER the render (which was included in my answer although someone downvoted it). Dispatching the call will only happen after, or not at all if the exception was thrown before (in this case a nullpointer).
You can get the slug from react-router with match, may be handy for you to know.
export default function Blog({ match }) {
const slug = match.params.slug;
etc
The git repo shows how dispatch as is added as array parameter to useEffect, which is not necessary.