はじめに
Reactを触り始めて、3日目。。。
備忘もかねて投稿します。
React素人のため、情報、書き方に誤りがあるかもしれません。
その場合はコメント等で指摘していただけると幸いです。
環境
React 18.3.1
概要
カスタムプロバイダーを利用したコンテキスト経由でのデータの公開と、
ステートの管理について把握するためのサンプルです
映画レーティング
ユーザーは、映画の削除、追加、レーティングが行える
映画データ
color-data.json
id | 映画を識別する一意の値 |
---|---|
title | 映画名 |
rating | 評価 |
[
{
"id": "0175d1f0-a8c6-41bf-8d02-df5734d829a4",
"title": "Jurassic Park",
"rating": 0
},
{
"id": "83c7ba2f-7392-4d7d-9e23-35adbe186046",
"title": "Star Wars",
"rating": 0
},
]
各種コンポーネント
MovieProvider
ステート(映画データ)を保持し、カスタムプロバイダーとして機能する
カスタムプロバイダーはデータの公開、データの変更を可能にする
データ変更用のAPIを公開し、子コンポーネントはこのAPIを利用してデータの変更を行う
import React, { createContext, useState, useContext } from "react";
import movieData from "./data/movie-data.json";
// コンテキストを初期化
const MovieContext = createContext();
// コンテキストのフック
export const useMovies = () => useContext(MovieContext);
export default function MovieProvider({ children }) {
const [movies, setMovies] = useState(movieData);
// 子コンポーネントに公開されるAPI
const addMovie = (title) =>
setMovies(...movies, {
id: v4(),
rating: 0,
title,
});
const removeMovie = (id) =>
setMovies(movies.filter((movie) => movie.id !== id));
const rateMovie = (id, rating) =>
setMovies(
movies.map((movie) => (movie.id === id ? { ...movie, rating } : movie))
);
return (
<MovieContext.Provider
value={{ movies, addMovie, removeMovie, rateMovie }}
>
{children}
</MovieContext.Provider>
);
}
MovieList
MovieProvierで読み込まれた映画データをuseMovies()より参照
import React from "react";
import { useMovies } from "./MovieProvider";
import Movie from "./Movie";
export default function MovieList() {
const { movies } = useMovies();
if (!movies.length) return <div>No Movie Listed.</div>;
return (
<div>
{movies.map((movie) => (
<Movie key={movie.id} {...movie} />
))}
</div>
);
}
Movie
Providerで定義された各APIを参照、利用しデータの変更、更新を行う
import React from "react";
import { useMovies } from "./MovieProvider";
import StarRating from "../StarRating";
export default function Movie({ id, title, rating }) {
const { rateMovie, removeMovie } = useMovies();
return (
<section>
<h1>{title}</h1>
<StarRating
selectedStars={rating}
onRate={(rating) => rateMovie(id, rating)}
/>
<button onClick={() => removeMovie(id)}>Delete</button>
</section>
);
}
StarRating
import React, { useState } from "react";
import Star from "./Stars.js";
export default function StarRating({
totalStars = 5,
selectedStars = 0,
onRate = (f) => f,
}) {
return (
<>
{[...Array(totalStars)].map((n, i) => (
<Star
key={i}
selected={selectedStars > i}
onSelect={() => onRate(i + 1)}
/>
))}
<p>
{selectedStars} of {totalStars} stars
</p>
</>
);
}
Star
import React from "react";
import { FaStar } from "react-icons/fa";
export default function Star({ selected = false, onSelect = (f) => f }) {
return <FaStar color={selected ? "red" : "grey"} onClick={onSelect} />;
}
AddMovieForm
Providerで定義された各APIを参照、利用し新規データを追加する
import React from "react";
import { useInput } from "../hook";
import { useMovies } from "./MovieProvider";
export default function AddMovieForm() {
const [titleProps, resetTitle] = useInput("");
const { addMovie } = useMovies();
const submit = (e) => {
e.preventDefault();
addMovie(titleProps.value);
resetTitle();
};
return (
<form onSubmit={submit}>
<input
{...titleProps}
type="text"
placeholder="Movie title..."
required
/>
<button>ADD</button>
</form>
);
}
App
メインコンポーネント
import MovieList from "./movie/MovieList.js";
import AddMovieForm from "./movie/AddMovieForm.js";
function App() {
return (
<>
<AddMovieForm />
<MovieList />
</>
);
}
index.js
メインコンポーネントAppを、
カスタムプロバイダーMovieProviderでラップすることにより、
その配下のコンポーネントからmovieDataおよび、各種データ変更用のAPIにアクセスできる
import React, { createContext } from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import MovieProvider from "./movie/MovieProvider";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<MovieProvider>
<App />
</MovieProvider>
</React.StrictMode>
);
参考