2024-03-27 73일차 React

민짱·2024년 3월 27일

📅2024. 03. 27 73일차


todoListItem에 수정 기능 추가

TodoListItem 컴포넌트

  • props에 updateTodo 추가
  • useState 훅을 사용하여 isEditing과 editedTodo 상태를 정의.
    isEditing은 현재 수정 모드인지 여부를 추적하고, editedTodo는 수정 중인 할 일의 내용을 저장.
  • handleEditChange 함수를 사용하여 수정 중인 할 일의 내용이 변경될 때 호출되어 editedTodo 상태를 업데이트.
  • handleEditSubmit 함수사용 수정이 완료될 때 호출되어 수정된 할 일을 updateTodo 함수를 통해 업데이트하고 수정 모드를 종료.
  • handleEditClick 함수는 수정 버튼을 클릭했을 때 호출되어 수정 모드로 전환.
  • handleRemoveClick 함수는 삭제 버튼을 클릭했을 때 호출되어 해당 할 일을 삭제.
  • 삼항연사자 사용 수정 모드인 경우에는 입력 필드와 완료 버튼이 표시되고, 수정 모드가 아닌 경우에는 할 일의 내용과 수정 버튼이 표시.

TodoList 컴포넌트

  • props에 updateTodo 추가
  • TodoListItem 컨포넌트에 필요한 props에 updateTodo 추가

App 컴포넌트

  • updateTodo 함수 정의
  • todos.map 메서드를 사용하여 현재 할 일 목록(todos)을 순회.
    map 메서드는 배열의 각 요소를 변환하여 새로운 배열을 생성.
    새로운 배열의 각 요소는 이전 배열의 해당 요소를 변환한 값이 된다.
  • todoIndex === index 조건을 사용하여 현재 순회 중인 할 일 항목이 업데이트할 대상인지 확인.
    만약 현재 순회 중인 항목의 인덱스가 업데이트할 대상의 인덱스와 일치하면, 새로운 내용(updatedTodo)으로 해당 항목을 업데이트. 그렇지 않으면, 현재 할 일 항목을 그대로 유지.
  • map 메서드가 반환한 새로운 배열을 setTodos 함수를 사용하여 상태로 설정.
    이를 통해 업데이트된 할 일 목록이 UI에 반영.

//생략
const TodoListItem = ({ todo, index, removeTodo, updateTodo }) => {
    const [isEditing, setIsEditing] = useState(false);
    const [editedTodo, setEditedTodo] = useState(todo);

    const handleEditChange = (e) => {
        setEditedTodo(e.target.value);
    };

    const handleEditSubmit = () => {
        if (editedTodo.trim().length === 0) return;
        updateTodo(index, editedTodo);
        setIsEditing(false);
    };

    const handleEditClick = () => {
        setIsEditing(true);
    };

    const handleRemoveClick = () => {
        removeTodo(index);
    };

    return (
        <>
            <li className="flex items-center gap-x-3">
                {isEditing ? (
                    <>
                        <input
                            type="text"
                            value={editedTodo}
                            onChange={handleEditChange}
                        />
                        <button className="btn btn-secondary" onClick={handleEditSubmit}>
                            완료
                        </button>
                    </>
                ) : (
                    <>
                        <span>
                            {index + 1}번 째 할일 : {todo}
                        </span>
                        <button className="btn btn-secondary" onClick={handleEditClick}>
                            수정
                        </button>
                    </>
                )}
                <button className="btn btn-secondary" onClick={handleRemoveClick}>
                    삭제
                </button>
            </li>
        </>
    );
};

