4 minute read

学んだこと

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 を使うこと。

Tags:

Updated: