본문 바로가기

React

[React] 다크모드, 라이트모드 만들기 | useContext

반응형

 

⏩ 포트폴리오를 제작하면서 예전부터 구현해보고 싶었던 다크 모드/라이트 모드를 구현해보려 한다.

찾다보니 이미 npm dark mode 라이브러리가 있었지만, 직접 구현해보고 싶어서 해당 라이브러리는 사용하지 않았다.

가장 먼저 toggle button이 필요해서 material-ui 에서 이미 제작된 toggle 액션이 가능한 버튼을 가져왔고 색상이나 크기는 커스텀하여 작성해뒀다.

 

//기능 구현 전 / Toggle component
import React, { useState } from "react";
import { withStyles } from "@material-ui/core/styles";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from "@material-ui/core/Switch";

const OrangeSwitch = withStyles({
  root: {
    width: "60px",
    height: "40px",
  },
  switchBase: {
    color: "#F54404",
    "&$checked": {
      color: "#F54404",
    },
    "&$checked + $track": {
      backgroundColor: "#F54404",
    },

    "&$focusVisible $thumb": {
      border: "6px solid #F54404",
    },
  },
  track: {
    border: `1px solid black`,
    backgroundColor: "white",
  },
  checked: {},
})(Switch);

export default function CustomizedSwitches() {
  const [isChecked, setIsChecked] = useState(false);

  const handleChange = () => {
    setIsChecked(!isChecked);
  };

  return (
    <div>
      <FormControlLabel
        control={
          <OrangeSwitch
            checked={isChecked}
            onChange={handleChange}
            name="checkedA"
          />
        }
        label="Dark Mode !"
      />
    </div>
  );
}

 

 

⏩ 버튼은 준비되었고 이제 기능을 구현하면 된다. 다크모드와 라이트모드를 위해서 우선 theme.js 파일에 다크모드와 라이트모드에서 사용될 컬러를 지정해주어야 한다.

 

// theme.js 일부
const lightTheme = {
  bgColor: "#F1EFED",
  textColor: "black",
};

const darkTheme = {
  bgColor: "#181818",
  textColor: "white",
};

export const theme = {
  ...lightTheme,
  ...darkTheme,
};

export default theme;

 

 

⏩ 만들어둔 theme을 global.style에 적용해준다.

 

//global.style
import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
	* {
		padding: 0;
		margin: 0;
		box-sizing: border-box;
	}

	html,body {
		height:100%;
	}

    body {
        box-sizing: border-box;
		font-family: 'Syncopate','Noto Sans KR', sans-serif;
		background-color: #F1EFED;
		background: ${({ theme }) => theme.bgColor};
		color: ${({ theme }) => theme.textColor};
		transition: all 0.25s linear;
	}

	a {
		text-decoration:none;
		color: ${({ theme }) => theme.textColor};
	}

	li {
		list-style:none;
	}

	button {
		outline:none;
		border:none;
		color: ${({ theme }) => theme.textColor};
		background-color:transparent;
	}
`;

export default GlobalStyle;

 

- 관련 스타일은 모두 작성이 되었고 이제 상태를 확인하여 유저가 button을 클릭할 때 다크모드, 라이트모드로 변경하면 됐다. 이 과정을 고민하던 중 theme과 toggle 컴포넌트가 계속 props로 전달하여 상태를 주고 받는것이 비효율적이라고 생각해서 useContext hook을 사용하여 바로 상태를 주고받을 수 있도록 하는 방법을 생각했다.

 

 

Hooks API Reference – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

 

(😱 ⏩ 현재 styled-components를 theme을 만들어서 아래와 같이 사용하고 있었다. 여기서 ThemeProvider은 그대로 두고 외부에 다시 ThemeContext를 감싸면 되는데, 이 두가지를 합쳐서 적용해야 된다는 생각 때문에 삽질을 오래 했다..)

 

//App.js 일부

<ThemeProvider theme={theme}>
    <GlobalStyle theme={mode === darkTheme ? darkTheme : lightTheme} />
    <Layout />
</ThemeProvider>

 

 

⏩ 결국 따로 ThemeContext을만들어서 외부에서 감싸는 방식을 선택했는데, 두가지 같이 사용할 수 있는 방법이 있는지 아직도 궁금하다.. 

 

import React from "react";
import { ThemeProvider } from "styled-components";
import { useLightMode } from "./hooks/useLightMode";
import Layout from "./components/Layout";
import GlobalStyle from "./Styles/Global.style";
import { theme } from "./Styles/theme";
import { ThemeContext } from "./context/ThemeContext";

const { darkTheme, lightTheme } = theme;

function App() {
  const { mode, toggleTheme } = useLightMode(); //useLightMode hook

  return (
    <>
      <ThemeContext.Provider value={{ mode, toggleTheme }}>
        <ThemeProvider theme={theme}>
          <GlobalStyle theme={mode === darkTheme ? darkTheme : lightTheme} />
          <Layout />
        </ThemeProvider>
      </ThemeContext.Provider>
    </>
  );
}

export default App;

 

//themeContext.js
import { createContext } from "react";
import { theme } from "../Styles/theme";

const { darkTheme } = theme;

export const ThemeContext = createContext({
  mode: darkTheme,
  toggleTheme: () => {
    return null;
  },
});

 

 

⏩ 또한, ToggleButton 컴포넌트 내부에서 사용하던 로직을 useLightMode hook을 만들어서 사용했다.

 

//useLigthMode.js
import { useState } from "react";
import { theme } from "../Styles/theme";

export const useLightMode = () => {
  const { lightTheme, darkTheme } = theme;
  const [mode, setMode] = useState(darkTheme);

  const toggleTheme = () => {
    mode === darkTheme ? setMode(lightTheme) : setMode(darkTheme);
  };

  return { mode, toggleTheme };
};

 

 

✅ 기능이 구현된 ToggleButton 컴포넌트

 

//ToggleSwitch 컴포넌트

...

function ToggleSwitch() {
  const { mode, toggleTheme } = useContext(ThemeContext);

  return (
    <div>
      <FormControlLabel
        control={
          <OrangeSwitch onChange={toggleTheme} name="checkedA" mode={mode} />
        }
        label={
          mode.textColor === "white"
            ? "Switch to Light mode"
            : "Switch to Dark mode"
        }
      />
    </div>
  );
}

export default ToggleSwitch;