const TodoList = ({ todos, removeTodo, updateTodo }) => {
    return (
        <div>
            {todos.length === 0 ? (
                <h5>할 일이 없습니다.</h5>
            ) : (
                <>
                    <h5>새 할일</h5>
                    <nav>
                        <ul>
                            {todos.map((todo, index) => (
                                <TodoListItem
                                    key={index}
                                    todo={todo}
                                    index={index}
                                    removeTodo={removeTodo}
                                    updateTodo={updateTodo}
                                />
                            ))}
                        </ul>
                    </nav>
                </>
            )}
        </div>
    );
};
const App = () => {
    const [todos, setTodos] = useState([]);

    const addTodo = (newTitle) => {
        if (newTitle.trim().length === 0) return;
        setTodos([...todos, newTitle.trim()]);
    };

    const updateTodo = (index, updatedTodo) => {
        const newTodos = todos.map((todo, todoIndex) =>
            todoIndex === index ? updatedTodo : todo
        );
        setTodos(newTodos);
    };

    const removeTodo = (index) => {
        const newTodos = todos.filter((_, _index) => _index !== index);
        setTodos(newTodos);
    };

    return (
        <>
            <TodoWriteForm addTodo={addTodo} />
            <hr />
            <TodoList todos={todos} removeTodo={removeTodo} updateTodo={updateTodo} />
        </>
    );
};

🎨 리액트(React), 재시작, 새 할일 입력, 리스팅, 2차원 데이터

console.clear();

import React, { useState } from "https://cdn.skypack.dev/react@18";
import ReactDOM from "https://cdn.skypack.dev/react-dom@18";

const NewTodoForm = ({addTodo:_addTodo}) => {
	const [newTodoTitle, setNewTodoTitle] = useState("");
	
	const addTodo = () => {
		if(newTodoTitle.trim().length == 0) return;
		
		const id = 1;
		const title = newTodoTitle;
		const newTodo = {
			id,
			title
		};
		_addTodo(newTodo);
		setNewTodoTitle('');
		
	}
	
	return (
		<>
			<div className="flex items-center gap-x-3">
				<input
					className="input input-bordered"
					type="text"
					placeholder="새 할일 입력해"
					value={newTodoTitle}
					onChange={(e) => setNewTodoTitle(e.target.value)}
				/>
				<button className="btn btn-primary" onClick={addTodo}>할 일 추가</button>
			</div>
		</>
	);
};

const TodoList = (todos) => {
	return <>
		{JSON.stringify(todos)}
		</>
}

const App = () => {
	const [todos, setTodos] = useState([]);

	const addTodo = (newTodo) => {
		setTodos([...todos, newTodo]);
	};

	return (
		<>
			<NewTodoForm addTodo={addTodo} />
			<hr/>
			<TodoList todos={todos} />
		</>
	);
};

ReactDOM.render(<App />, document.getElementById("root"));
  • 기본 구조 완료 할 일 추가할때마다 자동으로 번호 증가하도록 구현해야함.

추가할 때 마다 자동으로 번호 증가

const NewTodoForm = ({ addTodo: _addTodo, todos }) => {
	const [newTodoTitle, setNewTodoTitle] = useState("");

	const addTodo = () => {
		if (newTodoTitle.trim().length == 0) return;

		const id = todos.length + 1;
		const title = newTodoTitle;
		const newTodo = {
			id,
			title
		};
		_addTodo(newTodo, todos);
		setNewTodoTitle("");
	};

	return (
		<>
			<div className="flex items-center gap-x-3">
				<input
					className="input input-bordered"
					type="text"
					placeholder="새 할일 입력해"
					value={newTodoTitle}
					onChange={(e) => setNewTodoTitle(e.target.value)}
				/>
				<button className="btn btn-primary" onClick={addTodo}>
					할 일 추가
				</button>
			</div>
		</>
	);
};

const TodoList = ({ todos }) => {
	return <>{JSON.stringify(todos)}</>;
};

const App = () => {
	const [todos, setTodos] = useState([]);

	const addTodo = (newTodo) => {
		setTodos([...todos, newTodo]);
	};

	return (
		<>
			<NewTodoForm addTodo={addTodo} todos={todos}/>
			<hr />
			<TodoList todos={todos} />
		</>
	);
};
  • NewTodoForm의 props에 todos 추가
  • id를 todos.length + 1로 구현

