fetching data in React with DynamicRoutes, nextJS and useEffect - reactjs

I have a blog link. Link is dynamic route with blog id. It's the Link wrapper from Next.
//link
<h3 className="blogTitle">
<Link href="[blog]" as={props.item.postingId}>{props.item.title}</Link>
</h3>
Now I want to pass "blog id" to the component and to present data in a new page.
//page where link leads to
const ad = () => {
const router = useRouter()
const {
query: {blog},
} = router
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
console.log('....outside useEffect log', blog)
useEffect(() => {
console.log('useEffect consolelog', blog);
axios.get('httpwww.blogapiadress.com/'+ ad)
.then(response => setData(response.data))
.then(setLoading(false))
}, [])
return(
<Container fluid className="padding0">
/// data should be here.
</Container>
);
}
export default ad;
Problem: in useEffect console.log('blog', blog) returns undefined, so router does not return value from query. However, outside of useEffect it does. How to solve that issue, I want to fetch data related to the router query?
Since axios is getting undefined instead of blog id, I am getting 404.

You can use getStaticProps() to fetch the blog data at build time.
Example:
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// Access route params:
const blog = context.query.blog // or context.params.blog for parametrized routes
return {
const res = await fetch('https://...')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
More info on NextJS docs.

I don't think you need to use the global window object to access dynamic data related to your route -- you should be able to use the Next router. I think the way you are defining href -- the only required prop for Link is causing issues. Looking at docs and your current exampel you probably want to use something like:
<Link
href={{
pathname: '/[blog]',
query: { blog: props.item.postingId },
}}
>
<a>{props.item.title}</a>
</Link>
// or
<Link href={`/${encodeURIComponent(props.item.postingId)}`}>
<a>{props.item.title}</a>
</Link>
Then you should be able to properly access [blog] (i.e., your postingId) using Router. For example, if your route was defined dynamically by /[blog].js, you could use the following:
import { useRouter } from 'next/router'
const ad = () => {
const router = useRouter()
const { blog } = router.query
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(()=>{
axios.get('httpwww.blogapiadress.com/'+ blog)
.then(response => {
setData(response.data)
setLoading(false)
})
}, [])
if (loading || !data) return <div> Loading... </div>
return(
<Container fluid className="padding0">
/// Render data
</Container>
);
}
export default ad;

Looking for answer for a few hours, and when I posted question on stack.. I figured out the answer.
So problem was that query is empty with next static generation at build time https://nextjs.org/docs/api-reference/next/router#router-object
I havent found best solution, but i found working one.
I got blog id from the windows.location href
useEffect(()=>{
const last = window.location.href.split('/').pop();
console.log('last', last)
axios.get('https://blogpostings.com/'+last)
.then(response => setData(response.data))
}, [])
I am not sure if its proper or good way, but it works.
I hope someone will find this helpful.

If you want get the query parameter from the link, you need to insert at the end of the url: https://sample-link.com?blog=123
Then same as your code above:
import { useRouter } from 'next/router'
const router = useRouter()
const {
query: {blog}
} = router;
console.log("blog: ", blog)
Result:
blog: 123

Related

Why does react-query query not work when parameter from router.query returns undefined on refresh?

When page is refreshed query is lost, disappears from react-query-devtools.
Before Next.js, I was using a react and react-router where I would pull a parameter from the router like this:
const { id } = useParams();
It worked then. With the help of the, Next.js Routing documentation
I have replaced useParams with:
import { usePZDetailData } from "../../hooks/usePZData";
import { useRouter } from "next/router";
const PZDetail = () => {
const router = useRouter();
const { id } = router.query;
const { } = usePZDetailData(id);
return <></>;
};
export default PZDetail;
Does not work on refresh. I found a similar topic, but manually using 'refetch' from react-query in useEffects doesn't seem like a good solution. How to do it then?
Edit
Referring to the comment, I am enclosing the rest of the code, the react-query hook. Together with the one already placed above, it forms a whole.
const fetchPZDetailData = (id) => {
return axiosInstance.get(`documents/pzs/${id}`);
};
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {});
};
Edit 2
I attach PZList page code with <Link> implementation
import Link from "next/link";
import React from "react";
import TableModel from "../../components/TableModel";
import { usePZSData } from "../../hooks/usePZData";
import { createColumnHelper } from "#tanstack/react-table";
type PZProps = {
id: number;
title: string;
entry_into_storage_date: string;
};
const index = () => {
const { data: PZS, isLoading } = usePZSData();
const columnHelper = createColumnHelper<PZProps>();
const columns = [
columnHelper.accessor("title", {
cell: (info) => (
<span>
<Link
href={`/pzs/${info.row.original.id}`}
>{`Dokument ${info.row.original.id}`}</Link>
</span>
),
header: "Tytuł",
}),
columnHelper.accessor("entry_into_storage_date", {
header: "Data wprowadzenia na stan ",
}),
];
return (
<div>
{isLoading ? (
"loading "
) : (
<TableModel data={PZS?.data} columns={columns} />
)}
</div>
);
};
export default index;
What you're experiencing is due to the Next.js' Automatic Static Optimization.
If getServerSideProps or getInitialProps is present in a page, Next.js
will switch to render the page on-demand, per-request (meaning
Server-Side Rendering).
If the above is not the case, Next.js will statically optimize your
page automatically by prerendering the page to static HTML.
During prerendering, the router's query object will be empty since we
do not have query information to provide during this phase. After
hydration, Next.js will trigger an update to your application to
provide the route parameters in the query object.
Since your page doesn't have getServerSideProps or getInitialProps, Next.js statically optimizes it automatically by prerendering it to static HTML. During this process the query string is an empty object, meaning in the first render router.query.id will be undefined. The query string value is only updated after hydration, triggering another render.
In your case, you can work around this by disabling the query if id is undefined. You can do so by passing the enabled option to the useQuery call.
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {
enabled: id
});
};
This will prevent making the request to the API if id is not defined during first render, and will make the request once its value is known after hydration.

