getStaticProps with SWR "Error serializing `.initialData.config.transformRequest[0]` returned from `getStaticProps`" - reactjs

I'm trying to use SWR in Next.js env.
const Best = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
const { data } = useSWR('/best', apis.getBestProduct, {
initialData: props.initialData,
});
console.log(data);
return (
...SOME PRESENTER
);
};
export const getStaticProps: GetStaticProps = async () => {
const data = await apis.getBestProduct();
return { props: { initialData: data } };
};
export default Best;
I want to use useSWR with getStaticProps.
But this code makes error like this.
Server Error
Error: Error serializing `.initialData.config.transformRequest[0]` returned from `getStaticProps` in "/best".
Reason: `function` cannot be serialized as JSON. Please only return JSON serializable data types.
The data came well, I don't know why it doesn't work.
I'm using MacOS, custom Node.js server, and this is localhost.

getStaticProps sends your data to the front end by first serializing it with JSON—that is, it transforms data from a runtime JavaScript object into a string that can be parsed into a runtime JavaScript object by your front end.
However, JSON has certain limitations, one of which is that it cannot serialize functions. And somewhere in your data object you have a value that is a function. You need to clean up this data object before you return it from getStaticProps.
For example, suppose data.myFunction is a function, you could do something like:
export const getStaticProps: GetStaticProps = async () => {
const {myFunction, ...data} = await apis.getBestProduct();
return { props: { initialData: data } };
};
This will remove myFunction and leave all the other keys in data.

Related

How to throw argument in RTK Query (queryFn)

I have queryFn query in RTK, and I need to get some data from firebase DB by element ID. But when I give this arg to queryFn like in example below, I got undefined.
and I'm calling it like this:
The reason you got undefined is because the useGetCardByIdQuery hook returns the data undefined initially. The data is going to be available after a success fetch.
As far I understand from your code, you are trying to get the cards of authorized firebase user; so you don't need to pass any id indeed since I see that you are not using the id in the queryFn.
In that case, just pass the undefined like useGetCardByIdQuery(undefined); and return the cardList.
And for better typing, you can define the builder query with <OutputType, InputType>
getCardsById: builder.query<CardList, string>({
queryFn: async (id, api, extraOptions, fetchWithBQ) => {
try {
const user = getAuth();
...
const cardList = cardSnapshot.docs.map(doc => doc.data())
return { data: cardList }
} catch (error) {
return { error }
}
},
})
Then you can call the hook in the component.
const response = useGetCardsByIdQuery(undefined);
if (response.data) {
const cards = response.data;
console.log(cards);
}

MobX not hydrating in next.js state when fetching async data

