ReactにてContextでのState管理について学ぶ
学んだこと
Context での state 管理
-
context に前回 state で作った isAdmin を持たせることで、複数のコンポーネントを修正せずとも 1 つのコンポーネントを改修するだけで完結する。
- 前回は、関連するすべてのコンポーネントの props に isAdmin を渡してバケツリレーのような形で UserIconWithName に渡していた。
- 今回は context で state を管理するため、UserIconWithName 内で isAdmin を確認し処理を分ければ修正完了になる。
修正したコンポーネント一覧
- App.jsx
- context を使いたいため、UserProvider で Router を囲んだ。
- UserIconWithName.jsx
- context の isAdmin を使って、管理者と一般ユーザの処理を分けた。
- UserProvider.jsx
- 今回新しく作成したコンポーネント
- UserInfo の context を作成。
- UserCard.jsx
- 前回 UserCard コンポーネントで props として isAdmin を受け取っていた。
- isAdmin を受け取る必要がないため、処理を削除。
App.jsx
import './App.css';
import { Router } from './router/Router';
import { UserProvider } from './provider/UserProvider';
export default function App() {
return (
<UserProvider>
<Router />
</UserProvider>
);
}
UserIconWithName.jsx
import { useContext } from 'react';
import styled from 'styled-components';
import { UserContext } from '../../../provider/UserProvider';
export const UserIconWithName = (props) => {
const { image, name } = props;
const { userInfo } = useContext(UserContext);
console.log(userInfo);
const isAdmin = userInfo ? userInfo.isAdmin : false;
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: #40514;
`;
const SEdit = styled.span`
text-decoration: underline;
color: #aaa;
cursor: pointer;`;
UserProvider.jsx
import React, { createContext, useState } from 'react';
export const UserContext = createContext({});
export const UserProvider = (props) => {
const { children } = props;
const [userInfo, setUserInfo] = useState(null);
return (
<UserContext.Provider value=>
{children}
</UserContext.Provider>
);
};
UserCard.jsx
import styled from 'styled-components';
import { Card } from '../../atoms/card/Card';
import { UserIconWithName } from '../../molecules/user/UserIcomWithName';
export const UserCard = (props) => {
const { user } = props;
return (
<Card>
<UserIconWithName image={user.image} name={user.name} />
<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;
dt{
float: left;
}
dd{
padding-left: 32px;
padding-bottom: 8px;
overflow-wrap: break-word;
}
`;
Context の注意点
- Context 内の State を使っているコンポーネントは、すべて再レンダリングの対象になる。
実際に再レンダリングされるか確認
- SearchInput.jsx
import { PrimatyButton } from '../atoms/button/PrimaryButton';
import { Input } from '../atoms/input/input';
import styled from 'styled-components';
export const SearchInput = () => {
console.log('SearchInput');
return (
<div>
<SContainer>
<Input placeholder="検索条件を入力" />
<SButtonWrapper>
<PrimatyButton>検索</PrimatyButton>
</SButtonWrapper>
</SContainer>
</div>
);
};
const SButtonWrapper = styled.div`
padding-left: 8px;
`;
const SContainer = styled.div`
display: flex;
align-items: center;
`;
- UserCard.jsx
import styled from 'styled-components';
import { Card } from '../../atoms/card/Card';
import { UserIconWithName } from '../../molecules/user/UserIcomWithName';
export const UserCard = (props) => {
const { user } = props;
console.log('UserCard');
return (
<Card>
<UserIconWithName image={user.image} name={user.name} />
<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;
dt{
float: left;
}
dd{
padding-left: 32px;
padding-bottom: 8px;
overflow-wrap: break-word;
}
`;
- UserIconWithName.jsx
import { useContext } from 'react';
import styled from 'styled-components';
import { UserContext } from '../../../provider/UserProvider';
export const UserIconWithName = (props) => {
const { image, name } = props;
const { userInfo } = useContext(UserContext);
console.log(userInfo);
const isAdmin = userInfo ? userInfo.isAdmin : false;
console.log('UserIconWithName');
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: #40514;
`;
const SEdit = styled.span`
text-decoration: underline;
color: #aaa;
cursor: pointer;`;
- User.jsx
- state を切り替えするボタンを用意した。
- state を切り替えて再レンダリングが行われるか確認する。
import styled from 'styled-components';
import { SearchInput } from '../molecules/SearchInput';
import { UserCard } from '../organisms/user/UserCard';
import { useLocation } from 'react-router-dom';
import { SecondaryButton } from '../atoms/button/SecondaryButton';
import { useContext } from 'react';
import { UserContext } from '../../provider/UserProvider';
export const Users = () => {
const { userInfo, setUserInfo } = useContext(UserContext);
const onClickSwitch = () => setUserInfo({ isAdmin: !userInfo.isAdmin });
return (
<SContainer>
<SUserArea>
<h2>ユーザ一覧</h2>
<SearchInput />
<br />
<SecondaryButton onClick={onClickSwitch}>切り替え</SecondaryButton>
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</SUserArea>
</SContainer>
);
};
const users = [...Array(10).keys()].map((val) => {
return {
id: val,
name: `あああ${val}`,
image:
'https://images.unsplash.com/photo-1598133894008-61f7fdb8cc3a?q=80&w=988&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
email: '12345@example.com',
phone: '000-0000-0000',
company: {
name: 'テスト株式会社',
},
website: 'https://google.com',
};
});
const SContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
padding: 24px;
`;
const SUserArea = styled.div`
padding-top: 40px;
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 20px;
`;
結果
- 以下のログを確認すると、関連するコンポーネント全部再レンダリングが行われている事がわかった。
SearchInput
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
UserCard.jsx:7 UserCard
UserIcomWithName.jsx:8 {isAdmin: false}
UserIcomWithName.jsx:10 UserIconWithName
再レンダリングしないための方法
- memo を使うことで再レンダリングを防止することができる。
- 以下のように関数宣言時に memo で囲むと再レンダリングを防止することができる。
import { memo } from 'react';
import { PrimatyButton } from '../atoms/button/PrimaryButton';
import { Input } from '../atoms/input/input';
import styled from 'styled-components';
export const SearchInput = memo(() => {
console.log('SearchInput');
return (
<div>
<SContainer>
<Input placeholder="検索条件を入力" />
<SButtonWrapper>
<PrimatyButton>検索</PrimatyButton>
</SButtonWrapper>
</SContainer>
</div>
);
});
const SButtonWrapper = styled.div`
padding-left: 8px;
`;
const SContainer = styled.div`
display: flex;
align-items: center;
`;
まとめ
- Context はグローバルにどのコンポーネントからでもアクセスすることができる。
- Context を使っているコンポーネントは必ず再レンダリングが行われる為注意する。
- 再レンダリングを防止したい場合は、memo を使うこと。