how can I refetch with getServerSideProps in next on a click on client side?

I am using next.js, and trying to refresh the page with SSR data on a click of a button, doing like so:
import type { NextPage } from 'next'
import { useState } from 'react'
type HomeProps = NextPage & {
data: any
}
const Home = ({data}: HomeProps) => {
const [index, setIndex] = useState(data)
const handleClick = async() => {
const res = await fetch(`https://fakerapi.it/api/v1/companies?_quantity=2`)
const data= await res.json()
setIndex(data)
}
return (
<div>
{data.data.map(el => (
<div key={el.id}>{el.name}</div>
))}
<button onClick={handleClick}>next</button>
</div>
)
}
export async function getServerSideProps(){
const res = await fetch(`https://fakerapi.it/api/v1/companies?_quantity=1`)
const data= await res.json()
return{ props:{data}}
}
export default Home
I am getting the result of the first API call when next renders the page the first time, but when I click on the button, even though I am getting the result from the API call, the page does not refresh... Even thought I am using useState which should force the page to refresh.
Because of the way getServerSideProps works, you could refresh the data on the client-side using router object.
For example, when you click your button it could call a function to programmatically navigate to that same page using: router.replace(router.asPath).
This works because since getServerSideProps runs on every request, and you're already on the client-side and doing a navigation to a SSR page, instead of generating an HTML file, it will send the data as JSON to the client.
This is not a very good solution UX wise tho, but if used correctly it can be very handy.
oops my bad, i was not printing out the result of the useState, here is the proper change in the return of the function :
return (
<div>
{index.data.map(el => (
<div key={el.id}>{el.name}</div>
))}
<button onClick={handleClick}>next</button>
</div>
)

How to fetch data in React blog app and stay DRY?

