跳至主要内容

[react] React State 放在 URL 的好處

說明

這篇主要介紹為什麼要考慮把 React 的 state 放在 URL。

影片介紹

使用 useState

假設我們的畫面中有一個功能,是可以讓使用者選取電影名稱並給予評價,選取的電影名稱和評價也會有對應的 style,如下圖:

Image

程式碼如下,我們使用 useState 來記錄使用者選取了哪部電影以及評價為何。

App.jsx
import { useState } from "react";

const movies = ["Toy Story", "Elemental"];

const stars = ["1", "2", "3", "4", "5"];

function App() {
const [movieSelected, setMovieSelected] = useState("");
const [starSelected, setStarSelected] = useState("");

const selectedStyle = (selectedValue, targetValue) =>
selectedValue === targetValue ? "border-blue-500" : "";

return (
<>
<div className="flex flex-col gap-4">
{movieSelected && (
<img
src={poster[movieSelected]}
width={100}
alt="movie poster"
className="m-auto"
/>
)}
<div className="flex gap-2">
{movies.map((movie) => (
<div
key={movie}
className={`${selectedStyle(movie, movieSelected)} movie`}
onClick={() => setMovieSelected(movie)}
>
<p>{movie}</p>
</div>
))}
</div>
<div className="flex gap-2 items-center justify-center">
{stars.map((star) => (
<div
key={star}
onClick={() => setStarSelected(star)}
className={`${selectedStyle(star, starSelected)} star`}
>
<p>{star}</p>
</div>
))}
</div>
</div>
</>
);
}

而如果我們現在想把選取好的結果複製下來,分享出去,或是將網址分享給其他人,讓其他人看到我們選取的結果,你會發現目前沒有辦法做到這個功能,這是因為我們使用了 useState,只要畫面重新整理,state 都會被重置。

使用 useSearchParams

要實作上述提到的功能,我們可以使用 react-router-dom 提供的 useSearchParams 來更改與取得網址的參數(query string),而當使用者選取電影及評價時,就將選取的結果儲存至網址(URL)。

假設網址是 http://127.0.0.1:5173/,當使用者選取電影和評價時,我們可以透過 useSearchParams 將網址設定成 http://127.0.0.1:5173/?movie=Toy+Story&star=5

先將原先使用 useState 記錄的結果換成 useSearchParams,並使用 get 方法來取得網址的參數:

App.jsx
// const [movieSelected, setMovieSelected] = useState("");
// const [starSelected, setStarSelected] = useState("");

const [searchParams, setSearchParams] = useSearchParams({
movie: "",
star: "",
});

const movieSelected = searchParams.get("movie"); //取得 query string 名稱為 movie 的值

const starSelected = searchParams.get("star"); //取得 query string 名稱為 star 的值

接著實作更改網址的 function:

App.jsx
const handleClick = (selected, qsType) => {
setSearchParams(
(prev) => {
// qsType 為 query string 的名稱
// selected 為選取的結果
// 假設 qsType 是 movie,selected 是 Toy Story
// 使用 set 後,網址為變成 xxx://?movie=Toy+Story
prev.set(qsType, selected);
return prev;
},
// replace : true 的意思是不要留下 history,讓使用者無法回到上一步(上一頁)
{ replace: true }
);
};

最後將 function 放到元素的 onClick 內,並傳入 query string 的名稱:

App.jsx
import { useSearchParams } from "react-router-dom";

const movies = ["Toy Story", "Elemental"];

const stars = ["1", "2", "3", "4", "5"];

function App() {
const [searchParams, setSearchParams] = useSearchParams({
movie: "",
star: "",
});

const movieSelected = searchParams.get("movie");

const starSelected = searchParams.get("star");

const handleClick = (selected, qsType) => {
setSearchParams(
(prev) => {
prev.set(qsType, selected);
return prev;
},
{ replace: true }
);
};

const selectedStyle = (selectedValue, targetValue) =>
selectedValue === targetValue ? "border-blue-500" : "";

return (
<>
<div className="flex flex-col gap-4">
{movieSelected && (
<img
src={poster[movieSelected]}
width={100}
alt="movie poster"
className="m-auto"
/>
)}
{movieSelected && <p>Movie Name : {movieSelected}</p>}
<div className="flex gap-2 flex-wrap">
{movies.map((movie) => (
<div
key={movie}
className={`${selectedStyle(movie, movieSelected)} movie`}
onClick={() => handleClick(movie, "movie")}
>
<p>{movie}</p>
</div>
))}
</div>
{starSelected && <p>You give {starSelected} star.</p>}
<div className="flex gap-2 items-center justify-center">
{stars.map((star) => (
<div
key={star}
className={`${selectedStyle(star, starSelected)} star`}
onClick={() => handleClick(star, "star")}
>
<p>{star}</p>
</div>
))}
</div>
</div>
</>
);
}

但最後還是要說一下,並不是所有的 state 都適合放到 URL