Dynamic하게 style 반영하기

inline으로 css를 반영하는 것은 기존 css 요소를 모두 덮어쓸 수 있을 뿐만 아니라, 코드의 중복이 일어날 확률이 높으므로 지양하는 것이 좋다. 즉 className에 접근하여 상태에 따라 'invalid' 라는 클래스명을 추가/제거해주면서 동적으로 css를 추가하는 것이 바람직할 것이다.

 

form-control 클래스명에 invalid가 추가되는 경우, 유저가 아무것도 입력하지 않았을 때 경고 처리를 해주기 위해 다음과 같이 CSS를 작성했다.

.form-control.invalid input {
  border-color: red;
  background: #ffd7d7;
}

.form-control.invalid label {
  color: red;
}

 

유저가 제대로 된 값을 입력하기 시작한 경우 다시 isValid state를 true로, 제출 버튼 클릭 시 아무것도 입력하지 않은 경우 isValid state를 false로 바꿔주는 로직을 추가했다. 또한 form-control 클래스명을 `` (백틱) 으로 전달하여 ${} 문법을 통해 내부에 JS 코드를 작성할 수 있도록 했다.

 

import React, { useState } from "react";

import Button from "../../UI/Button/Button";
import "./CourseInput.css";

const CourseInput = (props) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isValid, setIsValid] = useState(true);

  const goalInputChangeHandler = (event) => {
    if (event.target.value.trim().length !== 0) {
      setIsValid(true);
    }
    setEnteredValue(event.target.value);
  };

  const formSubmitHandler = (event) => {
    event.preventDefault();
    if (event.target.value.trim().length === 0) {
      setIsValid(false);
      return;
    }
    props.onAddGoal(enteredValue);
  };

  return (
    <form onSubmit={formSubmitHandler}>
      <div className={`form-control ${!isValid ? 'invalid' : ''}`}>
        <label>Course Goal</label>
        <input type="text" onChange={goalInputChangeHandler} />
      </div>
      <Button type="submit">Add Goal</Button>
    </form>
  );
};

export default CourseInput;

 

유저가 아무것도 입력하지 않거나, 스페이스바만 누른 채로 버튼을 클릭했을 때

 

Styled Components 사용하기

한 컴포넌트에만 적용할 수 있는, scoped styling을 적용하기 위해 사용해보자. css파일을 import해오는 방식은 얼마든지 다른 요소에 영향을 미칠 수 있으니까 말이다. 수많은 개발자들이 함께 작업하는 파일에서 실수로 클래스명이 겹치는 악몽은 직접 겪지 말자.

 

https://styled-components.com/

 

styled-components

Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress 💅🏾

styled-components.com

 

위 페이지에서 보다 자세한 정보를 볼 수 있다. 일단은 우리의 프로젝트에 설치하자.

npm install --save styled-components

위 명령어를 통해 설치할 수 있다. 

 

기존에 버튼 컴포넌트를 관리하던 Button.js Button.css파일을 styled-components를 사용하여 하나의 파일로 합쳐보자.

기존 파일은 다음과 같다.

 

import React from 'react';

import './Button.css';

const Button = props => {
  return (
    <button type={props.type} className="button" onClick={props.onClick}>
      {props.children}
    </button>
  );
};

export default Button;
.button {
  font: inherit;
  padding: 0.5rem 1.5rem;
  border: 1px solid #8b005d;
  color: white;
  background: #8b005d;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.26);
  cursor: pointer;
}

.button:focus {
  outline: none;
}

.button:hover,
.button:active {
  background: #ac0e77;
  border-color: #ac0e77;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.26);
}

 

styled component는 styled.___ 문법으로 사용 가능하다. styled. 뒤에 올 수 있는 것들은 전부 HTML 태그들이다. styled component는 JSX element를 반환하며 해당 요소 안에 css를 적용하는 방법은 ``을 사용하여 그 안에 코드를 넣어주면 된다. 하지만 css selector가 필요 없으므로 생략하거나 추가로 작성하고자 하는 경우 & 연산자를 사용한다. 적용된 모습을 보면 다음과 같다.

import styled from "styled-components";

const Button = styled.button`
  font: inherit;
  padding: 0.5rem 1.5rem;
  border: 1px solid #8b005d;
  color: white;
  background: #8b005d;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.26);
  cursor: pointer;

  &:focus {
    outline: none;
  }

  &:hover,
  &:active {
    background: #ac0e77;
    border-color: #ac0e77;
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.26);
  }
`;

export default Button;

 

코드를 바꾸더라도 아무런 문제 없이 프로젝트는 잘 작동한다. 그리고 잊지말자! styled가 반환하는 객체는 JSX element이므로 변수는 대문자로 시작해야 한다.

 

Styled components & Dynamic props

앞서 form-control 부분 역시 styled-components를 적용해보자. 기존 코드는 다음과 같다.

.form-control {
  margin: 0.5rem 0;
}

.form-control label {
  font-weight: bold;
  display: block;
  margin-bottom: 0.5rem;
}

.form-control input {
  display: block;
  width: 100%;
  border: 1px solid #ccc;
  font: inherit;
  line-height: 1.5rem;
  padding: 0 0.25rem;
}

.form-control input:focus {
  outline: none;
  background: #fad0ec;
  border-color: #8b005d;
}

