Why can't I memoize in NextJS' getServerSideProps? - javascript

I'm using React + NextJS to display a list of products on a category page.
I can get the products just fine using getServerSideProps, but I don't like that it re-requests the product list on each visit to the same page. I'm trying to memoize a function that gets the list, and while that seems to work (meaning there are no errors thrown), the supposedly memoized function is still called on subsequent visits to the same page.
See the code below, and note that the "get category" console log is shown in the terminal window when I revisit a page, and in Chrome's network tools I see a fetch request made by NextJS.
How can I make it cache the result of my getCategory function so it doesn't keep fetching it?
export async function getServerSideProps(context) {
let config = await import("../../config/config");
let getCategory = memoize(async (url) => {
console.log("getting category");
let response = await axios.get(url);
if ( response.status ) {
return response.data;
} else {
return false;
}
});
let response = await getCategory(`${config.default.apiEndpoint}&cAction=getCTGY&ctgyCode=${context.params.code}`);
if ( response ) {
return {
props: {
category: response
}
};
} else {
return {
props: {
category: null
}
};
}
}

This doesn't work becuase nextjs api routes are "serverless", which means the state that memoize is supposed to remember is destroyed after HTTP call.
The serverless solution is to use a separate service for caching, which is accessible from your api route.
Otherwise, you may need to look at using a custom server.

Related

React Recoil - Call Delete API Endpoint async on Set

I am just trying to get to grips with React Recoil to replace Redux in my application. I want to be able to Delete a Season record so have set the following:
Selector Family
export const deleteSeason = selectorFamily({
key: "deleteSeason",
set:
() =>
async ({ get, set }, seasonId) => {
return await seasonApi.deleteSeason(seasonId);
},
});
Where seasonApi.deleteSeason carries out the actual API call.
The Select-Family is referenced here:
const setSeasonDelete = useSetRecoilState(deleteSeason());
And called in an async function here:
const onDeleteSeason = async (selectedSeason) => {
setInEditMode(false);
try {
await setSeasonDelete(selectedSeason.Id);
} catch (error) {
deleteSeasonFailNotification("error");
}
};
When the method is called (from a button click) the API Appears to be hit and the DB deletes the record, however the TRY-CATCH drops out with the error:
"Recoil: Async selector sets are not currently supported."
I presume there is a better/correct way of doing what I am trying to do so any help is greatly appreciated.

axios.post not returning data from server: "Cannot destructure property 'data' of '(intermediate value)' as it is undefined"

I am trying to get data from server via axios.post().
Decided to use POST and not GET because I want to send an array with ids to look up in the database, which might be too large to fit in GET query params.
I managed to send an array with ids in the body of the POST. This reaches my server. I can successfully find the items in the data base. The items are then returned in the response. The data shows up in Chrome devtools > Network (status 200). I also get the right stuff back when sending a request manually using Postman.
Everything seems to be working fine, but the response does not arrive in my data variable in the axios function.
I spent the day trying out the solutions to all the similar answers here. Nothing worked...
I also tried GET and sending the ids in query params instead, which gives the same error. I suspect I am doing something wrong with async/await because I am getting this "intermediate value" thingy.
Thanks in advance for the help.
CLIENT axios functions
const url = 'http://localhost:5000';
export const getStuff = Ids => {
axios.post(
`${url}/cart/stuff`,
{
Ids: Ids,
},
{
headers: {
'Content-Type': 'application/json',
},
}
);
};
CLIENT actions
import * as api from '../api';
export const getStuff = Ids => async dispatch => {
try {
// Ids is an array like ["5fnjknfdax", "5rknfdalfk"]
const { data } = await api.getStuff(Ids);
// this gives me the error in the title, data never comes through
//dispatch(-dolater-);
} catch (error) {
console.log(error);
}
};
SERVER controllers
export const getStuff = async (req, res) => {
try {
const { Ids } = req.body;
const stuff = await STUFF.find().where('_id').in(Ids);
console.log('SERVER', stuff);
// this works until here. request comes through and
// I can successfully find the stuff I want in the database
res.status(200).json(stuff); // this also works, response is being sent
} catch (error) {
res.status(404).json({ message: error });
}
};
SERVER routes
router.post('/cart/stuff', getStuff);
You have some extra curly braces here (or a missing return, depending on how you look at it). When you use a lambda (arrow function) with curly braces, you have to explicitly return a value or else it will return undefined. Change your code from this:
export const getStuff = Ids => {
axios.post(...);
};
to one of these:
// Option 1
export const getStuff = Ids => {
return axios.post(...);
};
// Option 2
export const getStuff = Ids => axios.post(...);
Either format will return the actual axios promise, instead of the default undefined.
export const fetchPost = () => {
return axios.get(url);
};
This works for me!!

