Nextjs getStaticPaths not fetching data from firebase firestore - javascript

I am failing to get dynamic data from firestore using getStaticPaths in nextjs. When I render the data from firestore using getStaticProps, it works, but when I open a specific item to get its details, it refuses and gives me a 404 page. This is what my code looks like for now, the [id].js page.
import React from 'react'
import { db } from '#/Firebase';
import {collection, getDoc} from "firebase/firestore";
const reference = collection(db, "abantu");
export const getStaticProps = async (context) => {
const id = context.params.id;
const data = await getDoc(reference);
const umuntuData = fetch(`${data}` + id);
return {
props: {
umuntu: umuntuData
}
}
}
export const getStaticPaths= async () => {
const umuntu = await getDoc(reference);
// const umuntuData = umuntu.docs
const paths = umuntu.docs.map(doc => {
return {
params: { id: doc.id }
}
})
return {
paths,
fallback: false
}
}
function Details({umuntu}) {
return (
<div>
<h1>{umuntu.ibizo}</h1>
</div>
)
}
export default Details
Where could I be going wrong?.

Your query getDoc(specific doc) vs getDocs(list of docs)
export const getStaticPaths= async () => {
const umuntu = await getDocs(reference);
// const umuntuData = umuntu.docs
const paths = umuntu.docs.map(doc => {
return {
params: { id: doc.id }
}
})
return {
paths,
fallback: false
}
}
For your static props, you will need to get specific document
//import {doc} from "firebase/firestore";
export const getStaticProps = async (context) => {
const id = context.params.id;
const docRef = doc(db, "abantu", id);
const data = await getDoc(docRef);
const umuntuData = fetch(`${data}` + id);
return {
props: {
umuntu: umuntuData
}
}
}

Related

How to use getStaticPaths with Redux in Next.js?

