Thinking in React - React State🚀
Mar 27, 2023
React State Step 3: Find the minimal but complete representation of UI state
Step 4: Identify where your state should live
Step 5: Add inverse data flow
리액트에는 state라는 개념이 있고 state가 바뀌면 해당 컴포넌트와 하위 컴포넌트가 다시 렌더링하게 된다.
React State의 조건은?
상태는 누가 관리해야할까? 현재 관리해야하는 상태는? 👉🏻 체크박스 on, off
체크박스는 누가 관리하는 게 좋을까? 상태를 여러 컴포넌트에 넘겨줘야하는 상황이라면 하위 컴포넌트를 가지고 있는 공통 조상 컴포넌트가 관리하는 것이 좋다. 이것을 Lifting State Up 이라고 한다.
checkbox를 클릭하면 stocked가 true인 제품의 목록만 나오도록 만들기 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { useRef } from 'react' ;type CheckBoxFieldProps = { label : string, }; export default function CheckBoxField ({ label }: CheckBoxFieldProps ) { const id = useRef (`checkbox-${label} ` .replace (/ /g , '-' ).toLowerCase ()); return ( <div > <input type ="checkbox" id ={id.current} /> <label htmlFor ={id.current} > {label}</label > </div > ); }
상태를 관리하려면? useState 사용
1 const [inStockOnly, setInStockOnly] = useState (false );
체크박스를 클릭했을 때 inStockOnly의 값을 변경하는 방법
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import { useRef, useState } from 'react' ;type CheckBoxFieldProps = { label : string ; }; export default function CheckBoxField ({ label }: CheckBoxFieldProps ) { const id = useRef (`checkbox-${label} ` .replace (/ /g , '-' ).toLowerCase ()); const [inStockOnly, setInStockOnly] = useState<boolean >(false ); const handleChange = ( ) => { setInStockOnly (!inStockOnly); }; return ( <div > <input type ="checkbox" id ={id.current} checked ={inStockOnly} onChange ={handleChange} /> <label htmlFor ={id.current} > {label}</label > </div > ); }
CheckBoxField
컴포넌트를 누가 사용하지? ProductInCategory
컴포넌트
inStockOnly가 true가 되면 ProductInCategory
컴포넌트에서 제품 목록 필터링을 해야한다.
CheckBoxField
와 ProductInCategory
컴포넌트를 모두 가지고 있는 조상 컴포넌트를 찾으면?
👉🏻 FilterableProductTable
컴포넌트, 즉 inStockOnly가 있어야 하는 곳이다.
TextField 컴포넌트 만들기
1 2 3 4 5 6 7 8 9 10 11 type TextFieldProps = { placeholder : string ; }; export default function TextField ({ placeholder }: TextFieldProps ) { return ( <div > <input type ="text" placeholder ={placeholder} /> </div > ); }
FilterableProductTable 컴포넌트가 가져야 할 state 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import ProductTable from './ProductTable' ;import SearchBar from './SearchBar' ;import Product from '../types/Product' ;type FilterableProductTableProps = { products : Product []; }; export default function FilterableProductTable ({ products, }: FilterableProductTableProps ) { return ( <div > <SearchBar /> <ProductTable products ={products} /> </div > ); }
검색어라는 상태를 관리하고 싶다면
1 2 3 4 5 6 7 8 export default function TextField ({ placeholder }: TextFieldProps ) { const [filterText, setFilterText] = useState<string >('' ); return ( <div > <input type ="text" placeholder ={placeholder} value ={filterText} /> </div > ); }
키를 입력할 때마다 무엇을 입력했는 지 확인하려면 onChange 이벤트를 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export default function TextField ({ placeholder }: TextFieldProps ) { const [filterText, setFilterText] = useState<string >('' ); const handleChange = (event: React.ChangeEvent ) => { const { value } = event.target ; setFilterText (value); }; return ( <div > <input type ="text" placeholder ={placeholder} value ={filterText} onChange ={handleChange} /> </div > ); }
👉🏻 target을 얻었는데 불분명해서 나오는 에러. 현재 타겟은 input element이므로 로 타입을 지정해준다.
FilterableProductTable 컴포넌트로 작성한 state를 옮겨준다.
즉, state가 필요한 컴포넌트에서 먼저 작성하고 공통 조상 컴포넌트로 옮기는 방법이 Lifting State Up
그리고 SearchBar 컴포넌트에 props로 넘겨주기
SerachBar 컴포넌트에서는?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import CheckBoxField from './CheckBoxField' ;import TextField from './TextField' ;type SearchBarProps = { filterText : string ; }; export default function SearchBar ({ filterText }: SearchBarProps ) { return ( <div className ="seach-bar" > <TextField placeholder ="Search..." filterText ={filterText} /> <CheckBoxField label ="Only Show products in stock" /> </div > ); }
props으로 받아왔기 때문에 SearchBarProps로 타입을 지정해주고, TextField 컴포넌트에 props로 다시 전달해주기
TextField 컴포넌트에서는?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import React from 'react' ;type TextFieldProps = { filterText : string ; placeholder : string ; }; export default function TextField ({ filterText, placeholder }: TextFieldProps ) { const handleChange = (event: React.ChangeEvent<HTMLInputElement> ) => { const { value } = event.target ; setFilterText (value); }; return ( <div > <input type ="text" placeholder ={placeholder} value ={filterText} onChange ={handleChange} /> </div > ); }
TextFieldProps에 filterText prop 타입 지정하기
이제 useState는 더 이상 필요 없기 때문에 지워도 된다.
문제는 setFilterText 데이터를 위에서 아래로 보내는 건 문제 없는데, 함수를 아래서 위로 보내야 하는 상황
이럴 때는 하위 컴포넌트의 props로 함수를 전달한다. (콜백 함수)
filterText prop을 ProductTable 컴포넌트로 넘겨주기 1 <ProductTable products={products} filterText={filterText} />
이렇게 넘겨주는 방법이 있지만, 또 다른 한 가지 방법은
filterText 값에 해당하는 products만 넘겨주기. (prop을 한개만 넘겨줘도 되는 장점이 있다.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export default function FilterableProductTable ({ products, }: FilterableProductTableProps ) { const [filterText, setFilterText] = useState<string >('' ); const query = filterText.trim ().toLowerCase (); const filteredProducts = !query.length ? products : products.filter ((product ) => product.name .toLowerCase ().includes (query)); return ( <div > <SearchBar filterText ={filterText} setFilterText ={setFilterText} /> <ProductTable products ={filteredProducts} /> </div > ); }
inStockOnly, setInStockOnly도 동일하게 리팩토링하기