グローバルなstate管理

グローバルなstate管理

グローバルなstate管理あり・なしで管理者の時は編集リンクを表示させ、
一般ユーザの時は編集リンクを表示させないことを実装する

ツラい実装

まずはグローバルなstate管理なしで管理者の時は編集リンクを表示させ、
一般ユーザの時は編集リンクを表示させないことを実装する

管理者ユーザと一般ユーザ用のボタンを用意する

import { useHistory } from "react-router-dom";
import styled from "styled-components";
import { SecondaryButton } from "../atoms/button/SecondaryButton";

export const Top = () => { const history = useHistory();

const onClickAdmin = () => history.push({ pathname: "/users", state: { isAdmin: true } }); const onClickGeneral = () => history.push({ pathname: "/users", state: { isAdmin: false } });

return ( <SContainer> <h2>TOPページです</h2> <SecondaryButton onClick={onClickAdmin}>管理者ユーザ</SecondaryButton> <br /> <br /> <SecondaryButton onClick={onClickGeneral}>一般ユーザ</SecondaryButton> </SContainer> ); };

const SContainer = styled.div text-align: center; ;

SecondaryButtonはonClickを受け取れるようになっている

import styled from "styled-components";
import { BaseButton } from "./BaseButton";

export const SecondaryButton = (props) => { const { children, onClick } = props;

return <SButton onClick={onClick}>{children}</SButton>; };

const SButton = styled(BaseButton) background-color: #11999e; ;

ページ遷移時のstateをuseLocationで取得

import { useLocation } from "react-router-dom";
import styled from "styled-components";
import { SearchInput } from "../molecules/SearchInput";
import { UserCard } from "../organisms/user/UserCard";

const users = [...Array(10).keys()].map((val) => { return { id: val, name: Bob$<span class="synIdentifier">{</span>val<span class="synIdentifier">}</span>, image: "https://source.unsplash.com/2l0CWTpcChI", email: "12345@example.com", phone: "090-1111-2222", company: { name: "テスト株式会社" }, website: "https://google.com" }; });

export const Users = () => { const { state } = useLocation(); const isAdmin = state ? state.isAdmin : false;

return ( <SContainer> <h2>ユーザー一覧</h2> <SearchInput /> <SUserArea> {users.map((obj) => ( <UserCard key={obj.id} user={obj} isAdmin={isAdmin} /> ))} </SUserArea> </SContainer> ); };

const SContainer = styled.div text-align: center; flex-direction: column; align-items: center; padding: 24px; ;

const SUserArea = styled.div padding-<span class="synStatement">top</span>: 40px; width: 100%; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-gap: 20px; ;

渡されたisAdminを受け取る

import styled from "styled-components";
import { Card } from "../../atoms/card/Card";
import { UserIconWithName } from "../../molecules/user/UserIconWithName";

export const UserCard = (props) => { const { user, isAdmin } = props;

return ( <Card> <UserIconWithName image={user.image} name={user.name} isAdmin={isAdmin} /> <SDL> <dt>メール</dt> <dd>{user.email}</dd> <dt>TEL</dt> <dd>{user.phone}</dd> <dt>会社名</dt> <dd>{user.company.name}</dd> <dt>WEB</dt> <dd>{user.website}</dd> </SDL> </Card> ); };

const SDL = styled.dl text-align: left; margin-bottom: 0px; dt <span class="synIdentifier">{</span> <span class="synStatement">float</span>: left; <span class="synIdentifier">}</span> dd <span class="synIdentifier">{</span> padding-left: 32px; padding-bottom: 8px; overflow-wrap: <span class="synStatement">break</span>-word; <span class="synIdentifier">}</span> ;

渡されたisAdminを受け取る

import styled from "styled-components";

export const UserIconWithName = (props) => { const { image, name, isAdmin } = props; return ( <SContainer> <SImg height={160} width={160} src={image} alt={name} /> <SName>{name}</SName> {isAdmin && <SEdit>編集</SEdit>} </SContainer> ); };

const SContainer = styled.div text-align: center; ;

const SImg = styled.img border-radius: 50%; ; const SName = styled.p font-size: 18px; font-weight: bold; margin: 0; color: #40514e; ; const SEdit = styled.span text-decoration: underline; color: #aaa; cursor: pointer; ;

これがもっと規模が大きくなるとツラい。

これをグローバルなstate管理で解決していく。

Context

$ mkdir -p src/providers

$ touch src/providers/UserProvider.jsx

コンテキストのコンポーネントを作成

import { createContext, useState } from "react";

// 新しいコンテキストの作成 export const UserContext = createContext({});

export const UserProvider = (props) => { const { children } = props;

const [userInfo, setUserInfo] = useState(null);

return ( // valueというグローバルなstateを渡す <UserContext.Provider value={{ userInfo, setUserInfo }}> {children} </UserContext.Provider> ); };

Providerは要素を囲む必要がある

import { UserProvider } from "./providers/UserProvider";
import { Router } from "./router/Router";
import "./styles.css";

export default function App() { return ( <UserProvider> <Router /> </UserProvider> ); }

コンテキストの値を参照していくにはuseContextを使用する

import { useContext } from "react";
import styled from "styled-components";
import { UserContext } from "../../../providers/UserProvider";

export const UserIconWithName = (props) => { const { image, name } = props; const { userInfo } = useContext(UserContext); const isAdmin = userInfo ? userInfo.isAdmin : false;

return ( <SContainer> <SImg height={160} width={160} src={image} alt="プロフィール写真" /> <SName>{name}</SName> {isAdmin && <SEdit>編集</SEdit>} </SContainer> ); };

const SContainer = styled.div text-align: center; ;

const SImg = styled.img border-radius: 50%; ; const SName = styled.p font-size: 18px; font-weight: bold; margin: 0; color: #40514e; ; const SEdit = styled.span text-decoration: underline; color: #aaa; cursor: pointer; ;

import { useContext } from "react";
import { useHistory } from "react-router-dom";
import styled from "styled-components";
import { UserContext } from "../../providers/UserProvider";
import { SecondaryButton } from "../atoms/button/SecondaryButton";

export const Top = () => { const history = useHistory();

const { setUserInfo } = useContext(UserContext);

const onClickAdmin = () => { setUserInfo({ isAdmin: true }); history.push("/users"); }; const onClickGeneral = () => { setUserInfo({ isAdmin: false }); history.push("/users"); };

return ( <SContainer> <h2>TOPページです</h2> <SecondaryButton onClick={onClickAdmin}>管理者ユーザ</SecondaryButton> <br /> <br /> <SecondaryButton onClick={onClickGeneral}>一般ユーザ</SecondaryButton> </SContainer> ); };

const SContainer = styled.div text-align: center; ;

レンダリングの最適化(Context)

作成したプロバイダー(UserProvider)に設定したstate(userInfo, setUserInfo)の値が更新された場合、
その値を使用しているコンポーネント全てが再レンダリングされる

memoを使用してpropsに変更がない場合は再レンダリングしないという風に
コンポーネントを設定する必要がある

import { memo } from "react";

コンテキストを使う場合は、更新時にどのコンポーネントが再レンダリングされるか意識する