슨생님.var

console.clear();

import React, { useState } from "https://cdn.skypack.dev/react@18";
import ReactDOM from "https://cdn.skypack.dev/react-dom@18";

const NewTodoForm = ({addTodo:_addTodo}) => {
	const [newTodoTitle, setNewTodoTitle] = useState("");
	
	const addTodo = () => {
		if(newTodoTitle.trim().length == 0) return;
		
		const title = newTodoTitle;
		const newTodo = {
			id : null,
			title
		};
		_addTodo(newTodo);
		setNewTodoTitle('');
		
	}
	
	return (
		<>
			<div className="flex items-center gap-x-3">
				<input
					className="input input-bordered"
					type="text"
					placeholder="새 할일 입력해"
					value={newTodoTitle}
					onChange={(e) => setNewTodoTitle(e.target.value)}
				/>
				<button className="btn btn-primary" onClick={addTodo}>할 일 추가</button>
			</div>
		</>
	);
};

const TodoList = (todos) => {
	return <>
		{JSON.stringify(todos)}
		</>
}

const App = () => {
	const [todos, setTodos] = useState([]);
	const [lastTodoId, setLastTodoId] = useState(0);

	const addTodo = (newTodo) => {
		newTodo.id = lastTodoId + 1;
		setTodos([...todos, newTodo]);
		
		setLastTodoId(newTodo.id);
	};

	return (
		<>
			<NewTodoForm addTodo={addTodo} />
			<hr/>
			<TodoList todos={todos} />
		</>
	);
};

ReactDOM.render(<App />, document.getElementById("root"));

목록 UI 변경, 아이템 삭제

console.clear();

import React, { useState } from "https://cdn.skypack.dev/react@18";
import ReactDOM from "https://cdn.skypack.dev/react-dom@18";

const NewTodoForm = ({ addTodo: _addTodo }) => {
	const [newTodoTitle, setNewTodoTitle] = useState("");

	const addTodo = () => {
		if (newTodoTitle.trim().length == 0) return;
		const title = newTodoTitle.trim();
		_addTodo(title);
		setNewTodoTitle("");
	};

	return (
		<>
			<div className="flex items-center gap-x-3">
				<input
					className="input input-bordered"
					type="text"
					placeholder="새 할일 입력해"
					value={newTodoTitle}
					onChange={(e) => setNewTodoTitle(e.target.value)}
				/>
				<button className="btn btn-primary" onClick={addTodo}>
					할 일 추가
				</button>
			</div>
		</>
	);
};

const TodoListItem = ({ todo, removeTodo: _removeTodo }) => {
	const removeTodo = () => {
		_removeTodo(todo.id);
	};

	return (
		<li className="flex items-center gap-x-3 mb-3">
			<span className="badge badge-primary badge-outline">{todo.id}</span>
			<span>{todo.title}</span>
			<button className="btn btn-primary" onClick={removeTodo}>
				삭제
			</button>
		</li>
	);
};

const TodoList = ({ todos, removeTodo }) => {
	return (
		<>
			{todos.length == 0 ? (
				<h4>할 일 없음</h4>
			) : (
				<>
					<h4>할 일 목록</h4>
					<ul>
						{todos.map((todo) => (
							<TodoListItem key={todo.id} todo={todo} removeTodo={removeTodo} />
						))}
					</ul>
				</>
			)}
		</>
	);
};

const App = () => {
	const [todos, setTodos] = useState([]);
	const [lastTodoId, setLastTodoId] = useState(0);

	const addTodo = (title) => {
		const id = lastTodoId + 1;

		const newTodo = {
			id,
			title
		};
		setTodos([...todos, newTodo]);
		setLastTodoId(id);
	};

	const removeTodo = (id) => {
		const newTodos = todos.filter(todo => todo.id != id);
		setTodos(newTodos);
	};

	return (
		<>
			<NewTodoForm addTodo={addTodo} />
			<hr />
			<TodoList todos={todos} removeTodo={removeTodo} />
		</>
	);
};

