객체로 이루어진 배열에 객체 추가하기 - 리액트 React hooks

Hmm·2021년 12월 26일
0
post-thumbnail


현재 이런 상태이고, input에 값을 입력하고 버튼을 누르면

[{name: "미키"}, {name: "새로입력한 값"}, .......]

이런 배열을 만들고 싶다.
현재코드는 이렇다.

import React, { useState } from "react";
import "./App.css";
import react from "react";

function App() {
  const [todo, setTodo] = useState([{ name: "미키" }]);
  let [inputValue, setInputValue] = useState("");

  const inputChg = (e) => {
    setInputValue(e.target.value);
  };
  const onSubmit = (e) => {
    e.preventDefault();
  };
  console.log(todo);
  return (
    <div className="parent">
      <p>id: {todo[0].id}</p>
      <p>name: {todo[0].name}</p>
      <p>memo: {todo[0].memo}</p>
      <form onSubmit={onSubmit}>
        <input value={inputValue} onChange={inputChg}></input>
        <button>저장</button>
      </form>
    </div>
  );
}

export default App;

1. 배열에 새로운 엘리먼트 추가하기

(1) ...array (스프레드 연산자) 사용

원래 array (그러니까 oldArray)를 ...를 이용해 복사하고, 그 뒤에 element를 추가한다. 이렇게 복사한 array에 element를 추가한 값으로 setTheArray를 바꿔준다.

const [theArray, setTheArray] = useState(initialArray);
  • 방법1 : 콜백함수
setTheArray(oldArray => [...oldArray, newElement]);

oldArray에는 마음대로 작명하면 되고, 원래 만들어놓은 배열이라고 잘 이해한다.

  • 방법2
setTheArray([...theArray, newElement]);

동일한 결과가 나온다.

방법1이랑 2가 과정에 차이가 있다는데..

2. input 값을 객체로 만들기

음 그러면 저 inputValue를 객체로 만들어놓으면 되겠다! 그러면 [{ }, { }, { }]이렇게 계속 추가할 수 있을 것 같다.


input값 마지막 글자가 짤리는 에러
그런데 마지막 글자가 안보이는 문제가 생겼다. 사실 이전에 객체를 추가할 때는 이런 문제가 없었는데.. 그때 글을 다시 보니 잘못한 점이 있었다.
  let newInfo = {
    id: "n",
    todo: todoValue,
    memo: memoValue,
  };

이렇게 데이터를 작성했어서 오류가 나지 않았나보다. 그렇지만 리액트에서는 원본 데이터를 건드리지 않아야 하니까 저렇게 지속적으로 데이터를 바꾸는 방식은 좋지 않을 것 같아 수정했다.
이번엔 아래와 같이 useState를 이용하였더니 마지막 글자가 안보이는 에러가 생겼다.

let [newTodo, setNewTodo] = useState({ name: inputValue });

전체코드

import React, { useEffect, useState } from "react";
import "./App.css";
import react from "react";

function App() {
  let [inputValue, setInputValue] = useState("");
  const [todo, setTodo] = useState([{ name: "" }]);
  let [newTodo, setNewTodo] = useState({ name: inputValue });

  const inputChg = (e) => {
    setInputValue(e.target.value);
    setNewTodo({ name: inputValue });
  };

  const OnSubmit = (e) => {
    e.preventDefault();
    setTodo([...todo, newTodo]);
  };
  const todosMap = todo.map((todoItem, i) => <p key={i}>{todoItem.name}</p>);
  return (
    <div className="parent">
      name: {todosMap}
      <form onSubmit={OnSubmit}>
        <input value={inputValue} onChange={inputChg}></input>
        <button>저장</button>
      </form>
    </div>
  );
}

export default App;

클래스 컴포넌트에서 setState() function은 비동기적으로 작동해서 그렇다는데.. 함수형도 그런건가?? https://stackoverflow.com/questions/33088482/onchange-in-react-doesnt-capture-the-last-character-of-text

찾아보니 useEffect hook을 이용해서 input값을 온전하게 업데이트 할 수 있다고 한다. 그렇게 해보자.


useEffect 사용법

  • (1) 마운트 또는 언마운트 할 때 실행

  • (2) 특정 props, state가 바뀔 때 실행

    앞에서 말했다시피 useEffect는 마운트(첫번째로 나타날)될 때도 실행되고, 이후에 렌더링할 때마다도 실행된다. 그런데 만약 컴포넌트가 첫번째로 나타날 때만 쓰고 싶다고 하면 아래와 같이 사용하면 된다.

useEffect(렌더링 후 실행할 함수, [deps]);

//deps?
//특정한 값이 변경될 때 effect함수를 실행하고 싶을 경우 배열 안에 그 값을 넣어준다. [name]이렇게 쓰면 name변수가 변경될 때 함수가 실행된다.
//컴포넌트가 Mount될 때에만 실행하고 싶으면 빈 배열을 쓰면 된다.

