I am building a Next.js application with multiple pages with dynamic routing. Each page has multiple axios calls to the backend that are called with useEffect. My goal is to instead call these functions with getServerSideProps functions for speed purposes as the application is scaled to accomodate a larger user database.
My issue is when i try to recieve emails from the database, I get the error:
Error: Error serializing .allEmails.config.transformRequest[0] returned from getServerSideProps in "/emails".
Reason: function cannot be serialized as JSON. Please only return JSON serializable data types.
I want to recieve emails and pass it into props where i can then access the data on the page.
import React, { useState, useEffect, useContext } from 'react';
import axios from 'axios';
import jsHttpCookie from 'cookie';
import jsCookie from 'js-cookie';
const Emails = ({allEmails}) => {
const [emails, setEmails] = useState(allEmails);
return (
<></>
)
}
export async function getServerSideProps({req, res}) {
const {token} = jsHttpCookie.parse(req.headers.cookie);
const allEmails = await axios.get("http://localhost:8000/api/allCompanyEmails");
console.log(allEmails, "all data")
return {
props: {
allEmails
}
}
}
export default Emails;
allEmails is actually AxiosResponse type, it is not the data you get from api. And it contains non-serializable stuff like functions and etc.
To get the data you need to access data property of this response:
export async function getServerSideProps({req, res}) {
const {token} = jsHttpCookie.parse(req.headers.cookie);
const response = await axios.get("http://localhost:8000/api/allCompanyEmails");
console.log(response, "all data")
return {
props: {
allEmails: response.data
}
}
}
Related
I have a NextJS application where I am building a search results page based on a query parameter being passed. I am using the next/router to pick up the query string. The first time the page loads after compilation the querystring gets picked up and the call to the API is successful and returns results. Every subsequent try, whether on a F5 refresh, reloading the page via the reload button, or accessing the page again the call to the API gives no results because the querystring is undefined.
the URL:
http://localhost:3000/search?search=blah
import { useRouter } from "next/router";
import React, { useEffect, useState } from 'react';
import axios from 'axios'
export default function SearchResults1(props) {
const { query } = useRouter();
const [searchResults, setSearchResults] = useState({})
const [ getQuery, setQuery] = useState({})
useEffect(() => {
function fetchData(){
setQuery(query.search)
console.log(getQuery)
if (getQuery) {
axios.get(`http://localhost:3001/search/${getQuery}`)
.then((response) => {
setSearchResults(response.data)
})
.catch((error) => {
console.warn(error)
})
}
}
fetchData()
},[getQuery, setQuery])
I tried adding the getQuery and setQuery in the useEffect hook so it would be triggered when there are changes, but i still get undefined whenever i load the page a second time.
I also tried removing the getQuery and setQuery and tried without using state, but I still get the same result if I call the query.search directly - the first time it works, subsequently it does not.
I did notice that if I called {query.search} in the return HTML it always renders - so I am thinking it has something to do with perhaps the API call happening and not waiting for the query to be populated.
return (
<div>{query.search}</div> <!--this always works and shows the correct value-->
)
Any thoughts or suggestions on how I can achieve consistent results and always return the query parameters so I can make a good API call? I'm fairly confident I am missing something obvious.
Instead of parsing it directly with the useRouter(), try to get the router ready state and access it once it is ready in the useEffect.
import { useRouter } from "next/router";
import React, { useEffect, useState } from 'react';
import axios from 'axios'
export default function SearchResults1(props) {
const router = useRouter();
const [searchResults, setSearchResults] = useState({})
//const query = router.query; <-- or use this. The point is to access router.isReady in useEffect
useEffect(() => {
if (!router.isReady) return; // <-- only use the query when it is ready.
function fetchData(){
axios.get(`http://localhost:3001/search/${router.query.search}`)
.then((response) => {
setSearchResults(response.data)
})
.catch((error) => {
console.warn(error)
})
}
fetchData()
},[router.isReady]) // <-- use this router.isReady
According to the Next.js, It will be an empty object during prerendering if the page doesn't have data fetching requirements.
https://nextjs.org/docs/api-reference/next/router
This way, we can make sure the router information is ready for use when you access it.
I am making an weather app with API, I am successfully receiving the data with API in the function but I am not able to take it out of the function
here is the code
import React, {useState} from 'react'
import {Text, View} from 'react-native'
const axios = require('axios');
let HomeScreen =() => {
let key = "XXXX"
axios.get(`https://api.weatherapi.com/v1/current.json?key=${key}&q=London&aqi=no`)
.then(function (response) {
// handle success
console.log(response)
})
return(
<Text>This is {response}</Text>
)
}
export default HomeScreen
If you want to simply return data from the API use a normal JS function, not a React component.
function getData() {
return axios(`https://api.weatherapi.com/v1/current.json?key=${key}&q=London&aqi=no`)
}
It will return a promise which you can then use somewhere else.
async main() {
const data = await getData();
}
If you want your component to render data retrieved from the API you need to do things differently.
Use useEffect to run once when the component is mounted, and use useState to store the data once it's been retrieved. The state will then inform the JSX how to render.
Note that the response from the API is an object with location and current properties. You can't just add that to the JSX because React won't accept it. So, depending on what value you need from the data, you need to target it specifically.
Here's an example that returns the text value from the condition object of the current object: "It is Sunny".
const { useEffect, useState } = React;
function Example() {
// Initialise your state with an empty object
const [data, setData] = useState({});
// Call useEffect with an empty dependency array
// so that only runs once when the component is mounted
useEffect(() => {
// Retrieve the data and set the state with it
async function getData() {
const key = 'XXX';
const data = await axios(`https://api.weatherapi.com/v1/current.json?key=${key}&q=London&aqi=no`)
setData(data.data);
}
getData();
}, []);
// If there is no current property provide a message
if (!data.current) return <div>No data</div>;
// Otherwise return the current condition text
return (
<p>It is {data.current.condition.text}</p>
);
}
Here is my code:
import React from "react";
import { useState, useEffect } from "react";
import TutorialList from "./TutorialList";
import PropTypes from "prop-types";
const Home = () => {
const [tutorials, setTutorials] = useState(null);
useEffect(() => {
console.log("Fetching data");
fetch("http://192.168.212.52:8080/api/tutorials/all/")
.then((res) => {
return res.json();
console.log("Getting json from response");
})
.then((data) => {
console.log(data);
setTutorials(data);
});
}, []);
return (
<div className="home">
{console.log(tutorials)}
{tutorials && (
<TutorialList tutorials={tutorials} title={"All tutorials"} />
)}
</div>
);
};
Home.propTypes = {
title: PropTypes.string,
};
export default Home;
I expect this to make 1 get request to the server, which returns the data
that is then rendered with react.
What it actually does is make more than 10
requests to the server, still rendering the data after all the requests
finish. I can see the requests both from the server logs and from the browser
networking dev tools.
However, the Fetching data and Getting json from response logs only execute
once, as expected.
Since fetch requests a resource from the network, it runs asynchronously. This said, if you want to get to run it inside a useEffect it might be that if you wrap it in an async function it should work. However, keep in mind that it's not the argument of this hook itself async but another function that you define inside. For instance,
useEffect(() => {
console.log("Fetching data");
async function retrieveData() {
const json = await fetch("http://192.168.212.52:8080/api/tutorials/all/")
const data = await response.json()
setTutorials(data)
console.log("Got json from response");
}
retrieveData()
}, []);
I have a straightforward react component that looks so in AllWords.js :
import React, { useEffect, useState } from 'react';
import consts from '../../constants/Constants.js';
function AllWords() {
const [words, setWords] = useState([]);
async function fetchData(){
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
setWords(data);
};
// API: useEffect( () => { . . . return cleanup; },[var_n_whose_change_triggers_useEffect . . .] );
useEffect(() => {fetchData()}, [] );
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
I would like to refactor the fetchData() method out of the component into another file (basically a separate .js file that holds the fetch call).
What I would like is to have created a file titled FetchAllWords.js under src/actions/ & then import it. & use that.
I have several questions :
do I need to set the state in the FetchAllWords.js and then useSelector to extract the state in AllWords.js?
in FetchAllWords.js do I need to usedispatch to dispatch a method call setting the state? I would like to just setState in FetchAllWords.js and then extract it in AllWords.js. This is what I have so far:
import consts from '../constants/Constants.js';
import { useState } from 'react';
async function FetchAllWords(){
const [words, setWords] = useState([]);
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
setWords(data);
}
export default FetchAllWords;
I am unsure how to import this and use it in AllWords.js. I am using the following statement :
import wordList from '../../actions/FetchAllWords';
Then I am trying to use wordList as a handle to the file '../../actions/FetchAllWords.js' & attempting to access the async function FetchAllWords so wordList.FetchAllWords();
Firstly , the editor (VSCode) won't let me see the function despite the import call.
Secondly I am getting an error (something like) :
TypeError: _actions_FetchAllWords_js__WEBPACK_IMPORTED_MODULE_3__.default.FetchAllWords is not a function
Any insight or help would be appreciated since rather uneasy with JS & React.
The github repo is : https://github.com/mrarthurwhite/hooks-p5-react-redux
EDIT: As per David's suggestions :
So AllWords.js React component is :
import React, { useEffect, useState } from 'react';
import wordList from '../../services/Fetch.js';
function AllWords() {
const [words, setWords] = useState([]);
function fetchData(){
wordList.fetchAllWords().then(
data => setWords(data)
);
};
// API: useEffect( () => { . . . return cleanup; },[var_n_whose_change_triggers_useEffect . . .] );
useEffect(() => {fetchData()}, [] );
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
And Fetch.js is :
import consts from '../constants/Constants.js';
class Fetch {
async fetchAllWords(){
const response= await fetch(consts.FETCH_URL);
const data = await (response.json());
return data;
}
}
export default Fetch;
No, don't worry about state in the external file. Just focus on the one thing it should do, perform the AJAX operation. At its simplest it's just a function, something like:
import consts from '../../constants/Constants.js';
const fetchAllWords = async () => {
const response = await fetch(consts.FETCH_URL);
const data = await (response.json());
return data;
}
export default fetchAllWords;
You can even make it a class which contains this function, if you plan on adding other service operations as well. (Fetch specific word? Find word? etc.) The point is that this does just one thing, provide data. Let the React components handle React state.
Within the component you'd just use that to get your data. Something like:
import React, { useEffect, useState } from 'react';
import fetchAllWords from '../../services/FetchAllWords.js';
function AllWords() {
const [words, setWords] = useState([]);
useEffect(() => {
fetchAllWords().then(w => setWords(w));
}, []);
return (
<>
{
words.map(w=> <div>{w.word}</div>)
}
</>
);
}
export default AllWords;
Overall it's a matter of separating concerns. The service performs the AJAX operation and returns the meaningful data, internally concerned with things like JSON deserialization and whatnot. The React component maintains the state and renders the output, internally concerned with updating state after useEffect runs and whatnot.
I'm making a call to my server to get some json data from a 3rd party API (Yelp). I'm using axios and redux-promise to set the state in redux so I can use it in react. The data that gets returned is confusing me.
I want to set the state to an array of business data returned from the Yelp API. Currently, this action works if I pass in a basic string as the payload. I'm not sure how I get this JSON data from the api response and then manipulate it in react from the redux state.
Here is what the data looks like
Here is my action js file
import axios from 'axios';
export const ZIP_CODE = 'ZIP_CODE';
const ROOT_URL = 'http://www.localhost:3000/v1/location/1/1/2/4000'
function getBusinesses(json) {
const business_data = json['data']['businesses']
console.log(business_data)
return business_data;
}
/*
* Updates the users location
*/
export function updateZip(zipCode) {
const request = axios.get(ROOT_URL)
.then(getBusinesses)
return {
type: ZIP_CODE,
payload: request
}
}
I found a fix for my solution which involved some awkward tinkering and fixing bugs on my end.
Fix 1 - Updating the store
I've added thunk and it works well
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancers(
applyMiddleware(ReduxThunk)
));
Fix 2 - Integration of thunk into action
I parsed the json in an intial function then returned it to the action that gets sent to the reducer
import axios from 'axios';
export const ZIP_CODE = 'ZIP_CODE';
const ROOT_URL = 'http://www.localhost:3000/v1/location/1/1/2/4000'
function getBusinesses(res) {
return {
type: ZIP_CODE,
payload: res['data']['businesses']
}
}
export function updateZip(zipCode) {
return function(dispatch) {
axios.get(ROOT_URL)
.then((response) => {
dispatch(getBusinesses(response))
})
}
}