Next.js dynamic routes with Firestore collection - javascript

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);

Related

Nextjs getStaticPaths not fetching data from firebase firestore

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
}
}
}

How to prevent user for access inner page if not loggged in Using Reactjs

I am working with Reactjs and using nextjs framework,I am working on Login and logout module,I want if any user trying to access inner page without "loggged in" then he should redirect to "index/login" page,How can i do this ? Here is my current code if someone login with correct credentials
const handleSubmit = (e: any) => {
sessionStorage.setItem("email", response.data.email);
const email = sessionStorage.getItem("email");
router.push('/dashboard');
}
I think for checking user authentication it's better to store email or token in cookie and in getServerSideProps check cookie (because you have no access to localStorage in it)
and in getServerSideProps you can access cookie via req.cookies or use cookies package
sth like this :
import Cookies from 'cookies';
export const getServerSideProps = async ({ req, res, locale, ...ctx }) => {
if (req) {
// const posts = axios.get...
const cookies = new Cookies(req, res);
const token = cookies.get('email');
if (!token) {
return {
redirect: {
destination: '/login',
statusCode: 401
}
}
}
return {
props: {
posts,
// your other props
},
};
}
}
and in client code to set cookie you can use js-cookie package
forexample :
import Cookies from 'js-cookie'
const handleSubmit = (e: any) => {
const email = response.data.email
Cookies.set('email', email )
router.push('/dashboard');
}
Well you can do this by holding a context in your whole project or a state or props in specific pages that need permission to view.
So let's assume you have a Dashboard page. You can handle your login in getServerSideProps for better experience and also redirect your user with redirect property before even letting the user to see your page:
export const DashboardPage = (props) => {
console.log(props.data);
return /* ... */;
};
export async function getServerSideProps(context) {
const res = await fetch(`Your Login URL`)
const data = await res.json()
if (!data) {
return {
redirect: {
destination: '/login',
permanent: false,
},
}
}
/* The rest logic of your getServerSideProps */
return {
props: {
data: data,
/* other props */
}
}
}
In this way you should use the logic in every page you have.
Instead you can send the login request from client and set the response to a context value and use that context in every other pages and components.
Context File:
import React, { useState, useEffect } from 'react';
export const UserContext = React.createContext();
export function UserProvider({ children }) {
const [userData, setUserData] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState();
const value = { userData, setUserData, isLoggedIn, setMode };
const handleLogin = async () => {
const res = await fetch(`Your Login URL`);
if(res.status === 200) {
const data = await res.json();
setUserData(data);
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
}
useEffect(() => {
handleLogin();
}, []);
return (
<UserContext.Provider value={value}>
{ children }
</UserContext.Provider>
);
}
And then use the values in your pages, for example Dashboard:
export const DashboardPage = (props) => {
const { isLoggedIn } = useContext(UserContext);
useEffect(() => {
if(isLoggedIn === false) {
router.push('/login');
}
}, [isLoggedIn]);
return /* ... */;
};

How to export value to another const

I'm using react-google-recaptcha to generate invisible ReCaptcha and I need to use the token in another const.
The token is generating correctly, but I don't know how to pass it on to another location. How should I do this?
const onTextSubmit = async () => {
let recaptchaToken;
if (recaptchaLoaded) {
recaptchaToken = await recaptcha.current.execute();
}
// How to export recaptchaToken?
};
I need to get the recaptchaToken and use it here:
const onSubmit: SubmitHandler<FormInput> = (data) => {
formCreateMutation.mutate({
data,
recaptchaToken,
});
};
The two const are in the same file, I'm using react to do that.
Thanks!!
You are not posting all code from the component but you can store the recaptchaToken value in react state like this:
export default function App() {
const [recaptchaToken, setRecaptchaToken] = useState(undefined);
const onTextSubmit = async () => {
if (recaptchaLoaded) {
const value = await recaptcha.current.execute();
setRecaptchaToken(value);
}
// How to export recaptchaToken?
};
const onSubmit: SubmitHandler<FormInput> = (data) => {
if (recaptchaToken) {
formCreateMutation.mutate({
data,
recaptchaToken,
});
}
};
return (
<div className="App">
<h1>{recaptchaToken}</h1>
</div>
);
}

How do I wrap methods in react components

I have a lot of functions looking like this
doSomething = async (...) => {
try {
this.setState({loading: true});
...
var result = await Backend.post(...);
...
this.setState({loading: false});
} catch(err) {
this.setState({error: err});
}
}
Basically I have 2 variables loading & error that I have to manage for a lot of functions and the code is basically the same for all of them. Since there are no decorators in javascript and I do not wish to install any experimental lib for that how could I wrap this function to remove the duplicated setStates from above ?
Here is my current way, I pass the function as parameter.
We have many API, fetch data form backend, we have to handle error and do something with data.
Only data of service are different, the handling error is the same.
private processServiceResponse(resp: any, doSthWithData: (data: any) => void) {
let { errors } = resp;
if (this.hasError(errors)) {
this.handleServiceErr(errors);
return;
}
let data = resp;
if (resp && resp.data) {
data = resp.data;
}
doSthWithData(data);
}
And here is how i pass function as parameter.
let rest1 = service1.getData();
processServiceResponse(rest1,(data)=>{
//only need to focus with processing data.
})
PS: It's typescript coding.
if you are using function conponents, you can define a custom hook to avoid repeat code
//useFetch.js
import { useState, useEffect } from 'react';
import axios from 'axios';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
setLoading('loading...')
setData(null);
setError(null);
const source = axios.CancelToken.source();
axios.get(url, { cancelToken: source.token })
.then(res => {
setLoading(false);
//checking for multiple responses for more flexibility
//with the url we send in.
res.data.content && setData(res.data.content);
res.content && setData(res.content);
})
.catch(err => {
setLoading(false)
setError('An error occurred. Awkward..')
})
return () => {
source.cancel();
}
}, [url])
return { data, loading, error }
export default useFetch;
usage:
import useFetch from './useFetch';
import './App.css';
function App() {
const { data: quote, loading, error } =
useFetch('https://api.quotable.io/random')
return (
<div className="App">
{ loading && <p>{loading}</p> }
{ quote && <p>"{quote}"</p> }
{ error && <p>{error}</p> }
</div>
);
}
export default App;
You can use a Higher Order Function (a function that takes as argument another function) to make the common loading and error functionality reusable. It is very similar to a decorator pattern. For example:
const doSomething = withLoadingAndErrorHandling(Backend.post, this.setState);
function withLoadingAndErrorHandling(fn, setState) {
return async function(...args) {
try {
setState({loading: true});
var result = await fn(args);
setState({loading: false});
return result;
} catch(err) {
setState({error: err});
}
}
}

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>
)

Categories