In my Main.tsx:
import React, { FC, useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useAppDispatch, useAppSelector } from '../../hook'
import { getProducts } from '../../store/ProductsSlice'
import Filter from '../Filter/Filter'
import Pagination from '../Pagination/Pagination'
import Products from '../Products/Products'
import { ErrorMessage, FilterError } from './styled'
const Main: FC = () => {
const products = useAppSelector((state) => state.products.list)
const dispatch = useAppDispatch()
const [errorId, setErrorId] = useState<string>('')
const [errorMessage, setErrorMessage] = useState<string>('')
const [page, setPage] = useState<number>(1)
const [filterId, setFilterId] = useState<number>()
const [pageParams, setPageParams] = useSearchParams()
pageParams.get(`page`) || ''
pageParams.get(`id`) || ''
useEffect(() => {
async function fetchProducts(id?: number, productsPage = 1) {
const itemsPerPage = 5
let url: string
if (id) {
url = `https://reqres.in/api/products/${id}`
} else {
url = `https://reqres.in/api/pr231oducts?per_page=${itemsPerPage}&page=${productsPage}`
}
const requestOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}
fetch(url, requestOptions)
.then(async (response) => {
const data = await response.json()
if (response.ok) {
setErrorId('')
setErrorMessage('')
if (id) {
dispatch(
getProducts({
page: 1,
per_page: 1,
total: 1,
total_pages: 1,
data: [data.data],
})
)
setPageParams({ page: `1`, id: `${id}` })
} else {
dispatch(getProducts(data))
setPageParams({ page: `${productsPage}` })
}
} else {
const error = (data && data.message) || response.status
return Promise.reject(error)
}
setErrorMessage(data.id)
})
.catch((error) => {
setErrorId(error.toString())
console.error('There was an error!', error)
})
}
fetchProducts(filterId, page)
}, [filterId, page])
return (
<div>
{!products ? (
<>
{errorId ? <ErrorMessage>{errorId}</ErrorMessage> : null}
{errorMessage ? (
<ErrorMessage>
Something went wrong
{errorMessage}
</ErrorMessage>
) : null}
</>
) : (
<>
<Filter setFilterId={setFilterId} />
{errorId ? (
<FilterError>
{errorId}:
{errorId === '404'
? ' Product not found'
: `${errorId}: ${errorMessage}`}
</FilterError>
) : (
<Products />
)}
<Pagination setPage={setPage} />
</>
)}
</div>
)
}
export default Main
Filter.tsx:
import React, { FC } from 'react'
import { FilterContainer, FilterInput } from './styled'
const Filter: FC<{
setFilterId: React.Dispatch<React.SetStateAction<number | undefined>>
}> = ({ setFilterId }) => {
return (
<FilterContainer>
<FilterInput
onChange={(e) => {
if (e.target.value === '0') {
e.target.value = ''
}
setFilterId(Number(e.target.value))
}}
placeholder="Search by id"
type="number"
/>
</FilterContainer>
)
}
export default Filter
Pagination.tsx:
import { FC } from 'react'
import { useAppSelector } from '../../hook'
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'
import { PaginationBtn, PaginationContainer } from './styled'
const Pagination: FC<{
setPage: React.Dispatch<React.SetStateAction<number>>
}> = ({ setPage }) => {
let pageNumber = useAppSelector((state) => state.products.list.page)
const totalPages = useAppSelector((state) => state.products.list.total_pages)
return (
<PaginationContainer>
<PaginationBtn
onClick={() => {
setPage((pageNumber -= 1))
}}
disabled={pageNumber <= 1}
>
<ArrowBackIosIcon fontSize="large" />
</PaginationBtn>
<PaginationBtn
onClick={() => {
setPage((pageNumber = 1))
}}
disabled={pageNumber >= totalPages}
>
<ArrowForwardIosIcon fontSize="large" />
</PaginationBtn>
</PaginationContainer>
)
}
export default Pagination
The fetchProducts function makes a request to the API, using the productPage and id variables passed to the function, the corresponding request is sent and the necessary information is displayed on the screen. I'm going to take the page and id from the link and pass them to the fetchProducts function so that if something happens, the site opens immediately with the necessary information.
I have useSearchParams() with which I make a link that can be "sent to other users". But I don’t understand how to implement that when parameters are inserted into the link, they are applied and the page with the necessary data is loaded.
Now the correct link is generated, but if you copy it and paste it in another browser window, the standard "list of products" will be loaded
CodePudding user response:
I have already an exemple for make you understand How to pass parameters from a URL link to a request:
App.js
function App() {
return (
<>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/add-book" element={<AddBook />} />
<Route path="/upd-book/:id" element={<UpdBook />} />
</Routes>
</BrowserRouter>
</>
)
}
export default App;
Home.js
<Link to={`/upd-book/${id}`} >Update</Link>
UpdatePage.js exemple url after you click to Link: localhost:3000/upd-book/30
import {useParams} from 'react-router-dom';
const {id} = useParams();
{id} is 30
I hope this exemple explain how this is work.
CodePudding user response:
Issue
The Main
component has competing "sources of truth", the queryString params and local state.
Solution
Use the queryString params as the source of truth for the API requests. Access the "page"
and "id"
query params in the component and pass as useEffect
hook dependencies and on to the fetchProducts
handler. Instead of enqueuing state updates enqueue navigation redirects that only update the URL queryString.
const Main: FC = () => {
const dispatch = useAppDispatch();
const products = useAppSelector((state) => state.products.list);
const [searchParams, setSearchParams] = useSearchParams();
// Read the queryString parameters, convert to number type
const page = Number(searchParams.get("page") || 1);
const filterId = Number(searchParams.get("id"));
const [errorId, setErrorId] = useState<string>('');
const [errorMessage, setErrorMessage] = useState<string>('');
useEffect(() => {
async function fetchProducts(id?: number, page: number = 1) {
const itemsPerPage = 5;
const url = id
? `https://reqres.in/api/products/${id}`
: `https://reqres.in/api/pr231oducts?per_page=${itemsPerPage}&page=${page}`;
const requestOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}
try {
const response = await fetch(url, requestOptions);
const data = await response.json();
if (response.ok) {
setErrorId('');
setErrorMessage('');
if (id) {
dispatch(
getProducts({
page: 1,
per_page: 1,
total: 1,
total_pages: 1,
data: [data.data],
})
);
setSearchParams({ page, id }, { replace: true });
} else {
dispatch(getProducts(data));
setPageParams({ page }, { replace: true });
}
} else {
const error = data?.message || response.status;
return Promise.reject(error);
}
setErrorMessage(data.id);
} catch(error) {
setErrorId(error.toString());
console.error('There was an error!', error);
}
};
fetchProducts(filterId, page);
}, [filterId, page]);
// Callbacks to update the queryString parameters
const setPage = page => setSearchParams(params => {
params.set("page", page);
return params;
}, { replace: true });
const setFilterId = id => setSearchParams(params => {
params.set("id", id);
return params;
}, { replace: true });
return (
<div>
{!products ? (
<>
{errorId ? <ErrorMessage>{errorId}</ErrorMessage> : null}
{errorMessage && (
<ErrorMessage>
Something went wrong
{errorMessage}
</ErrorMessage>
)}
</>
) : (
<>
<Filter setFilterId={setFilterId} />
{errorId ? (
<FilterError>
{errorId}:
{errorId === '404'
? "Product not found"
: `${errorId}: ${errorMessage}`
}
</FilterError>
) : (
<Products />
)}
<Pagination setPage={setPage} />
</>
)}
</div>
);
};