.form-control.invalid input {
  border-color: red;
  background: #ffd7d7;
}

.form-control.invalid label {
  color: red;
}
import React, { useState } from "react";

import Button from "../../UI/Button/Button";
import "./CourseInput.css";

const CourseInput = (props) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isValid, setIsValid] = useState(true);

  const goalInputChangeHandler = (event) => {
    if (event.target.value.trim().length !== 0) {
      setIsValid(true);
    }
    setEnteredValue(event.target.value);
  };

  const formSubmitHandler = (event) => {
    event.preventDefault();
    if (enteredValue.trim().length === 0) {
      setIsValid(false);
      return;
    }
    props.onAddGoal(enteredValue);
  };

  return (
    <form onSubmit={formSubmitHandler}>
      <div className={`form-control ${!isValid ? "invalid" : ""}`}>
        <label>Course Goal</label>
        <input type="text" onChange={goalInputChangeHandler} />
      </div>
      <Button type="submit">Add Goal</Button>
    </form>
  );
};

export default CourseInput;

 

styled-components를 적용하면 다음과 같다. nested된 요소 역시 & 연산자를 사용하면 된다.

 

import React, { useState } from "react";
import styled from "styled-components";

import Button from "../../UI/Button/Button";
import "./CourseInput.css";

const FormControl = styled.div`
  margin: 0.5rem 0;

  & label {
    font-weight: bold;
    display: block;
    margin-bottom: 0.5rem;
  }

  & input {
    display: block;
    width: 100%;
    border: 1px solid #ccc;
    font: inherit;
    line-height: 1.5rem;
    padding: 0 0.25rem;
  }

  & input:focus {
    outline: none;
    background: #fad0ec;
    border-color: #8b005d;
  }

  &.invalid input {
    border-color: red;
    background: #ffd7d7;
  }

  &.invalid label {
    color: red;
  }
`;

const CourseInput = (props) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isValid, setIsValid] = useState(true);

  const goalInputChangeHandler = (event) => {
    if (event.target.value.trim().length !== 0) {
      setIsValid(true);
    }
    setEnteredValue(event.target.value);
  };

  const formSubmitHandler = (event) => {
    event.preventDefault();
    if (enteredValue.trim().length === 0) {
      setIsValid(false);
      return;
    }
    props.onAddGoal(enteredValue);
  };

  return (
    <form onSubmit={formSubmitHandler}>
      <FormControl>
        <label>Course Goal</label>
        <input type="text" onChange={goalInputChangeHandler} />
      </FormControl>
      <Button type="submit">Add Goal</Button>
    </form>
  );
};

export default CourseInput;

 

자 그러나 className을 dynamic하게 넘겨주던 부분이 사라졌다. 이 부분도 다시 완성해보자. 물론 form-control이라는 클래스명은 더 이상 적어줄 필요가 없으니 다음과 같이 적어주기만 하면 된다. styled component로 만든 요소 역시 JSX element이므로 className 속성을 사용할 수 있다. 

<FormControl className={!isValid && 'invalid'}>

 

이 방식이 조금 더 클래식한 방식이라면, props를 사용하여 바꿔줄 수도 있다. 이 방식이 조금 더 css상으로는 깔끔하게 보일 것이다. 

<FormControl isvalid={!isValid}>

위와 같이 FormControl 요소에 isvalid(boolean 형) props를 넘겨줄 수도 있다! 그러면 이제 우리는 props.isvalid값에 따라 css를 다르게 작용해줄 수 있다. 왜냐하면 div 뒤에 css를 적어줄 때 `` 을 사용했기 때문에 ${}를 사용하여 얼마든지 js 구문을 쓸 수 있다.

 

import React, { useState } from "react";
import styled from "styled-components";

import Button from "../../UI/Button/Button";
import "./CourseInput.css";

const FormControl = styled.div`
  margin: 0.5rem 0;

  & label {
    font-weight: bold;
    display: block;
    margin-bottom: 0.5rem;
    color: ${(props) => (props.isvalid ? "red" : "black")};
  }

  & input {
    display: block;
    width: 100%;
    border: 1px solid ${(props) => (props.isvalid ? "red" : "#ccc")};
    background: ${(props) => (props.isvalid ? "#ffd7d7" : "transparent")};
    font: inherit;
    line-height: 1.5rem;
    padding: 0 0.25rem;
  }

  & input:focus {
    outline: none;
    background: #fad0ec;
    border-color: #8b005d;
  }
`;

const CourseInput = (props) => {
  const [enteredValue, setEnteredValue] = useState("");
  const [isValid, setIsValid] = useState(true);

  const goalInputChangeHandler = (event) => {
    if (event.target.value.trim().length !== 0) {
      setIsValid(true);
    }
    setEnteredValue(event.target.value);
  };

  const formSubmitHandler = (event) => {
    event.preventDefault();
    if (enteredValue.trim().length === 0) {
      setIsValid(false);
      return;
    }
    props.onAddGoal(enteredValue);
  };

  return (
    <form onSubmit={formSubmitHandler}>
      <FormControl isvalid={!isValid}>
        <label>Course Goal</label>
        <input type="text" onChange={goalInputChangeHandler} />
      </FormControl>
      <Button type="submit">Add Goal</Button>
    </form>
  );
};

export default CourseInput;

+ Recent posts