useEffect를 사용해도 에러
아래와 같이 useEffect를 사용하였는데도, 여전히 오류가 있었다. input값이 바뀔 때마다... useEffect가 실행돼서 한글자 칠때마다 배열이 계속 추가됐다..ㅋㅋ
import React, { useEffect, useState } from "react";
import "./App.css";
import react from "react";

function App() {
  const [todo, setTodo] = useState([{ name: "" }]);
  let [inputValue, setInputValue] = useState("");
  let [newTodo, setNewTodo] = useState({ name: "" });

  const inputChg = (e) => {
    setInputValue(e.target.value);
    setNewTodo({ name: inputValue });
  };

  const onSubmit = (e) => {
    e.preventDefault();
    setTodo([...todo, newTodo]);
    setInputValue("");
  };

  const todosMap = todo.map((todoItem, i) => <p key={i}>{todoItem.name}</p>);
  return (
    <div className="parent">
      name: {todosMap}
      <form onSubmit={onSubmit}>
        <input value={inputValue} onChange={inputChg}></input>
        <button>저장</button>
      </form>
    </div>
  );
}

export default App;

useEffect가 두번 실행되는 오류

콘솔로그를 해보면 아무 동작을 하지않았음에도 처음에 렌더링이 두번 되어있다. 알아서 useEffect가 실행된듯하다ㅠ 그래서 로드되자마자 지 혼자 인풋 값이 비어있는 상태에서 바로 배열에 추가해버림..
[deps]추가했으면 이거 수정할 때만 되는거 아닌가? 그게 아니라, [deps]가 수정될 때 뿐만 아니라 처음 렌더링될때에도 실행된다.

이번에는 deps 에 특정 값을 넣어보도록 하겠습니다. 
deps 에 특정 값을 넣게 된다면, 컴포넌트가 처음 마운트 될 때에도 호출이 되고, 지정한 값이 바뀔 때에도 호출이 됩니다. 
그리고, deps 안에 특정 값이 있다면 언마운트시에도 호출이되고, 값이 바뀌기 직전에도 호출이 됩니다.
출처: https://react.vlpt.us/basic/16-useEffect.html

코드는 이렇게 작성했었다

import React, { useEffect, useState } from "react";
import "./App.css";
import react from "react";

function App() {
const [todo, setTodo] = useState([{ name: "" }]);

let [inputValue, setInputValue] = useState("");
let [newTodo, setNewTodo] = useState({ name: "" });

const inputChg = (e) => {
  setInputValue(e.target.value);
};

const onSubmit = (e) => {
  e.preventDefault();
  setNewTodo({ name: inputValue });
};

useEffect(() => setTodo([...todo, newTodo]), [newTodo]);
console.log(todo);
const todosMap = todo.map((todoItem, i) => <p key={i}>{todoItem.name}</p>);
return (
  <div className="parent">
    name: {todosMap}
    <form onSubmit={onSubmit}>
      <input value={inputValue} onChange={inputChg}></input>
      <button>저장</button>
    </form>
  </div>
);
}

export default App;

newTodo가 바뀔 때마다 setTodo를 이용해서, 전체 todo의 값에 새로운 todo를 추가해주려고 했다.
그런데 오류가 나서 구조를 수정하였다.

결론: input 값을 객체로 만들고 배열에 추가하려면,

1) input값을 useState("")에 넣는다.
아직 단순한 string 상태이다.

2) 아까 input값으로 만든 useState를 이용해 객체를 만든다.
이제 객체를 만들었다.
useEffect를 사용해서, input값이 변할 때마다 객체를 그 최신 input값으로 업데이트 해준다.

  //inputValue가 변하면, 그때 newTodo값을 바꿔주자
  useEffect(() => setNewTodo({ name: inputValue }), [inputValue]);

{key: 인풋값} 이런식이다.

3) 해당 객체를 배열에 추가한다.

  const onSubmit = (e) => {
    ...
    setTodo([...todo, newTodo]);
    ...
  };

input값이 변할 때, 그 때 newTodo값을 바꿔주기로 하였다.

완성 코드

import React, { useEffect, useState } from "react";
import "./App.css";
import react from "react";

function App() {
  const [todo, setTodo] = useState([{ name: "" }]);
  let [inputValue, setInputValue] = useState("");
  let [newTodo, setNewTodo] = useState({ name: "" });

  const inputChg = (e) => {
    setInputValue(e.target.value);
  };
  
  //inputValue가 변하면, 그때 newTodo값을 바꿔주자
  useEffect(() => setNewTodo({ name: inputValue }), [inputValue]);

  const onSubmit = (e) => {
    e.preventDefault();
    setTodo([...todo, newTodo]);
    setInputValue("");
  };

  const todosMap = todo.map((todoItem, i) => <p key={i}>{todoItem.name}</p>);
  return (
    <div className="parent">
      name: {todosMap}
      <form onSubmit={onSubmit}>
        <input value={inputValue} onChange={inputChg}></input>
        <button>저장</button>
      </form>
    </div>
  );
}

export default App;

(참고:
https://thewebdev.info/2021/05/29/how-to-capture-the-last-character-of-text-entered-with-onchange-in-react/ ,
https://tocomo.tistory.com/32
)

0개의 댓글