How do I use getStaticPaths when using Redux with Next.js?
I am using next-redux-wrapper to store my content and i am having trouble getting the data to display.
Please see my code example below
import { useSelector } from "react-redux";
import {getPageData} from '../redux/actions/pages'
import { useRouter } from "next/router";
import {wrapper} from '../redux'
import { getNavItems } from '../redux/actions/navItems';
import { getServiceData } from '../redux/actions/services';
import { getHomePage } from '../redux/actions/homePage';
export default function pageTemplate({page}) {
return(
<h1>{page.title}</h1>
)
}
export const getStaticPaths = async () => {
const pages = await getPageData()
const paths = Object.keys(pages).map((key) => {
const page = pages[key]
return{
params: {slug: page.slug.current}
}
})
return{
paths,
fallback: false
}
}
export const getStaticProps = wrapper.getStaticProps((store) => async (context) => {
await store.dispatch(getHomePage());
await store.dispatch(getServiceData());
await store.dispatch(getNavItems());
const slug = context.params.slug
console.log(slug)
const page = await store.dispatch(getPageData(slug))
return {
props: {page},
revalidate: 60
};
}
You can also see my redux action which is working fine as I have tested it in the sanity.io groq playground.
import * as actionTypes from '../actions/actionTypes';
import { groq } from 'next-sanity';
import { getClient } from '../../lib/sanity.server';
export const getPageData = (slug) => async (dispatch) => {
const query = groq`
*[_type == "page"]{
_id,
title,
slug
}
`;
const queryTwo = groq`
*[_type == "page" && slug.current != $slug]{
_id,
title,
slug
}
`;
if(slug) {
try {
// const client = ...
const pageData = await getClient().fetch(query);
dispatch({
type: actionTypes.GET_ALL_PAGES,
payload: pageData
});
} catch (err) {
console.log(err);
}
}
try {
// const client = ...
const pageData = await getClient().fetch(queryTwo);
dispatch({
type: actionTypes.GET_ALL_PAGES,
payload: pageData || pagesData
});
} catch (err) {
console.log(err);
}
};
I have faced the same issue before just releasing that you can use getStaticPaths without the need of the next-redux-wrapper
here is some example from a code that I've been working on lately
import { ReduxWrapper, store } from '#store/store'
export const getStaticPaths = async () => {
const postsSlugs = store
.getState()
.posts.posts.map((post) => ({ params: { slug: post.slug } }))
return {
paths: postsSlugs,
fallback: true,
}
}
export const getStaticProps = ReduxWrapper.getStaticProps(
(store) => async (context) => {
const slug = context.params?.slug
const post = store.getState().posts.post
return {
props: { post },
}
},
)
I hope that it may help you or anyone searching for the same issue

React show data from array of objects

Today I made a useFetch hook to get all the data from a certain category. As you can see on the image it's possible to see all the data in JSON format. Also you can see that it is in an array of objects. I was wondering how I can show this data in normal format like on the page. Most of the time I'm getting the error of data.name NULL. But as you can see the data is fetched correctly in JSON format on the image. I just don't understand how to show all this data normally. Any suggestions?
enter image description here
enter image description here
import React from "react";
import "../Style/menu.css";
import { useParams, withRouter } from "react-router-dom";
import useFetch from "../ApiService/useFetch";
import { render } from "#testing-library/react";
const Product = () => {
const { id } = useParams();
const { data, error, isPending } = useFetch("http://localhost:8080/products/category/" + id);
return (
<p>{JSON.stringify(data)}</p>
)
}
export default Product;
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isLoading, setIsPending] = useState(true);
const [error, setError ] = useState(null);
useEffect(() => {
fetch(url) //custom url so you can reuse it
.then(res => {
if(!res.ok) {
throw Error('could not fetch data');
}
return res.json();
})
.then(data => {
setData(data);
setIsPending(false)
setError(null)
})
.catch(err => {
setError(null)
setIsPending(false)
})
}, [url]);
return {data, isLoading, error} //use properties with custom hook
}
export default useFetch;
This might be helpful for you
...
const Product = () => {
const { id } = useParams();
const { data, error, isPending } = useFetch("http://localhost:8080/products/category/" + id);
return (
{ data && data.length &&
data.map((row) =>{
<p>row.name</p>
})
}
)
}
...
your useFetch is async, as I can see isPending variable, why don't you use this ?
const { id } = useParams();
const { data, error, isPending } = useFetch("http://localhost:8080/products/category/" + id);
return (
<p>{isPending ? null : JSON.stringify(data)}</p>
)

Next.js dynamic routes with Firestore collection

I'm looking for a way to have a dynamic route that displays for every document in a Firestore collection using Server-side Rendering.
For example, a document called foo would exist at test.com/foo under the [doc] page component. Any time a document is added, it should be able to be accessed through its respective URL.
I've tried this method but I haven't been able to get it to work.
I've also tried implementing getServerSideProps but have not had much success, any pointers would be appreciated.
Code from the method above as follows:
under pages/api/[doc].js
export default (req, res) => {
db.collection("docs")
.doc(req.query.name)
.get()
.then((doc) => {
res.json(doc.data());
})
.catch((error) => {
res.json({ error });
});
};
under pages/[shoal].jsx
import { useRouter } from "next/router";
import useSWR from "swr";
const fetcher = async (...args) => {
const res = await fetch(...args);
return res.json();
};
function Doc() {
const router = useRouter();
const { name } = router.query;
const { data } = useSWR(`/api/${name}`, fetcher);
if (!data) {
return "Loading...";
}
return (
<div>
<p>Title: {data.title}</p>
</div>
);
}
export default Doc;
You can try using getServerSideProps:
export const getServerSideProps = async (ctx) => {
const doc = await db.collection("docs").doc(ctx.query.id).get()
const data = doc.data()
if (!data) return { notFound: true };
return { props: { data } };
};
function Doc({data}) {
const router = useRouter();
const { name } = router.query;
if (!data) {
return "Loading...";
}
return (
<div>
<p>Title: {data.title}</p>
</div>
);
}
export default Doc;
Simple solution.
const { data } = useSWR(api ? '/api/${name}' : null, fetcher);
Conditionally fetch the data if your variable is defined, if not, don't pass a URL string, better yet; you can conditionally consider the fetcher for usage also.
const { data } = useSWR(name ? '/api/${name}' : null, name ? fetcher : null);