ReactDOM.render(<App />, document.getElementById("root"));

State란?

  • 간단하게 state는 변수이다. state는 일반 변수와 다르게 값이 변하게 되면 렌더링이 일어난다. 즉, 값이 변하게 되면 연관있는 컴포넌트들이 다시 렌더링이되어 화면이 바뀌게 된다. state와 함께 사용되는 함수는 setState이다. setState는 state 값을 변경시켜주는 함수이다.
const [state, setState] = useState(초기값)

state는 위와 같이 선언한다. state는 변수, setState는 state를 변경시키는 함수, useState는 state, setState를 return 하면서 초기값을 설정해주는 hook이다.

setState()란?

  • setState()는 컴포넌트의 state 객체에 대한 업데이트를 실행한다. state가 변경되면, 컴포넌트는 리렌더링된다.

  • setState()는 리액트의 함수형 컴포넌트 내에서 상태를 관리하기 위해 사용하는 hooks인 useState()를 통해 반환되는 함수이다.

setState() 특징

  1. 기본적으로 비동기로 동작한다.
  2. 연속적으로 호출했을 때 리액트 내부적으로는 BATCH 처리를 한다.
  3. state 객체를 넘겨줄 수 있을 뿐만 아니라 새로운 state를 반환하는 함수를 인자로 넘겨줄 수 있다.

기본적으로 setState()함수는 state를 변경하고 state가 포함된 해당 컴포넌트를 다시 렌더링한다. 그런데 이게 동기적으로 동작하는 것이 아니라 비동기적으로 동작함에 주목해야한다(= setState()를 실행하자마자 바로 state를 변경하고 바로 렌더링하는게 아니다!).

setState 방식 2가지

1) setNumber(number + 1);
2) setNumber((_number) => _number + 1);

1번 방법은 직접적으로 2번 방법은 함수형 업데이트 방법

const Num1Button = ({ num, setNum }) => {
	const onClick = () => {
		setNum(num + 1);
		setNum(num + 1);
	};

	return <button onClick={onClick}>증가</button>;
};

const Num2Button = ({ setNum }) => {
	const onClick = () => {
		setNum((num) => num + 1);
		setNum((num) => num + 1);
	};

	return <button onClick={onClick}>증가</button>;
};

const App = () => {
	const [num1, setNum1] = useState(0);
	const [num2, setNum2] = useState(0);

	return (
		<>
			num1 : {num1} <Num1Button num={num1} setNum={setNum1} />
			<hr />
			num2 : {num2} <Num2Button setNum={setNum2} />
		</>
	);
};
  • Num1Button을 눌렀을 때는 1만 증가한다.
  • Num2Button을 눌렀을 때는 정상적으로 2가 증가한다.

다른 이유는 React의 useState hook은 비동기적으로 상태를 업데이트한다. 이것은 함수형 업데이트를 사용하는 것과 직접 값을 전달하는 것 사이의 차이 때문에 발생한다.

Num1Button에서 setNum(num + 1)을 두 번 호출하면, 이 코드는 현재 num 값에 1을 추가한 값을 상태로 설정한다. 이 작업은 비동기적으로 이루어지므로, 두 번의 호출 중 먼저 호출된 setNum(num + 1)에서는 이전 상태의 num 값을 기반으로 한다. 따라서 두 번 호출해도 결과적으로는 1이 증가한 값만 적용된다.

Num2Button에서는 함수형 업데이트를 사용하여 setNum을 호출한다. 함수형 업데이트를 사용하면 React는 이전 상태를 보장하고 업데이트를 수행한다. 따라서 두 번의 setNum((num) => num + 1) 호출은 두 번의 증가가 반영된다.

참고1
참고2
참고3


0개의 댓글