The question is simple. How to fetch data in your React blog and stay DRY? Let's say that you have just two components in your blog - PostsList and SinglePost, in both components you must fetch data, activate isLoading state, etc. There will be chunks of the same code in both components.
I investigated the situation a little bit, checking React-blog demo apps of big headless CMS providers, like Prismic or Sanity.io, and they all just repeat fetch functions in both PostsList and SinglePost.
Does anybody have any idea? You can point me to some good resources?
You can achieve this by using High Order Components. You can use them for reusing component logic. Let me show you an example of how to handle the isLoading with a HOC:
HOC:
import React, { useState } from 'react'
const hocLoading = (WrappedComponent, loadingMessage) => {
return props => {
const [ loading, setLoading ] = useState(true)
const setLoadingState = isComponentLoading => {
setLoading(isComponentLoading)
}
return(
<>
{loading && <p>{loadingMessage}</p>} //message showed when loading
<WrappedComponent {...props} setLoading={setLoadingState} />
</>
)
}
}
export default hocLoading
As you can see this HOC is receiving the WrappedComponent and a message that you can set depending on your component. Then you will have to wrap every component where you want to show the loading feedback with the HOC and you can use the setLoading prop to stop showing the loading feedback:
const Component = props => {
const { setLoading } = props
useEffect(() => {
const loadUsers = async () => {
await fetchData() // fetching data
setLoading(false) // this function comes from the HOC to set loading false
}
loadUsers()
},[ ])
return (
<div className="App">
{usuarios.data.map(x => <p key={x.id}>{x.title}</p>)}
</div>
);
}
export default hocLoading(Component, "Data is loading") //component wrapped
// with the HOC and setting feedback message
This way you avoid repeating this process for every component. Regarding the data fetching you can create a Hook or a function that receives dynamic params so you can just call something like fetchData(url). Here is an example of a dynamic function for making request using axios:
const baseUrl = "" //your BASE URL
async function request(url,method,data){
try {
const response = await axios({
method,
url: `${baseUrl}${url}`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
data: data ? data : undefined
})
return response
} catch (e) {
// handle error
}
}

Can't render data from API being passed down as props (ReactJS)

