React でデータ保存する方法Ｘ選

# はじめに
<dl>
  <dt>やっていること：</dt>
  <dd>フロントエンド React で WEB サービスを書いている。</dd>
  <dt>やりたいこと：</dt>
  <dd>useState で管理しているデータを URL 遷移先に渡したい。</dd>
</dl>

[codesandbox.io](https://codesandbox.io/) で色々動かしてみました。
スーパーロング羅列型記事になってしまったので、目次のみご覧いただけると嬉しいです👼

## ストレージを使う
- localStorage
- sessionStorage
```tsx
const data = { message: 'こんにちは', id: 123 };
localStorage.setItem('sharedData', JSON.stringify(data));

const data = JSON.parse(localStorage.getItem('sharedData') || '{}');
```

## クッキーを使う
- react-cookie（js-cookie とか他の選択肢もありそう）
パッケージドキュメント：https://www.npmjs.com/package/react-cookie?activeTab=readme
```tsx
// index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { CookiesProvider } from "react-cookie";

import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <CookiesProvider defaultSetOptions={{ path: "/" }}>
    <StrictMode>
      <App />
    </StrictMode>
  </CookiesProvider>
);

// App.tsx
import React, { useState } from "react";
import { useCookies } from "react-cookie";

interface CookieValues {
  name?: string;
}

function App(): React.ReactElement {
  const [cookies, setCookie] = useCookies<CookieValues>(["name"]);
  const [inputValue, setInputValue] = useState(cookies.name || "");

  // input フォーム管理用の関数。クッキーには関係なし。
  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value);
  };

  // クッキー保存用の関数
  const handleSubmit = () => {
    setCookie("name", inputValue); // 入力された名前をクッキーに保存
  };

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleInputChange}
        placeholder="名前を入れてください"
      />
      <button onClick={handleSubmit}>Save Name</button>

      {/* クッキーに保存された名前があれば表示 */}
      {cookies.name && <h1>Hello {cookies.name}!</h1>}
      {console.log("クッキー", cookies)}
    </div>
  );
}

export default App;
```

## react-router-dom 系を使う

### ~~ヒストリーを使う~~　バージョン6で廃止
- ~~useHistory~~
- ~~useLocation~~
```tsx
```

### ステートを使う
- useNavigate
- useLocation 
```tsx
// App.tsx
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";

export default function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Router>
  );
}

// Home.tsx
import { useNavigate } from "react-router-dom";

function Home() {
  const navigate = useNavigate();

  const goToAboutPage = () => {
    // ページ遷移で情報を渡している部分
    navigate("/about", { state: { message: "これがHomeからAboutに送る情報です。" } });
  };

  return <button onClick={goToAboutPage}>アバウトページに行く</button>;
}

export default Home;

// Abouts.tsx
import { useLocation } from "react-router-dom";

function About() {
  // ページ遷移で情報を受け取っている部分
  const location = useLocation();
  const message = location.state || "情報の送信がされていません。";
  
  return (
    <p>{message}</p>
  );
}

export default About;
```

### URLを使う①
- useNavigate
- useParams 
- （encodeURIComponent、decodeURIComponent）
```tsx
// App.tsx
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";

export default function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        {/* URL を変形 */}
        <Route path="/about/:userName/:id" element={<About />} />
      </Routes>
    </Router>
  );
}

// Home.tsx
import { useNavigate } from "react-router-dom";

function Home() {
  const navigate = useNavigate();

  const userName = "太郎";
  // const encodedUserName = encodeURIComponent(userName); // エンコードしてもOK
  const id = 1;
  const goToAboutPage = () => {
    navigate(`/about/${userName}/${id}`);
  };

  return <button onClick={goToAboutPage}>アバウトページに行く</button>;
}
export default Home;

// About.tsx
import { useParams } from "react-router-dom";

function About() {
  const { userName, id } = useParams();
  // const decodedUserName = decodeURIComponent(userName); // デコードする

  return (
    <div>
      <h1>受信データ</h1>
      <p>名前: {userName || "未設定"}</p>
      <p>ID: {id || "未設定"}</p>
    </div>
  );
}
export default About;
```

### URLを使う②（①とほぼ同じ）
- useNavigate
- useSearchParams 
```tsx
// App.tsx
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";

export default function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        {/* URL 普通でOK */}
        <Route path="/about" element={<About />} />
      </Routes>
    </Router>
  );
}

// Home.tsx
import { useNavigate } from "react-router-dom";

function Home() {
  const navigate = useNavigate();

  const goToAboutPage = () => {
    navigate("/about?userName=太郎&id=1");
  };

  return <button onClick={goToAboutPage}>アバウトページに行く</button>;
}

export default Home;

// About.tsx
import { useSearchParams } from "react-router-dom";

function About() {
  const [searchParams] = useSearchParams();
  const userName = searchParams.get("userName");
  const id = searchParams.get("id");

  // ↑ 上３行コメントアウトして ↓ 下４行コメント外したら useLocation でも同じことができる
  // const location = useLocation();
  // const queryParams = new URLSearchParams(location.search);
  // const userName = queryParams.get("userName");
  // const id = queryParams.get("id");

  return (
    <div>
      <h1>受信データ</h1>
      <p>名前: {userName || "未設定"}</p>
      <p>ID: {id || "未設定"}</p>
    </div>
  );
}

export default About;
```

## コンテキストを使う
- createContext
- useContext
```tsx
// App.tsx
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { DataProvider } from "./DataContext"; // DataProviderをインポート
import Home from "./Home";
import About from "./About";

export default function App() {
  return (
    {/* DataProvider で全体をラップ */}
    <DataProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Router>
    </DataProvider>
  );
}

// DataContext.tsx
import { createContext, useState, ReactNode } from "react";

const DataContext = createContext<
  | {
      data: string | null;
      setData: React.Dispatch<React.SetStateAction<string | null>>;
    }
  | undefined
>(undefined);

function DataProvider({ children }: { children: ReactNode }) {
  const [data, setData] = useState<string | null>(null);

  return (
    <DataContext.Provider value={{ data, setData }}>
      {children}
    </DataContext.Provider>
  );
}

export { DataContext, DataProvider };

// Home.tsx
import React, { useContext } from "react";
import { useNavigate } from "react-router-dom";
import { DataContext } from "./DataContext";

function Home() {
  const { setData } = useContext(DataContext);
  const navigate = useNavigate();

  const goToAboutPage = () => {
    setData({ id: 123, name: "example" });
    navigate("/about");
  };

  return <button onClick={goToAboutPage}>アバウトページに行く</button>;
}

export default Home;

// About.tsx
import React, { useContext } from "react";
import { DataContext } from "./DataContext";

function About() {
  const context = useContext(DataContext);
  const data = context?.data || { name: "未設定", id: "未設定" };

  return (
    <div>
      <h1>受信データ</h1>
      <p>名前: {data.name}</p>
      <p>ID: {data.id}</p>
    </div>
  );
}

export default About;
```

## zustand を使う
- zustand
```tsx
// store.js
import { create } from "zustand";

// Zustandストアの作成
const useStore = create((set) => ({
  data: { id: null, name: "" },
  setData: (newData) => set({ data: newData }),
}));

export default useStore;

// Home.js
import React from "react";
import { useNavigate } from "react-router-dom";
import useStore from "./store";

function Home() {
  const setData = useStore((state) => state.setData);
  const navigate = useNavigate();

  const goToAboutPage = () => {
    setData({ id: 123, name: "example" }); // Zustandでデータを更新
    navigate("/about");
  };

  return <button onClick={goToAboutPage}>アバウトページに行く</button>;
}

export default Home;

// About.js
import React from "react";
import useStore from "./store";

function About() {
  const data = useStore((state) => state.data); // Zustandからデータを取得

  return (
    <div>
      <h1>受信データ</h1>
      <p>名前: {data.name}</p>
      <p>ID: {data.id}</p>
    </div>
  );
}

export default About;
```

## react-query を使う
```tsx
// App.tsx
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "react-query";
import Home from "./Home";
import About from "./About";

// React Query のクライアントを作成
const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Router>
    </QueryClientProvider>
  );
}

export default App;

// Home.js
import React from "react";
import { useQuery } from "react-query";
import { useNavigate } from "react-router-dom";

// API呼び出し用の関数
const fetchUserData = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  if (!response.ok) {
    throw new Error("ネットワークエラー");
  }
  return response.json();
};

function Home() {
  const navigate = useNavigate();

  // useQuery を使用して API からデータを取得
  const { data, isLoading, error } = useQuery("userData", fetchUserData);

  if (isLoading) {
    return <div>データ読み込み中...</div>;
  }

  if (error) {
    return <div>エラーが発生しました: {error.message}</div>;
  }

  // 取得したデータを表示
  return (
    <div>
      <p>名前: {data.name}</p>
      <p>メール: {data.email}</p>
    </div>
  );
}

export default Home;
```

## Redux を使う
```tsx
// App.tsx
import React from "react";
import { Provider } from "react-redux";
import { store } from "./store";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";

function App() {
  return (
    <Provider store={store}>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Router>
    </Provider>
  );
}

export default App;

// store.js
import { configureStore, createSlice } from "@reduxjs/toolkit";

const initialState = { name: "なまえ", id: "1" };

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    setUser: (state, action) => {
      state.name = action.payload.name;
      state.id = action.payload.id;
    },
  },
});

export const { setUser } = userSlice.actions;
export const store = configureStore({
  reducer: {
    user: userSlice.reducer,
  },
});

// Home.tsx
import React from "react";
import { useDispatch } from "react-redux";
import { setUser } from "./store";
import { useNavigate } from "react-router-dom";

function Home() {
  const dispatch = useDispatch();
  const navigate = useNavigate();

  const data = { name: "なまえ", id: "1" };

  const goToAboutPage = () => {
    dispatch(setUser(data));
    navigate("/about");
  };

  return (
    <div>
      <button onClick={goToAboutPage}>アバウトページに行く</button>
    </div>
  );
}

export default Home;

// About.tsx
import React from "react";
import { useSelector } from "react-redux";

function About() {
  const user = useSelector((state) => state.user);

  return (
    <div>
      <p>名前: {user.name}</p>
      <p>ID: {user.id}</p>
    </div>
  );
}

export default About;
```

## その他調べきれなかったもの
[そもそも React ページ遷移に関連するか微妙なもの]
- POSTリクエストを使ってバックエンドにデータを送信＆DBに保存
- 親コンポーネントで管理

[ブラウザの機能使う系]
- ブラウザのカスタムイベントを使う
- グローバルな window オブジェクトにデータを一時的に保存
- window.location.hashを使う
- WebSocketやカスタムイベントを使う

[DB使う系]
- シングルトン風のオブジェクトを作りデータを一時的に保存
- IndexedDBを使う

[パッケージインストール系]
- cache.writeQuery、cache.readQueryを使う
- jotaiを使う
- valtioを使う
- mobxを使う
- recoilを使う
- xstateを使う
- remixを使う
- nanostoresを使う

# おわりに
**（（（　えっ、React の SPA とかって何？　）））**
URL でページ遷移させたらダメってこと？