
현재 만들어진 프로젝트는 input 창에 아무것도 입력하지 않아도 Add Goal버튼을 누르면 빈 값이 추가가 가능한데, 만약 사용자가 입력한 값이 빈 값이면 추가를 하지 못하게 막고, 값이 유효하지 않다고 표시하는 기능을 만들어보았다.
// 기존 코드
const CourseInput = props => {
const [enteredValue, setEnteredValue] = useState('');
const goalInputChangeHandler = event => {
setEnteredValue(event.target.value);
};
const formSubmitHandler = event => {
event.preventDefault();
props.onAddGoal(enteredValue);
};
return (
<form onSubmit={formSubmitHandler}>
<div className="form-control">
<label>Course Goal</label>
<input type="text" onChange={goalInputChangeHandler} />
</div>
<Button type="submit">Add Goal</Button>
</form>
);
};
// 변경된 코드
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();
// 만약 사용자가 공백을 입력하면 실행 X
if (enteredValue.trim().length === 0) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
};
return (
<form onSubmit={formSubmitHandler}>
<div className="form-control">
<label style={{ color: !isValid ? "red" : "black" }}>Course Goal</label>
<input
style={{
borderColor: !isValid ? "red" : "black",
background: !isValid ? "salmon" : "transparent",
}}
type="text"
onChange={goalInputChangeHandler}
/>
</div>
<Button type="submit">Add Goal</Button>
</form>
);
};
submit 기능을 해주는 Add Goal버튼을 클릭했을때 작동하는 formSubmitHandler 함수에서 사용자가 입력한 값을 추가하는 동작이 이뤄지기 전에 if를 사용하여 사용자가 공백을 입력하면 그 함수를 빠져 나오게 return 을 써줬다.
그리고 사용자가 이를 알 수 있도록 style도 적용하기 위해 useState를 사용하여 코드를 작성했다.
isValid의 기본값은 true로 사용자가 유효하지 않을 값을 입력하였을 경우 이를 false로 변경하여 스타일을 동적으로 바꿀 수 있도록 하였다.
이렇게 변경된 isValid를 JSX문 안에서 삼항 연산자를 사용해 inline으로 작성하여 디자인을 변경하였다.
하지만 이렇게 inline 방식으로 적용된 스타일이 최우선으로 적용되기에 기존에 갖고 있던 스타일을 덮어버려 만족스럽지 않다. 이를 해결하기 위해 styled-components를 사용해보았다.
const FormControl = styled.div`
margin: 0.5rem 0;
& label {
font-weight: bold;
display: block;
margin-bottom: 0.5rem;
color: ${(props) => (props.invalid ? "red" : "black")};
}
& input {
display: block;
width: 100%;
border: 1px solid ${(props) => (props.invalid ? "red" : "#ccc")};
background: ${(props) => (props.invalid ? "#ffd7d7" : "transparent")};
font: inherit;
line-height: 1.5rem;
padding: 0 0.25rem;
}
& input:focus {
outline: none;
background: #fad0ec;
border-color: #8b005d;
}
`;
const CourseInput = (props) => {
.
.
.
return (
<form onSubmit={formSubmitHandler}>
// props로 invalid 넘김
<FormControl invalid={!isValid}>
<label>Course Goal</label>
<input type="text" onChange={goalInputChangeHandler} />
</FormControl>
<Button type="submit">Add Goal</Button>
</form>
);
};
기존 div 태그를 컴포넌트로 변경하여 그 안에 원하는 스타일을 적용시켜 준다. 이때 styled.div`` 이런식으로 템플릿 리터럴을 사용해 스타일을 감싸준다.
이때, props로 받은 invalid를 통해 동적으로 스타일을 적용해준다. invalid가 false라면 값이 유효하지 않으니 사용자가 이를 알 수 있도록 빨간색으로 표시해준다.
이렇게 styled-components를 사용하면 .css 파일로 스타일을 적용하면 같은 class name이 있다면, 내가 적용하고 싶은 컴포넌트에만 영향을 주는 것이 아닌 다른 컴포넌트의 스타일도 변경이 되는데, 이를 유일한 class 로 만들어주기 때문에 내가 원하는 컴포넌트에만 스타일을 적용할 수 있다.
하지만 styled-components는 컴포넌트와 스타일이 한 파일에 같이 정의도이어 있기 때문에 조금 복잡한 느낌이 있어 이를 분리해보았다.
import styles from "./CourseInput.module.css";
const CourseInput = (props) => {
.
.
.
return (
<form onSubmit={formSubmitHandler}>
// isValid가 false이면 className에 invalid를 추가함
<div
className={`${styles["form-control"]} ${!isValid && styles.invalid}`}
>
<label>Course Goal</label>
<input type="text" onChange={goalInputChangeHandler} />
</div>
<Button type="submit">Add Goal</Button>
</form>
);
};
CSS module을 사용하기 위해서는 CSS 파일명에 .module을 추가하여 하고, 이를 styled로 import 해야 한다.
styled-components를 사용할 때 변경되었던 컴포넌트를 다시 div로 바꿔준 뒤 className이 동적으로 변경될 수 있도록 && 연산자를 사용했다.
이렇게 만들어진 CSS module도 해당 컴포넌트에만 스타일이 적용되는데 그 이유는 CSS module이 자동으로 class를 "컴포넌트 이름클래스 이름고유한 해시값"으로 설정해주기 때문이다.

사용자가 유효하지 않은 값 (공백)을 입력하면 위와 같이 사용자가 알 수 있도록 표시해주며, 목록에 추가가 되지 않는다.