I want to fetch single data by id and I am using getStaticPaths and getStaticProps but I am getting the error data is not defined. Where am I going wrong Please help
My [id].tsx file
import MainComponentLayout from "../../components/Layout/MainLayoutComponent"
import EditProject from "../../components/EditProjectForm";
// HOW MANY HTML PAGES NEEDS TO BE MADE BASED ON OUR DATA
export const getStaticPaths = async () => {
const response = await fetch(`http://b560-45-248-23-129.ngrok.io/projects`)
const data = await response.json()
console.log(data)
const path = data.result.map(project => {
return{
params: {id:project.project_ID}
}
})
return{
paths:path,
fallback: false
}
}
// FETCH SINGLE DATA
export const getStaticProps = async (context) => {
const id = context.params.id
const response = await fetch(`http://b560-45-248-23-129.ngrok.io/projects/${id}`)
// Single Object
const data = await response.json()
return{
props: {fetchedData:data},
}
}
const EditForm = () => {
return(
<MainComponentLayout ComponentToRender = {<EditProject fetchedData = {fetchedData}/>}/>
)
}
export default EditForm
Change const EditForm = () => { to const EditForm = ({fetchedData}) => and it will work.
The getStaticProps, as its name implies, passes the fetched props object to the function as properties. You need to define them in the function as an object, and you can also destructure as in the example above, basically defining a fetchedData variable.
If You want to use props {fetchedData:data} in your app, You need pass them to the page component as props. As we can read in docs:
props - An optional object with the props that will be received by the
page component. It should be a serializable object
Here You have example page with getStaticProps() correctly used.
and Your code with props, Good Luck ! ;-)
import MainComponentLayout from "../../components/Layout/MainLayoutComponent";
import EditProject from "../../components/EditProjectForm";
const EditForm = ({ fetchedData }) => {
return (
<MainComponentLayout
ComponentToRender={<EditProject fetchedData={fetchedData} />}
/>
);
};
// FETCH SINGLE DATA
export const getStaticProps = async (context) => {
const id = context.params.id;
const response = await fetch(
`http://b560-45-248-23-129.ngrok.io/projects/${id}`
);
// Single Object
const data = await response.json();
return {
props: { fetchedData: data },
};
};
// HOW MANY HTML PAGES NEEDS TO BE MADE BASED ON OUR DATA
export const getStaticPaths = async () => {
const response = await fetch(`http://b560-45-248-23-129.ngrok.io/projects`);
const data = await response.json();
console.log(data);
const path = data.result.map((project) => {
return {
params: { id: project.project_ID },
};
});
return {
paths: path,
fallback: false,
};
};
export default EditForm;
Related
so im doing a little application of a pokedex utilizing the pokeapi to test react query, what i am trying to do is first fetch the data on mount and after that, if i use the search button the data already fetched to change something like this
fetch on mount
and the search
search fetch
something easy to do with useState but i am having problems with react query
i got something like this
pokedex
at the mount of the component i have this
export const fetchPokemon = async (URL: string) => {
const result = await axios.get<pokemon>(URL);
return result;
};
export const fetchPokemons = async () => {
const URL = "https://pokeapi.co/api/v2/pokemon?limit=20&offset=0s";
const { data } = await axios.get<pokemons>(URL);
const result = await Promise.all(
data.results.map(async (pokemon) => {
return fetchPokemon(pokemon.url);
})
);
return result;
};
export const useAllPokemons = () => {
return useQuery({
queryKey: ["pokemons"],
queryFn: fetchPokemons,
});
};
const { data, isLoading } = useAllPokemons();
works great but now i want to search pokemons with a search button like in the image and to replace the initial data that i already fetch so only the data that i searched appears so i did this
export const fetchAllPokemons = async () => {
const URL = "https://pokeapi.co/api/v2/pokemon?limit=100";
const { data } = await axios.get<pokemons>(URL);
const result = await Promise.all(
data.results.map(async (pokemon) => {
return fetchPokemon(pokemon.url);
})
);
return result;
};
let { data, refetch } = useQuery({
queryKey: ["pokemons"],
queryFn: fetchAllPokemons,
enabled: false,
select: (data) => {
const pokemonData = data.map((pokemon) => {
if (pokemon.data.name.startsWith("char")) {
return pokemon;
}
});
return pokemonData;
},
});
<button
onClick={() => {
refetch();
}}
>
asd
</button>
and nothing happens, but when i open the console the data is changing but then again returns to the initial fetch
I guess you should use the onSuccess:
const {data} = useQuery("fetchData", fetchData, {
onSuccess: (data) => {
// do something with your data and return
}
});
I am creating a blog in Next.js and using Strapi headless CMS as backend.
I was trying to get data from the API I made from Strapi.
For fetching data I made
./client.js
export default class StrapiClient{
constructor(){}
/*API_URL = "http://localhost:1337/api/"*/
async fetchData(path){
const url = `${process.env.API_URL}${path}`
const res = await fetch(url)
const posts = await res.json()
return posts
}}
and imported it to ./components/blog.js
import StrapiClient from '../client'
const Client = new StrapiClient
export const getStaticProps = async () => {
const posts = await Client.fetchData(`articles`)
return{
props: {
posts,
}
}
};
const Blog = ({posts}) => {
return (
<div>
{posts.data.map((element) => {
return(
<div key={element.id}>
<h1 className=" text-2xl">{element.attributes.title}</h1>
</div>
)
})}
</div>
);
};
export default Blog;
but I got error
TypeError: Cannot read properties of undefined (reading 'data')
and here is the structure of data I was using
{
"data" : [
"id" /*string*/
]
}
You need to await the async function to get data from Promise
export const getStaticProps = async () => {
const posts = await Client.fetchData(`articles`)
return{
props: {
posts,
}
}
};
Async functions always return a promise
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
const posts = Client.fetchData(`articles`)
I think you need to await for the fetchData.
Edit:
I just noticed you are using getStaticPaths function instead of getStaticProps. Can you please change the name and try again?
From next.js documentation the getStaticPaths method is used to define a list of paths to be statically generated but to fetch data for page you need to use getStaticProps:
export async function getStaticProps() {
const posts = await Client.fetchData(`articles`);
return {
props: {
posts,
},
}
}
Or fetch data using getServerSideProps without use getStaticPaths:
export async function getServerSideProps() {
const posts = await Client.fetchData(`articles`);
return { props: { posts } }
}
my code
export const getServerSideProps: GetServerSideProps = async () => {
const ref = collection(db, "books");
const results = [];
const unsub = onSnapshot(ref, (snapshot) => {
snapshot.forEach((doc) => {
results.push({ ...doc.data(), id: doc.id });
});
//here i get the results
console.log(results)
});
// here is empty
console.log(results)
return {
props: {
books: results,
},
};
};
I'm trying to get the real time data from firestore database on the getServerSideProps function, inside the snapshot I can get the results, but when it's outside the array it's empty and I can't pass to props.
Instead of using onSnapshot I would use getDocs (you need to import from 'firebase/firestore'):
export const getServerSideProps: GetServerSideProps = async () => {
const [results, setResults] = useState([]);
const ref = collection(db, 'books');
const snapshot = await getDoc(docRef);
articlesSnapshot.forEach((doc) => {
setResults((oldArray) => [...oldArray, doc.data()]);
});
return {
props: {
books: results,
},
};
};
I was also stucked in the same problem.
Here we should use get() instead of snapshot cause next will take care of your updated data under the hood.
so rewrite your code like this
export async function getServerSideProps(context) {
const resultref = await db.collection("books").get();
const results = resultRef.docs.map((doc)=>({
id: doc.id,
resultData: doc.data()
}))
return {
props: { books: JSON.parse(JSON.stringify(results)) },
}
}
so map function will return the new array in variable results
which is then deep copied and passed as props
I hope this will give you the desired result 😀
I want to create basic Next.js HOC for authentication. I searched but I didn't figure it out.
I have an admin page in my Next.js app. I want to fetch from http://localhost:4000/user/me and that URL returns my user. If user data returns, component must be rendered. If data didn't return, I want to redirect to the /admin/login page.
I tried this code but that didn't work. How can I solve this issue? Also can I use useSWR instead of fetch?
const withAuth = (Component, { data }) => {
if (!data) {
return {
redirect: {
destination: "/admin/login",
},
};
}
return Component;
};
withAuth.getInitialProps = async () => {
const response = await fetch("http://localhost:4000/user/me");
const data = await response.json();
return { data };
};
export default withAuth;
const AdminHome = () => {
return ();
};
export default withAuth(AdminHome);
Server-side authentication
Based on the answer from Create a HOC (higher order component) for authentication in Next.js, you can create a re-usable higher-order function for the authentication logic.
If the user data isn't present it'll redirect to the login page. Otherwise, the function will continue on to call the wrapped getServerSideProps function, and will return the merged user data with the resulting props from the page.
export function withAuth(gssp) {
return async (context) => {
const response = await fetch('http://localhost:4000/user/me');
const data = await response.json();
if (!data) {
return {
redirect: {
destination: '/admin/login'
}
};
}
const gsspData = await gssp(context); // Run `getServerSideProps` to get page-specific data
// Pass page-specific props along with user data from `withAuth` to component
return {
props: {
...gsspData.props,
data
}
};
}
}
You can then use it on the AdminHome page to wrap the getServerSideProps function.
const AdminHome = ({ data }) => {
return ();
};
export const getServerSideProps = withAuth(context => {
// Your normal `getServerSideProps` code here
return { props: {} };
});
export default AdminHome;
Client-side authentication
If you'd rather have the authentication done on the client, you can create a higher-order component that wraps the component you want to protect.
const withAuth = (Component) => {
const AuthenticatedComponent = () => {
const router = useRouter();
const [data, setData] = useState()
useEffect(() => {
const getUser = async () => {
const response = await fetch('http://localhost:4000/user/me');
const userData = await response.json();
if (!userData) {
router.push('/admin/login');
} else {
setData(userData);
}
};
getUser();
}, []);
return !!data ? <Component data={data} /> : null; // Render whatever you want while the authentication occurs
};
return AuthenticatedComponent;
};
You can then use it to wrap the AdminHome component directly.
const AdminHome = () => {
return ();
};
export default withAuth(AdminHome);
If you're looking for the typescript version:
withAuth.ts
export function withAuth(gssp: GetServerSideProps): GetServerSideProps {
return async (context) => {
const { user } = (await getSession(context.req, context.res)) || {};
if (!user) {
return {
redirect: { statusCode: 302, destination: "/" },
};
}
const gsspData = await gssp(context);
if (!("props" in gsspData)) {
throw new Error("invalid getSSP result");
}
return {
props: {
...gsspData.props,
user,
},
};
};
}
Home.tsx
export const getServerSideProps = withAuth(async (context) => {
return { props: {} };
});
I have an index page that includes data fetching within getServerSideProps.
If I use next/link or maybe router.push() - Is there a way for that data to persist across to the rest of the pages in my app?
Or is this a scenario where I'd need to use something like Context/Redux?
For example:
index.tsx
const App = ({ productData }: IndexProps) => {
return (
<Link href={`/product/${title}`}> ... </Link>
)
}
export const getServerSideProps: GetServerSideProps = async () => {
const productData = await getProducts();
return {
props: { productData },
};
};
/product/[id].tsx
const Product = ({ productData }) => {
return (
<Link href={`/product/${title}`}> ... </Link>
)
}
export const getServerSideProps: GetServerSideProps = async () => {
if (PRODUCTDATADOESNTEXIST) {
const productData = await getProducts();
}
// else use data fetched in from previous page?
return {
props: { productData },
};
};
Thanks!
You can create a service that will cache the data in memory.
Something like this.
const cache = {};
export const setProducts = (products) => {
products.forEach((p) => {
cache[p.id] = p;
});
};
const getProduct = (id) {
if(cache[id]){
Promise.resolve(cache[id]);
}
// make the API call to fetch data;
}
export default getProduct;
Use the set method to store the data from the NextJS Page and use the get method to fetch data when needed.