Recoil_StateManagement(React)

김정훈·2023년 7월 30일
0
post-thumbnail

ThemeProvider을
App.tsx → index.tsx로 이동시킨다.(state을 이용하기 위해서)

npm install recoil

⭐️ index.tsx로 가서 render을 RecoilRoot태그로 감싸자.
index.tsx

root.render(
  <>
    <RecoilRoot>
      <ThemeProvider theme={darkTheme}>
        <App />
      </ThemeProvider>
    </RecoilRoot>
  </>
);

Recoil(StateManagement)가 필요한 이유

DarkMode
: App.tsx에서 function을 Router로 보내고
-> 그 다음 Router에서 Coins로 보내고 있다.

App.tsx. 에서 isDark, modifierFunction 이 두 개를
Router -> Coins로 한번 보내고
Router -> Coin -> Chart로 한번더 보낸다.


Recoil 사용 전, 후

StateManagement(Recoil) 사용 전
isDark : App -> Router -> Coin -> Chart :: 하나하나 내려줘야함

StateManagement(Recoil) 사용 후
Header -> (isDark) <- Chart :: 필요한 애들이 가져다 쓰면 된다.

atom은 두가지 Argument을 받는다.
1번째는 key값 - 이름으로 유일
2번째는 기본값

// theme.ts 파일
import { DefaultTheme } from "styled-components";

export const darkTheme: DefaultTheme = {
  bgColor: "#2f3640",
  textColor: "white",
  accentColor: "#9c88ff",
  cardBgColor: "transparent",
};

export const lightTheme: DefaultTheme = {
  bgColor: "whitesmoke",
  textColor: "black",
  accentColor: "#9c88ff",
  cardBgColor: "white",
};

// atoms.ts 파일
export const isDarkAtom = atom({
	key: "isDark",
	default: false,
})

// App.tsx 파일
function App() {
  const isDark = useRecoilValue(isDarkAtom);
  return (
    <>
      <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
        <GlobalStyle />
        <Router />
        <ReactQueryDevtools initialIsOpen={true} />
      </ThemeProvider>
    </>
  );
}

// Coins.tsx 파일 : 사용하는 곳
function Coins() {
  const setDarkAtom = useSetRecoilState(isDarkAtom);
  const toggleDarkAtom = () => setDarkAtom(prev => !prev);
  
  <button onClick={toggleDarkAtom}>Toggle Mode</button>
}

useRecoilValue : Recoil의 현재 값을 가져올 수 있다.

import { useRecoilValue } from 'recoil';
import { myState } from './recoilState'; // Recoil 상태를 정의한 파일

function MyComponent() {
  const myValue = useRecoilValue(myState);

  return (
    <div>
      <p>My Value: {myValue}</p>
    </div>
  );
}

useRecoilState : Recoil 상태 값을 조회하고 변경하기 위해 사용 - 배열 형태, 1번째 요소 Recoil상태의 현재 값, 2번째 요소는 Recoil의 상태를 변경하는 함수

import { useRecoilState } from 'recoil';
import { myState } from './recoilState'; // Recoil 상태를 정의한 파일

function MyComponent() {
  const [myValue, setMyValue] = useRecoilState(myState);

  const handleButtonClick = () => {
    // Recoil 상태인 myState 값을 변경
    setMyValue('New Value');
  };

  return (
    <div>
      <p>My Value: {myValue}</p>
      <button onClick={handleButtonClick}>Change State</button>
    </div>
  );
}

Recoil - ToDo

const [todo, setTodo] = useState("");
const onChange = (event:React.FormEvent<HTMLInputElement>) => {
// TypeScrip 사용하기 때문에, event에 대해 가르쳐줘야함. 
    const {
      currentTarget: { value },
    } = event;
    setValue(value);
};
  const onSubmit = (event:React.FormEvent<HTMLFormElement>) =>{
    event.preventDefault();
    console.log(todo);
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input onChange={onChange} value={todo} placeholder="Write To Do" />
        <button>Add</button>
      </form>
    </div>
  );

이제 이 부분을 react-hook-form을 이용해서 바꿀 것이다.

폼이 크다면 Validation 또한 많아야 할 것이다.
그런데 react-hook-form을 사용하지 않는다면, form에 많은 state들을 등록하게 될 것이다.

React-Hook-Form : useForm

npm install react-hook-form

React-Hook-Form에서 문자열을 리턴하는 것은 에러 메시지를 리턴하는 것이다.

  • register : 해당 컨트롤의 값을 수집하고, 유효성 검사 수행이 가능하며(onBlur, onChange, state 같은 것을 제공),
    Form제출시에 함께 전송(가장 기본적으로 사용되는 메소드)
console.log(register("문자열"));
<input
    {...register("firstName", 
                 { required: "firstName is required" })}
    placeholder="firstName"
/>
  • watch : Form의 입력값들의 변화를 관찰할 수 있게 해주는 함수
