Computing Atman

Context File Render

Change Theme

Footer

説明

コンテキストファイルのサンプルコード。Contextファイルの中で、valueとsetValueを分けてExportしている。
今回のサンプルでは、色のテーマを変更した場合でも、Footerコンポーネントは再レンダリングされない。

補足

  • レンダリングが重いコンポーネントは、不要なレンダリングを避けるために、Contextのvalue / setValueの必要な方をimportして利用する方がよい。
  • Redux、Recoil、Jotaiなどを利用する場合は、Contextは不要となる。

利用するshadcn/uiコンポーネント

  • radio
  • label

サンプルコード

Example.tsx

import { Footer } from './_components/Footer';
import { Header } from './_components/Header';
import { Main } from './_components/Main';
import { ThemeProvider } from './_context/ThemeContext';

const Example = () => {
  return (
    <>
      <ThemeProvider>
        <Header />
        <Main />
        <Footer />
      </ThemeProvider>
    </>
  );
};

export { Example };

ThemeContext.tsx

'use client';

import { Dispatch, ReactNode, SetStateAction, createContext, useContext, useState } from 'react';

const ThemeContext = createContext<string>('');

const ThemeUpdateContext = createContext<Dispatch<SetStateAction<string>>>(() => '');

const ThemeProvider = ({ children }: { children: ReactNode }) => {
  const [theme, setTheme] = useState('gray');

  return (
    <ThemeContext.Provider value={theme}>
      <ThemeUpdateContext.Provider value={setTheme}>{children}</ThemeUpdateContext.Provider>
    </ThemeContext.Provider>
  );
};

const useTheme = () => useContext(ThemeContext);
const useUpdateTheme = () => useContext(ThemeUpdateContext);

export { ThemeContext, ThemeProvider, useTheme, useUpdateTheme };

Header.tsx

'use client';

import { Label, RadioGroup, RadioGroupItem } from '@repo/ui';
import { useTheme, useUpdateTheme } from '../_context/ThemeContext';

const Header = () => {
  const theme = useTheme();
  const setTheme = useUpdateTheme();

  const THEMES = ['yellow', 'gray', 'red'];

  const changeTheme = (e: any) => {
    if (e.target.value === theme || e.target.value === undefined) return;
    setTheme(e.target.value);
    console.log(e.target.value);
  };

  console.log('header');
  return (
    <>
      {/* display: 'none' div is hidden because it's dummy to load style */}
      <div style={{ display: 'none' }}>
        <div className=" bg-yellow-500 "></div>
        <div className=" bg-gray-500 "></div>
        <div className=" bg-red-500"></div>
      </div>
      <div className={`bg-${theme}-500 p-4`}>
        <RadioGroup
          defaultValue={theme}
          className="my-2 flex gap-8"
          onClick={(e) => changeTheme(e)}
        >
          {THEMES.map((item, index) => (
            <div key={item} className="flex items-center gap-2">
              <RadioGroupItem value={item} id={index as unknown as string} />
              <Label htmlFor={index as unknown as string}>{item}</Label>
            </div>
          ))}
        </RadioGroup>
      </div>
    </>
  );
};

export { Header };

Main.tsx

'use client';

import { useTheme } from '../_context/ThemeContext';

const Main = () => {
  const theme = useTheme();

  console.log('main');
  return (
    <div className={`bg-${theme}-500 p-4`}>
      <h1 className="p-4">Change Theme</h1>
    </div>
  );
};

export { Main };

Footer.tsx

'use client';

import { useUpdateTheme } from '../_context/ThemeContext';

const Footer = () => {
  // eslint-disable-next-line no-unused-vars
  const setTheme = useUpdateTheme();

  console.log('footer');
  return (
    <div>
      <h1 className="p-4">Footer</h1>
    </div>
  );
};

export { Footer };