ریاکتجیاس - چگونه پارامترها را از یک لینک URL به یک درخواست منتقل کنیم؟
در 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
تابع fetchProducts یک درخواست به API ارسال میکند، با استفاده از متغیرهای productPage و id که به تابع منتقل شدهاند، درخواست مربوطه ارسال میشود و اطلاعات لازم روی صفحه نمایش داده میشود. من قصد دارم صفحه و id را از لینک بگیرم و به تابع fetchProducts منتقل کنم تا اگر چیزی رخ داد، سایت بلافاصله با اطلاعات لازم باز شود.
من از useSearchParams() استفاده میکنم که به وسیله آن یک لینک میسازم که میتواند «برای سایر کاربران ارسال شود». اما نمیفهمم چگونه این را پیادهسازی کنم به طوری که وقتی پارامترها در لینک وارد میشوند، اعمال شوند و صفحه با دادههای لازم بارگذاری شود.
اکنون لینک صحیح ایجاد شده است، اما اگر آن را کپی کرده و در یک پنجره مرورگر دیگر بچسبانید، "لیست محصولات" استاندارد بارگذاری خواهد شد
پاسخ شماره ۱
من قبلاً یک مثال دارم تا به شما بفهمانم چگونه پارامترها را از یک لینک URL به یک درخواست منتقل کنید:
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;
خانه.js
<Link to={`/upd-book/${id}`} >Update</Link>
UpdatePage.js مثال آدرس URL پس از کلیک روی لینک: localhost:3000/upd-book/30
import {useParams} from 'react-router-dom';
const {id} = useParams();
{id} سی ساله است
امیدوارم این مثال توضیح دهد که این چگونه کار میکند.
پاسخ شماره ۲
مسئله
کامپوننت Main دارای «منابع حقیقت» رقیب است، پارامترهای queryString و وضعیت محلی.
راه حل
از پارامترهای queryString به عنوان منبع حقیقت برای درخواستهای API استفاده کنید. به پارامترهای query "page" و "id" در کامپوننت دسترسی پیدا کرده و آنها را به عنوان وابستگیهای hook useEffect و همچنین به هندلر fetchProducts منتقل کنید. به جای صفبندی بروزرسانیهای state، ریدایرکتهای ناوبری را صفبندی کنید که فقط queryString آدرس URL را بروزرسانی میکنند.
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>
);
};