I'm really stuck in trying to render some data being passed down as props. I'll include some code and definitions below, but if you feel that I need to include some further code snippets, please let me know (I'm really struggling to find what's causing the error, so I may have missed out the causal issue!).
I first take data from an API which is then used to populate a UserList component via useState (setUsers(data):
useEffect(() => {
async function getUserList() {
setLoading(true);
try {
const url =
"API URL";
const response = await fetch(url);
const data = await response.json();
setUsers(data);
} catch (error) {
throw new Error("User list unavailable");
}
setLoading(false);
}
getUserList();
}, []);
If a user is clicked in the UserList, this changes the selectedUser state of the parent Home component to be the specific user's unique_ID via:
onClick={() => setSelectedUser(unique_ID)}
If the selectedUser changes, the Home component also does a more updated data fetch from the API to get all information relevant to the specific user via their unique_ID:
useEffect(() => {
async function getSelectedUserData() {
try {
const url = `API URL/${selectedUser}`;
const response = await fetch(url);
const data = await response.json();
setSelectedUserData(data);
} catch (error) {
throw new Error("User data unavailable");
}
}
getSelectedUserData();
}, [selectedUser]);
The specific user data is then passed down as props to a child UserInformation component:
<UserInformation selectedUser={selectedUser} selectedUserData={selectedUserData} />
At this point, I can see all the data being passed around correctly in the browser React Developer Tools.
The UserInformation component then gets the data passed via props:
import React, { useEffect, useState } from "react";
function UserInformation({ selectedUser, selectedUserData }) {
const [currentUser, setCurrentUser] = useState({ selectedUserData });
useEffect(() => {
setCurrentUser({ selectedUserData });
}, [selectedUser, selectedUserData]);
return (
<div>
<p>{selectedUserData.User_Firstname}</p>
<p>{currentUser.User_Firstname}</p>
</div>
);
}
export default UserInformation;
And here is where I get stuck - I can't seem to render any of the data I pass down as props to the UserInformation component, even though I've tried a few different methods (hence the <p>{selectedUserData.User_Firstname}</p> and <p>{currentUser.User_Firstname}</p> to demonstrate).
I'd really appreciate any help you can give me with this - I must be making an error somewhere!
Thanks so much, and sorry for the super long post!
I managed to solve this (thanks to the help of Mohamed and Antonio above, as well as the reactiflux community).
import React from "react";
function UserInformation({ selectedUserData }) {
const currentUserRender = selectedUserData.map(
({ User_Firstname, User_Lastname }) => (
<div key={unique_ID}>
<p>{User_Firstname}</p>
</div>
)
);
return (
<div>
{selectedUserData ? currentUserRender : null}
</div>
);
}
export default UserInformation;
As selectedUserData was returning an array instead of an object, I needed to map the data rather than call it with an object method such as {selectedUserData.User_Firstname}.
const currentUserRender = selectedUserData.map(
({ User_Firstname, User_Lastname }) => (
<div key={unique_ID}>
<p>{User_Firstname}</p>
</div>
)
);
The above snippet maps the selected data properties found inside selectedUserData ({ User_Firstname, User_Lastname }), with the whole map being called in the return via {selectedUserData ? currentUserRender : null}.
Hopefully my explanation of the above solution is clear for anyone reading, and a big thanks again to Mohamed and Antonio (as well as a few others in the reactiflux Discord community) for helping!
You're trying to set the current user to an object with key "selectedUserData".
So if you want to access it you've to access it by this key name so change this line currentUser.User_Firstname to currentUser.selectedUserData.User_Firstname

Why It Renders 'Undefined' first And Then Display The Object In The Log [duplicate]

This question already has an answer here:
Why does useState cause the component to render twice on each update?
(1 answer)
Closed 2 years ago.
How should I fix my React App which renders two logs with different results even though the code has only one console.log in the application? Yes, I removed <React.StrictMode> in my index.js file because it also trigger renders twice. In the terminal the first log is an undefined and the second one has the object with data, because of that when I use array map method, it keeps saying " BookDetail.jsx:26 Uncaught TypeError: bookData.map is not a function" .
I'm trying to fetch the detailed information about a book from the firebase database. My goal is very simple, a frontend user clicks a title of book and it takes to the detailed page. All the data stores in firestore database. The detailed page code is below, hoping somebody can help me out, thank you!
import React, {useState, useEffect} from 'react'
import {Link} from 'react-router-dom'
import firebase from '../config/fbConfig'
const BookDetails = (props) => {
const id = props.match.params.id
const db = firebase.firestore()
const [books, setBooks] = useState('')
useEffect(() => {
db.collection("books")
.doc(id)
.get()
.then(doc => doc.data())
.then(data => setBooks(data))
},[])
const bookData = {books}
return (
<div className="book_details">
<Link to="/"><h2>Home</h2></Link>
{console.log(bookData)}
<h1>The Summary Of the Book </h1>
{bookData.map(book => <div key={book.id}> {book.brief} </div>)}
</div>
)
}
export default BookDetails
Per ReactJS documentation, it's recommended to call hooks at top level of component: https://reactjs.org/docs/hooks-rules.html
So it looks like when your component mounts it calls your custom useBooks hook.
It initializes books state and then executes the useEffect. However, I think calling to the Firestore db function is an async process and because you're not waiting for the response, your function is returning undefined.
It seems like you may not need your custom hook. Set up useState and useEffect at the top level.
const BookDetails = (props) => {
const id = props.match.params.id
const db = firebase.firestore()
const [books, setBooks] = useState([])
useEffect(() => {
const fetchBooks = () => {
db.collection("books")
.doc(id)
.get()
.then(doc => doc.data())
.then(data => setBooks(data))
}
fetchBooks()
},[])
return (
<div className="book_details">
<Link to="/"><h2>Home</h2></Link>
{console.log(books.title)}
<h1>The Summary Of the Book </h1>
{books && books.map(book => <div key={book.id}> {book.brief} </div>)}
</div>
)
}
export default BookDetails
const data = doc.data() appears to be an async function, so you may want to chain another .then for setBooks

Resources