console.log(watch());

import React from 'react';
import { useForm } from 'react-hook-form';

function MyForm() {
  const { register, handleSubmit, watch } = useForm();

  const onSubmit = (data) => {
    // 폼 데이터 처리
    console.log(data);
  };

  // watch를 이용하여 특정 폼 컨트롤 값 가져오기
  const usernameValue = watch('username');

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* 폼 컨트롤 등록 */}
      <input type="text" {...register('username')} />
      <input type="password" {...register('password')} />

      <button type="submit">Submit</button>

      {/* 특정 폼 컨트롤 값 출력 */}
      <p>Username: {usernameValue}</p>
    </form>
  );
}
  • handleSubmit : validation을 담당한다.
    이벤트를 preventDefault() 하는 것도 담당한다.
  • formState : 폼의 상태 확인, 상태에 따른 UI조작하거나 사용자에게 메시지를 보여주는 다양한 조작이 가능
import React from 'react';
import { useForm } from 'react-hook-form';

function MyForm() {
  const { register, handleSubmit, formState } = useForm();

  const onSubmit = (data) => {
    // 폼 데이터 처리
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* 폼 컨트롤 등록 */}
      <input type="text" {...register('username', { required: true })} />
      <input type="password" {...register('password', { required: true })} />

      {/* 유효성 검증 에러 메시지 출력 */}
      {formState.errors.username && <p>Username is required.</p>}
      {formState.errors.password && <p>Password is required.</p>}

      <button type="submit">Submit</button>
    </form>
  );
}
  • setError : 유효성 검사를 통과하지 못한 필드들은 에러를 발생시킨다.
    하지만 유효성 검사가 아닌
    🚫 서버 오프라인 같은 경우에는 setError()을 이용해서 Error을 발생시켜줘야 한다.
    그래서 Interface쪽에 extraError을 추가해주는 것이다.
// SetError
interface IForm{
	email: string;
    //...
    extraError?:string;
}
const onValid = (data: IForm) => {
	if(data.password !== data.password1){
        setError("password1", {message: "Password are not the same"})
    }
    setError("extraError", {message: "Server Offline"});
    // 이 ExtraError는 특정한 항목에 해당되는 에러가 아니라 ,전체 form에 해당되는 에러이다.
	};

Custom Validation

<input
	{...register("firstName", {
		required: "firstName is required",
		noHacker: (value) =>
			value.includes("hacker") ? "no hacker allowed" : true,
	})}

정규식 테스트 해볼 수 있는 곳
: https://www.regexpal.com

Categories(Todo의 카테고리를 바꿀 수 있게 해주는 기능)

const onClick = (newCategory: ITodo["category"])=>{
	console.log("i wanna to",newCategory);
}
return(
	<li>
		<span>{text}</span>
		{category !== "DOING" && (
			<button onClick={()=>onClick("TO_DO")}>To Do</button>
		)}
)
const onClick=(event: React.MouseEvent<HTMLButtonElement>) =>{
	const{
		currentTarget: {name},
	} = event;
}
return(
	<li>
		<span>{text}</span>
		{category !== "TO_DO" && (
	        <button name="TO_DO" onClick={onClick}>To Do</button>
	    )}
)

배열의 원소를 교체하는 원리

const food = ["pizza", "fruit", "kimchi", "pasta"];

const target = 1;  // 내가 바꾸고 싶은 배열의 index

food.slice(0, target);
food.slice(target+1)

[...food.slice(0,target),"감",...food.slice(target+1)]

Recoil의 Selector

:: state을 입력 받아서 그걸 변형해 반환하는 순수함수를 거쳐 반환된 값
⭐️ selector는 state(atom)를 가져다가 원하는대로 모습을 변형시킬 수 있는 도구이다. (+ atom은 단순히 배열만 준다.)

Todo의 카테고리에 따라서 알맞은 버튼만 보이게 할 것이다.

// atoms.ts 파일
export const todoState = atom<ITodo[]>({
  key: "todo",
  default: [],
});
export const categoryState = atom<Categories>({
  key: "category",
  default: Categories.TO_DO,
});
export const todoSelector = selector({
  key: "todoSelector",
  get: ({ get }) => {
    const todos = get(todoState);
    const category = get(categoryState);
    return todos.filter((todo) => todo.category === category);
  },
});

//ToDoList.tsx 파일
function ToDoList(){
  	const todo = useRecoilValue(todoSelector);
  
  <CreateToDo />
  return(
    {todo?.map((todoEle) =>(
    	<ToDo key={todoEle.id} {...todoEle} />
	))}
  )
}

get function이 있어야 atom을 받는다.
vanila Js의 filter함수는 배열을 return한다.

selector는 todoState,categoryStat을 바라보고 있기 때문에, 둘 중 하나의 값이 변하면 selector의 값도 변한다.

profile
WebDeveloper

0개의 댓글