23/12/27 typescript ,react-query 복습
2023. 12. 28. 08:36ㆍ카테고리 없음
타입스크립트로 한 투두리스트 리액트쿼리를 곁들인
<Nav.tsx> 네브를 따로 컴포넌로 만들었다.
import React, { useState } from "react";
import styled from "styled-components";
// import { __addTodo } from "../redux/mo/modules/todoSlice";
import { nanoid } from "nanoid";
import { useMutation, useQueryClient } from "@tanstack/react-query"; //유즈 뮤테이션과 유즈 쿼리 클라이언트 적용
import { addTodos } from "../redux/mo/modules/queryFns"; //쿼리펑션폴더에 넣어놓은 쿼리펑션
export default function Nav() {
type T = { id: string; title: string; content: string; isDone: boolean }; //객체형인 투두하나의 타입지정
const JSON_SERVER_BASE_URL = "http://localhost:4000/todos"; //인터셉터 굳이 안만들고 이렇게 놔둠 ㅋ
const initialForm = {
id: "",
title: "",
content: "",
isDone: false,
};
const [formState, setFormState] = useState<T>(initialForm); //제네릭안에 만들어놓은 타입T
const queryClient = useQueryClient();
const { mutate: mutateToAdd } = useMutation({ //CRUD중 CUD를 하기위한 훅
mutationFn: addTodos,
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["todos"] }); //얘가 캐쉬를 다시 가져옴
},
});
const OnchangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => { //요 타입지정 어려움 ㅜ
const { name, value } = e.target; //캬 내가 했지만 요 코드 이쁘네
setFormState((prev: T) => ({ ...prev, [name]: value }));
};
const OnSubmitHandler = async (e: React.FormEvent<HTMLFormElement>) => { //파라미터 옆에 : 하고 타입인데
const newTodo = { // 익숙치 않네
id: nanoid(),
title: formState.title,
content: formState.content, //유즈스테이트에서 받아온값 사용
isDone: false,
};
e.preventDefault();
mutateToAdd(newTodo); //여기서 유즈뮤테이트 사용
setFormState(initialForm); //이니셜스테이트 초기화
};
return (
<Header onSubmit={OnSubmitHandler}> //헤더를 폼태그로 만들었었네 ㅋㅋㅋ
<span>제목</span>{" "}
<input
name="title"
value={formState.title}
onChange={OnchangeHandler}
></input>
<span>내용</span>{" "}
<input
name="content"
value={formState.content}
onChange={OnchangeHandler}
></input>
<br />
<button disabled={!formState.title || !formState.content}> //해당 인풋에 요값 안넣으면 불능만듦
추가하기
</button>
</Header>
);
}
const Header = styled.form`
background-color: lightcyan;
height: 150px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
`;
<TodosLIst.tsx> 메인페이지이면서 유일한 페이지 하하
import React from "react";
import styled from "styled-components";
// import {
// __deleteTodo, //요거는 thunk를 사용한 흔적 // 아니 thunk쓰다가 리액트쿼리쓰니까
// __editTodo, //훠월씬 쉬움
// __getTodos,
// } from "../redux/mo/modules/todoSlice";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { deleteTodo, editTodo, getTodos } from "../redux/mo/modules/queryFns";
export default function TodosList({ listType }: { listType: boolean }) {
type T = { id: string; title: string; content: string; isDone: boolean }; //네브때와 동일한 타입 굳이 파일분리 안함 ㅋ
const JSON_SERVER_BASE_URL = "http://localhost:4000/todos";
const { data: todos, isLoading } = useQuery({ //유즈쿼리훅 사용법 안에 는 isloading이라는 키가 있다.
queryKey: ["todos"], //isFetching도 있다던데?
queryFn: getTodos,
});
const queryClient = useQueryClient(); //유즈뮤테이트때 쓰기위해 쿼리클라이언트 만들어야함
const { mutate: mutateToDelete } = useMutation({
mutationFn: deleteTodo,
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["todos"] });
},
});
const { mutate: mutateToEdit } = useMutation({
mutationFn: editTodo, //요 앞에 뮤테이션 함수는 파일분리 해놓음 이따 보여줌
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["todos"] });
},
});
const DeleteHandler = (id: string) => {
const confirmedData = window.confirm("정말로 삭제할꺼에여?");
if (confirmedData) {
mutateToDelete(id); //인자로 id가 필요해서 넘겨주고
}
};
const UpdateHandler = (id: string, isDone: boolean) => {
mutateToEdit({ id, isDone }); //여기는 객체형으로 id와 이즈돈 넘겨주고
};
const notYetTodos = todos?.filter((todo: T) => {
return todo.isDone === false; //요 두개의 필터는 투두스를 두개로 나눴다.
});
const completedTodos = todos?.filter((todo: T) => {
return todo.isDone === true;
});
const changedTodos = listType ? completedTodos : notYetTodos;
if (isLoading) {
return <>로딩중....</>;
}
return (
<>
{" "}
<div style={{ backgroundColor: "black", color: "white" }}>
{listType ? "완료된투두" : "해야할투두"}
</div>
<Section1>
<>
{changedTodos?.map((todo: T) => {
return (
<ListWrapper key={todo.id}>
<div>아이디 : {todo.id}</div>
<div>제목 : {todo.title}</div>
<div>내용 : {todo.content}</div>
<div>상태 : {todo.isDone ? "완료" : "미완료"}</div>
<button onClick={() => UpdateHandler(todo.id, todo.isDone)}> //온클릭주고 필요한 인자 넘겨주고
{listType ? "취소" : "완료"}
</button>
<button onClick={() => DeleteHandler(todo.id)}>삭제하기</button>
</ListWrapper>
);
})}
</>
</Section1>
</>
);
}
const Section1 = styled.section`
height: 200px;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
background-color: lightblue;
`;
const ListWrapper = styled.div`
height: 180px;
width: 180px;
background-color: lightgoldenrodyellow;
`;
<App.tsx> 이 파일을 먼저 보여줬어야 하나?
import React from "react";
import "./App.css";
import styled from "styled-components";
import Nav from "./pages/Nav";
import TodosList from "./pages/TodosList";
function App() {
type T = { id: string; title: string; content: string; isDone: boolean };
// const [todos, setTodos] = useState<T[]>([]);
console.log();
return (
<Container className="App">
{" "}
<Content>
<ZeMOK>투두리스트</ZeMOK>
<Nav /> //네브컴포넌트달고
<TodosList listType={false} /> //여기에 투두리스트를 두번 달았다.
<TodosList listType={true} /> //liistType에 불리언 값에 따라 랜더링하는 모양이 달라지게함
</Content> //위에 파일가보면 props 받은게 보일꺼임
</Container>
);
}
export default App;
const Container = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
const Content = styled.div` //씨에스에스는 평범한거 같음
width: 100%;
height: 100vh;
max-width: 1200px;
min-width: 800px;
`;
const ZeMOK = styled.div`
background-color: lightcoral;
font-size: 50px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
`;
// const Section2 = styled.section`
// background-color: lightpink;
// height: 200px;
// display: flex;
// justify-content: flex-start;
// align-items: center;
// gap: 10px;
// `;
// const ListWrapper = styled.div`
// height: 180px;
// width: 180px;
// background-color: lightgoldenrodyellow;
// `;
<queryFn.ts> 쿼리펑션과 뮤테이트펑션을 한 파일에 담아둠 나눌 필요가 있나?
import axios from "axios";
type T = { id: string; title: string; content: string; isDone: boolean };
const JSON_SERVER_BASE_URL = "http://localhost:4000/todos";
export const getTodos = async () => {
const { data } = await axios.get<T[]>(JSON_SERVER_BASE_URL); //쿼리펑션 겟투두스
return data;
};
export const addTodos = async (newTodo: T) => { //뮤테이트 펑션인 add투두
await axios.post(JSON_SERVER_BASE_URL, newTodo);
};
export const deleteTodo = async (id: string) => { //뮤테이트 펑션인 delete투두
await axios.delete(`${JSON_SERVER_BASE_URL}/${id}`); //인자로 넘겨운 아이디 타입 지정해주고
};
export const editTodo = async ({ //edit투두 //넘어온 객체 타입지정 해주고
id,
isDone,
}: {
id: string;
isDone: boolean;
}) => {
await axios.patch(`${JSON_SERVER_BASE_URL}/${id}`, { //아이디 일치하는에 isDone만 바꿔주고
isDone: !isDone,
});
};
//요번 과제할때 thunk나 query적용 때문에 빡치는게 아니라
//타입스크립트 때문에 뚜껑 여러번 열림 와 진짜 익숙해지면 오류해결엔 용이할텐데
//코드 자체를 짤때 타입스크립트 처음해보니까 자꾸 컴파일러가 뭐라해서 딥빡
//내일 올릴꺼 없으면 thunk 버전도 올리겠슴다. 하하