Internal API fetch with getServerSideProps? (Next.js)

I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.

Cannot console.log fetch results

I'm trying to do an API call using fetch().
I'm aware that fetch() returns a Promise and should be handled using .then or await. The same for the result.json() Followed this tutorial http://www.reactnativeexpress.com/networking, I arrived with fetchRoute()function. The console.log(route) inside the function is never called.
I tried to return console.log(fetchRoute(this.state.userLocation, text)), but it was still returning a Promise.
I read another quesiton here on Stack Overflow (sorry, can't find the link anymore), and they said to try something like this:
getRouteHandler = (text) => {
fetchRoute(this.state.userLocation, text).then(json => console.log(json));
Still, I couldn't log the fetch results. Anyone knows what could be going wrong? Here is the relevant code:
const fetchRoute = async (ori, dest) => {
let origin = ori.latitude+','+ori.longitude;
let destination = encodeURIComponent(dest);
const key = "MyAPIKey";
const URL = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin}&destination=${destination}&key=${key}`;
try{
const response = await fetch(URL)
const route = await response.json()
console.log(route)
return route
}catch(e){
return e
}
}
export default class App extends Component{
state = {
userLocation: null,
route: [],
}
getRouteHandler = (text) => {
fetchRoute(this.state.userLocation, text).then(json => console.log(json));
}
Sometimes if you're fetching large amounts of data it will take awhile for it to log. For example, in a past project the api I was fetching from had close to 5 million records. It took a few minutes to see anything in the console.

Nuxtjs async await in a page doesnt work on page refresh

Am trying to fetch data in the fetch method of my page using vuex and nuxt js but whenever a person refreshes the page it fails but works when i navigate through nuxt navigation
So in my page i have
fetch ({ store, params }) {
store.dispatch('getJobspecialisims')
},
//IVE ALSO TRIED ASYNC
async asyncData (context) {
await context.store.dispatch('getJobspecialisims');
},
SO in my vuex action i have
async getJobspecialisims({commit}){
await axios.get(process.env.baseUrl+'/job-specialisims').then((res)=>{
commit(types.SET_JOB_SPECIALISIMS, res.data.data)
},(err)=>{
console.log("an error occured ", err);
});
},
Now on my component where am fetching the records
<div>
<li v-for="(specialisim) in $store.getters.jobspecialisims">
<!-DO STUFF HERE->
</li>
The http request is nver sent when a person refreshes the browser
Where am i going wrong?
I would prefer the async await method. I uderstand that it returns a promise but i have no idea on how to know when the promise has resolved. Thepage should always await untill the data has been completely ffetched hence ive called the fetch or asyncdata method in my page
What else do i need to add or amend?
For async actions, you need to put return before dispatch when you call it inside fetch or asyncData:
fetch ({ store, params }) {
// store.dispatch('getJobspecialisims')
return store.dispatch('getJobspecialisims')
},
Source for this answer: Problem: Vuex actions, dispatch from page
Anyway, I still don't understand why you used await with then. If I, I will use async await like this:
async getJobspecialisims({ commit }) {
const res = await axios.get(YOUR_URL);
if (!res.error) {
commit("getJobspecialisims", res.data.data);
} else {
console.log(res.error);
}
}

Categories