상태변경을 위해서는 2개의 context를 먼저 만들어야 한다.
하나는 상태 전용 context, 다른 하나는 디스패치 전용 context이다
2개를 만드는 이유는 디스패치 함수를 사용했을 시에만 필요한 컴포넌트 상태도 업데이트될때 리랜더링되기 때문이다.
//ContextTodo.tsx
import React,{createContext, useContext, useReducer,Dispatch, ReactNode} from 'react'
type State = {
id: number;
content: string;
complete: boolean
}
type ArrayState = State[]
type Action =
| { type: 'Add_Todo'; content: string }
| { type: 'Remove_Todo'; id: number }
| { type: 'Toggle_complete'; id: number }
type todoDispatch = Dispatch<Action>
const stateContext = createContext<ArrayState | null>(null)
const dispatchContext = createContext<todoDispatch | null>(null)
createContext
함수의 Generics를 사용하여 Context에서 관리할 값 상태를 설정해준다.
우리가 추후 Provider를 사용하지 않았을 때에는 Context 값이 null
이 되어야하기에 <ArrayState | null>
로 선언
액션타입을 선언해준다(추가, 삭제, 완료상태변경)
type todoDispatch = Dispatch<Action>
을 불러와 Generic으로 액션들의 타입을 넣어주면 컴포넌트에서 액션을 디스패치할 때 액션들에 대한 타입을 검사할 수 있다.(content
나 id
값이 빠지면 오류발생)
//ContextTodo.tsx
import React,{createContext, useContext, useReducer,Dispatch, ReactNode} from 'react'
type State = {
id: number;
content: string;
complete: boolean
}
type ArrayState = State[]
type Action =
| { type: 'Add_Todo'; content: string }
| { type: 'Remove_Todo'; id: number }
| { type: 'Toggle_complete'; id: number }
type todoDispatch = Dispatch<Action>
const stateContext = createContext<ArrayState | null>(null)
const dispatchContext = createContext<todoDispatch | null>(null)
export function ContextTodoProvider ({children} : {children : ReactNode}) {
const [state, dispatch] = useReducer(reducer, [{
id: 0,
content: '블라블라',
complete: false
}])
return (
<stateContext.Provider value={state}>
<dispatchContext.Provider value={dispatch}>
{children}
</dispatchContext.Provider>
</stateContext.Provider>
)
}
export function ContextTodoProvider ({children} : {children : ReactNode}) {
const [state, dispatch] = useReducer(reducer, [{
id: 0,
content: '블라블라',
complete: false
}])
return (
<stateContext.Provider value={state}>
<dispatchContext.Provider value={dispatch}>
{children}
</dispatchContext.Provider>
</stateContext.Provider>
)
}
App.tsx에 불러와서 기존 내용을 감싸주기 위해 export를 시켜준다
stateContext
,dispatchContext
를 사용하기 위해 useContext를 사용해준다.
export function useTodo() {
const state = useContext(stateContext)
if(!state) throw new Error('Cannot find sampleProvider')
return state
}
export function useTodoDispatch() {
const dispatch = useContext(dispatchContext)
if(!dispatch) throw new Error('Cannot find sampleProvider')
return dispatch
}
//ContextInput.tsx
import React, { useState } from 'react'
import {useTodo, useTodoDispatch} from './ContextTodo'
import '../App.css'
import ContextList from './ContextList'
const ContextInput = () => {
//useContext불러오기
const todo = useTodo();
const todoDispatch = useTodoDispatch();
const [inputs, setInputs] = useState<string>('')
const inputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputs(e.target.value)
}
const submit = (e:any) => {
e.preventDefault();
if (!inputs) return
//상태 변경
todoDispatch({type:'Add_Todo', content: inputs })
setInputs('')
}
return (
<div>
<form onSubmit={submit}>
<input type="text" className='inputStyle' onChange={inputChange} value={inputs} />
<button className='btnStyle' type='submit'>+</button>
</form>
{todo?.map((x, i) => <ContextList data={x} key={i} todo={todo} />)}
</div> )
}
export default ContextInput
//ContextList.tsx
import React from 'react'
import { useTodoDispatch} from './ContextTodo'
interface Content {
id: number,
content: string,
complete: boolean
};
const ContextList = ({ data }: { data: Content }) => {
const todoDispatch = useTodoDispatch();
const isDone = () => {
todoDispatch({type: 'Toggle_complete', id: data.id})
}
const remove = () => {
console.log(data.id)
todoDispatch({ type: 'Remove_Todo', id: data.id })
}
return (
<div className='Container'>
{data.complete ? <div className='toggleLine'>{data.content}</div> : <div>{data.content}</div>}
<div>
<button className='noStyleBtn' onClick={isDone}>✅</button>
<button className='noStyleBtn' onClick={remove}>❌</button>
</div>
</div>
)
}
export default ContextList