I am building an application with storefront, which uses nextJS. I am able to use getServerSide props while loading a new page.
The page contains many components, each needing their own data. At the moment, I am getting all of these into a results array and then returning from getServerSideProps, as shown below.
export async function getServerSideProps({query}) {
let sid = query.shop
let promises = []
promises.push(getShopCategories(sid))
promises.push(getShopInfo(sid))
promises.push(offersFromShop(sid))
try {
let allPromises = Promise.all(promises)
let results = await allPromises;
//console.log("Shop Info:", results[1])
return {props: {
id: sid,
shopCategories: results[0],
shopInfo: results[1],
offers4u: results[2].products
}}
} catch(e) {
console.error("Failure:", e)
return { props: {}}
}
}
But in this way, I have to get the data needed for all components in one shot. I was thinking, how to let each sub component in the page to have its own 'getServerSideProps'.
I can implement this in each component, but I am unsure about passing the parameters needed (such as shopId, productId etc., which I would have fetched in the main page).
One way is to use cookies, so that the server side can pick up these values. Is there a better solution?
getServerSideProps is only available on direct component under page folder
If you want to fetch more data on sub-component in the page, consider using the useEffect hook or componentDidMount for that work.
Related
I have a page on which data is initialized via async fetch:
async fetch() {
const res = await requestApi(this, '/database');
this.sliderData = res.homeSlider;
this.modelData = res.model;
...
}
Then this data is thrown into the child components through the props, but since there are child components of level 3-4, it becomes not convenient to use the props with page. Is it possible to use provide/inject in this case? Moreover, it is important that the transmitted data is reactive. Objects always come from the request when trying to use provide/inject:
provide() {
return {
sliderData: this.sliderData
};
}
The data did not have time to be initialized and an empty object was sent.
I'm not sure that this is the best idea, but you can add a variable (for example isLoading: true) to your data; when all data you need is loaded change this.isLoading = false; additionally, add v-if="!isLoading" to the parent component. In this case, your child component will inject already loaded sliderData.
I'm trying to build a component that retrieves a full list of users from Amazon AWS/Amplify, and displays said results in a table via a map function. All good so far.
However, for the 4th column, I need to call a second function to check if the user is part of any groups. I've tested the function as a button/onClick event - and it works (console.logging the output). But calling it directly when rendering the table data doesn't return anything.
Here is what I've included in my return statement (within the map function)
<td>={getUserGroups(user.email)}</td>
Which then calls this function:
const getUserGroups = async (user) => {
const userGroup = await cognitoIdentityServiceProvider.adminListGroupsForUser(
{
UserPoolId: '**Removed**',
Username: user,
},
(err, data) => {
if (!data.Groups.length) {
return 'No';
} else {
return 'Yes';
}
}
);
};
Can anyone advise? Many thanks in advance if so!
Because you should never do that! Check this React doc for better understanding of how and where you should make AJAX calls.
There are multiple ways, how you can solve your issue. For instance, add user groups (or whatever you need to get from the backend) as a state, and then call the backend and then update that state with a response and then React will re-render your component accordingly.
Example with hooks, but it's just to explain the idea:
const [groups, setGroups] = useState(null); // here you will keep what "await cognitoIdentityServiceProvider.adminListGroupsForUser()" returns
useEffect(() => {}, [
// here you will call the backend and when you have the response
// you set it as a state for this component
setGroups(/* data from response */);
]);
And your component (column, whatever) should use groups:
<td>{/* here you will do whatever you need to do with groups */}</td>
For class components you will use lifecycle methods to achieve this (it's all in the documentation - link above).
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.
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.
How do I maintain a central/global object for some data with react-router?
Let's say my React site has 20 different routes. 15 out of those 20 routes need one JSON file from the server that almost never changes. I hate having to GET "/api/mydata" for every new URL/route.
I guess I can save the data in React's context, but that seems hacky and may not even work. Is there a more elegant solution for sharing data between react-router components?
Edit: I partially solved this by creating a Data.js file:
export var myData = function(cb) {
ajax('GET', '/api/mydata', resp => {
myData = function() {
return cb(resp);
};
cb(resp);
});
};
and use it like this:
myData(data => data.map(/* ...use the data */));
That way the data is guaranteed to only be fetched once per lifecycle.
Arrr... if you don't want to use redux or flux, you can create a singleton like this in a new js file
let appState = {};
export default appState;
First do ajax to fill the data into this singleton. Then import it to everywhere you want the data.