I have a MobX store where I have a function doing an API call. It works fine it's getting the data but it doesn't update the already rendered page. I'm following this tutorial https://medium.com/#borisdedejski/next-js-mobx-and-typescript-boilerplate-for-beginners-9e28ac190f7d
My store looks like this
const isServer = typeof window === "undefined";
enableStaticRendering(isServer);
interface SerializedStore {
PageTitle: string;
content: string;
isOpen: boolean;
companiesDto: CompanyDto[],
companyCats: string[]
};
export class AwardStore {
PageTitle: string = 'Client Experience Awards';
companiesDto : CompanyDto[] = [];
companyCats: string[] = [];
loadingInitial: boolean = true
constructor() {
makeAutoObservable(this)
}
hydrate(serializedStore: SerializedStore) {
this.PageTitle = serializedStore.PageTitle != null ? serializedStore.PageTitle : "Client Experience Awards";
this.companyCats = serializedStore.companyCats != null ? serializedStore.companyCats : [];
this.companiesDto = serializedStore.companiesDto != null ? serializedStore.companiesDto : [];
}
changeTitle = (newTitle: string) => {
this.PageTitle = newTitle;
}
loadCompanies = async () => {
this.setLoadingInitial(true);
axios.get<CompanyDto[]>('MyAPICall')
.then((response) => {
runInAction(() => {
this.companiesDto = response.data.sort((a, b) => a.name.localeCompare(b.name));
response.data.map((company : CompanyDto) => {
if (company.categories !== null ) {
company.categories?.forEach(cat => {
this.addNewCateogry(cat)
})
}
})
console.log(this.companyCats);
this.setLoadingInitial(false);
})
})
.catch(errors => {
this.setLoadingInitial(false);
console.log('There was an error getting the data: ' + errors);
})
}
addNewCateogry = (cat : string) => {
this.companyCats.push(cat);
}
setLoadingInitial = (state: boolean) => {
this.loadingInitial = state;
}
}
export async function fetchInitialStoreState() {
// You can do anything to fetch initial store state
return {};
}
I'm trying to call the loadcompanies from the _app.js file. It calls it and I can see in the console.log the companies etc but the state doesn't update and I don't get to see the actual result. Here's the _app.js
class MyApp extends App {
constructor(props) {
super(props);
// Don't call this.setState() here!
this.state = {
awardStore: new AwardStore()
};
this.state.awardStore.loadCompanies();
}
// Fetching serialized(JSON) store state
static async getInitialProps(appContext) {
const appProps = await App.getInitialProps(appContext);
const initialStoreState = await fetchInitialStoreState();
return {
...appProps,
initialStoreState
};
}
// Hydrate serialized state to store
static getDerivedStateFromProps(props, state) {
state.awardStore.hydrate(props.initialStoreState);
return state;
}
render() {
const { Component, pageProps } = this.props;
return (
<Provider awardStore={this.state.awardStore}>
<Component {...pageProps} />
</Provider>
);
}
}
export default MyApp;
In the console.log I can see that this.companyCat is update but nothing is changed in the browser. Any ideas how I can do this? Thank you!
When you do SSR you can't load data through the constructor of the store because:
It's does not handle async stuff, so you can't really wait until the data is loaded
Store is created both on the server side and on the client too, so if theoretically constructor could work with async then it still would not make sense to do it here because it would load data twice, and with SSR you generally want to avoid this kind of situations, you want to load data once and reuse data, that was fetched on the server, on the client.
With Next.js the flow is quite simple:
On the server you load all the data that is needed, in your case it's loaded on the App level, but maybe in the future you might want to have loader for each page to load data more granularly. Overall it does not change the flow though
Once the data is loaded (through getInitialProps method or any other Next.js data fetching methods), you hydrate your stores and render the application on the server side and send html to the client, that's SSR
On the client the app is initialized again, though this time you don't want to load the data, but use the data which server already fetched and used. This data is provided through props to your page component (or in this case App component). So you grab the data and just hydrate the store (in this case it's done with getDerivedStateFromProps).
Based on that, everything you want to fetch should happen inside getInitialProps. And you already have fetchInitialStoreState method for that, so all you need to do is remove data fetching from store constructor and move it to fetchInitialStoreState and only return the data from it. This data will then go to the hydrate method of your store.
I've made a quick reproduction of your code here:
The huge downside if App.getInitialProps is that it runs on every page navigation, which is probably not what you want to do. I've added console.log("api call") and you can see in the console that it is logged every time you navigate to any other page, so the api will be called every time too, but you already have the data so it's kinda useless. So I recommend in the future to use more granular way of loading data, for example with Next.js getServerSideProps function instead (docs).
But the general flow won't change much anyway!
Calling awardStore.loadCompanies in the constructor of MyApp is problematic because the loadCompanies method is populating the store class. What you want is to hydrate the store with the companyCats data. Since server and client stores are distinct, you want to load the data you need on the server side i.e. fetchInitialStoreState (or load it from a page's getStaticProps/getServerSideProps method) so that you can pass it into the hydrate store method from page/app props.
Note loadCompanies is async so it'll be [] when getDerivedStateFromProps is called so there's nothing to hydrate. For your existing hydrate method to work you need initialStoreState to be something like the fetchInitialStoreState method below. Alternatively if it's fetched on the page level, the hydrate may be closer to initialData?.pageProps?.companyCats
It's common to see the store hydration as needed for each page though it's still valid to call loadCompanies() from the client side. There's a lot I didn't get a chance to touch on but hopefully this was somewhat helpful.
export const fetchInitialStoreState = async() => {
let companyCats = [];
try {
const response = await axios.get < CompanyDto[] > ('MyAPICall')
response.data.map((company: CompanyDto) => {
if (Array.isArray(company.categories) && company.categories.length > 0) {
companyCats.push(...company.categories)
}
})
} catch (error) {
// Uh oh...
}
return {
serializedStore: {
companyCats,
// PageTitle/etc
}
}
}

React & Sanity - Fetch Error: invalid JSON response body

I have been following a tutorial on youtube to build a twitter-clone website. However, when trying to fetch tweets from Sanity I am getting this error. I even git cloned the repo of the person that made the tutorial and I'm still getting the same error. This leads me to believe it is an issue with my VS code and not the code itself, if anyone has any suggestions that would be great thank you.
// fetchTweets.ts
export const fetchTweets = async () => {
const res = await fetch(`http://localhost:3001/api/getTweets`)
const data = await res?.json()
const tweets: Tweet[] = data.tweets
console.log('fetching', tweets)
return tweets
}
// index.tsx
export const getServerSideProps: GetServerSideProps = async (context) => {
const tweets: Tweet[] = await fetchTweets()
return {
props: {
tweets,
},
}
}
That error is typically caused by trying to render HTML as JSON—and particularly, when JSON is expected but instead an API returns an error page. Is your server definitely running on port 3001? Fetching from a non-existent server is likely consistent with this error.

The data in Next.js shows as array, different from the JSON object returned by the API

I fetch data from Laravel API using getServerSideProps():
This is how I fetch the data:
function Product({ data }) {
console.log(data);
return (
<div>Hello</div>
)
}
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`http://localhost:8000/api/products/`, {
method: 'GET',
})
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Product
This is the output in the console:
So it means in order to get the actual value for a product I'll need to extract it from props like so: data.data[0].name - is there a better way to do it? It looks too complicated and prone to errors. Can I get a non-nested array in the first place?
*Also, the API returns a different object, and not an array like shown in the picture above
{"data":[{"id":1,"name":"some-product","price":"1250331.30","created_at":"2021-08-12T11:01:28.000000Z","updated_at":"2021-08-12T11:01:28.000000Z"},
{"id":2,"name":"some-other-product","price":"1141260.11","created_at":"2021-08-12T13:57:54.000000Z","updated_at":"2021-08-12T13:57:54.000000Z"}]}
You could use the destructuring notation ?
const { data } = await res.json()

How to import API route in NextJS for getStaticProps?

I am using NextJS's getStaticProps to fetch some data from an external API. Reading the data fetching documentation on getStaticProps I came across this particular note:
Note: You should not use fetch() to call an API route in your
application. Instead, directly import the API route and call its
function yourself. You may need to slightly refactor your code for
this approach.
Right now I am calling getStaticProps directly from a page component called Index as follows:
export default function Index({ data }) {
return <div>{data}</div>;
}
export async function getStaticProps() {
const response = await fetch("http://127.0.0.1:8000/data");
const data = await response.json();
return { props: { data } };
}
As per the above documentation, this should not be done. How can I restructure my code to fetch data correctly? What does it mean to "import the API route and call its function yourself?"
I think that the Note is related to internal api path
You should not use fetch() to call an API route in your application
I suppose is related to every path that you define in /pages/api/*. Instead of fetch you can simply refactor your code and import data.
So, the code below is correct, don't need any refactoring
export default function Index({ data }) {
return <div>{data}</div>;
}
export async function getStaticProps() {
const response = await fetch("http://127.0.0.1:8000/data");
const data = await response.json();
return { props: { data } };
}

Resources