Why does copies of the same API call returned undefined for one function and works for the other

While trying to use TMDB API in my project I ran into an issue that I am unable to figure out. I use copies of the same code as shown below in two different files and functions - one works, and the other one returned undefined for some reason. Can you please point out what I am not doing right, I need fresh new eyes on this. Thank you
import Head from 'next/head';
import React from 'react';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import styles from '../styles/Home.module.css';
export const getServerSideProps = async () => {
const movieApi = process.env.TMDB_API_KEY;
const res = await fetch(`https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=${movieApi}&page=1`);
const movie_data = await res.json();
return {
props: {
movies : movie_data
},
}
}
const Form = ({movies}) => {
console.log(movies); //returns "Undefined"
const [search, Setsearch] = useState("");
//Handle input value
const getLocation = async (e) => {
// console.log(e.target.value)
e.preventDefault();
}
//Handle Submit
const handleSubmit = (event) =>{
// console.log("clicked")
event.preventDefault();
}
export const getServerSideProps = async () => {
const movieApi = process.env.TMDB_API_KEY;
const res = await fetch(`https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=${movieApi}&page=1`);
const movie_data = await res.json();
return {
props: {
movies : movie_data
},
}
}
export default function Home({movies}) {
console.log(movies); //works perdectly
const [session, loading] = useSession();
const tmdbMpviesResults = movies.results
As per your comment, <Form /> is not a page. Exactly that is your problem:
getServerSideProps can only be exported from a page. You can’t export it from non-page files.

How to handle localstorage with result of a call to an external API in ReactJS?

The main gold is to make a serch bar from an external API. I'm using Context API to provide a global state, and a custom async hook to make a call to a pokeapi, I'm currently available, to store the data searched in localstorage, but the thing is that I store that data from a state that changes in a event, so when I reload the page the state is undefined, and sets the local storage value to undefined... there is a better approach to solve this?
context:
import React,{createContext, useEffect} from 'react'
import { usePokemonReducer } from './PokemonReducer'
import {FIND_POKEMON} from './Actions'
export const PokemonContext = createContext()
const PokemonProvider = ({children}) => {
const [state, dispatch] = usePokemonReducer(()=>{
const localData = localStorage.getItem('pokemons');
return localData ? JSON.parse(localData) : [];
});
const { pokemon } = state;
const findPokemon = (pokemon) => dispatch({ type: FIND_POKEMON, pokemon})
useEffect(() => {
localStorage.setItem('pokemons', JSON.stringify(pokemon.pokemon));
}, [pokemon]);
const providerValues = {
pokemon,
findPokemon,
}
return (
<PokemonContext.Provider value={providerValues}>
{children}
</PokemonContext.Provider>
)
}
export default PokemonProvider;
customAsyncHook:
import {useEffect, useState, useContext} from 'react'
import { PokemonContext } from '../../Services/Store/PokemonContext'
import {FIND_POKEMON} from '../../Services/Store/Actions'
import axios from 'axios'
const useAsyncHook = (id) => {
const [result, setResult] = useState();
const [loading, setLoading] = useState('false');
const { findPokemon } = useContext(PokemonContext)
useEffect(() => {
async function getPokemon() {
try {
setLoading('true');
const response = await axios(
`https://pokeapi.co/api/v2/pokemon/${id}`
);
setResult(response.data);
findPokemon({type:FIND_POKEMON, pokemon:response.data });
} catch (error) {
setLoading('null');
findPokemon({type:FIND_POKEMON, pokemon:null });
}
}
if (id !== "") {
getPokemon();
}
}, [id]);
return [result, loading];
}
export default useAsyncHook
You can just use if condition. if pokemon is undefined, you don't need to set item to localStorage.
useEffect(() => {
if (pokemon.pokemon !== undefined) {
localStorage.setItem('pokemons', JSON.stringify(pokemon.pokemon));
}
}, [pokemon]);

Categories