반응형

react-router-dom을 사용하여 페이지 이동 기능 구현 중에 문제가 발생했다.

 

포스팅 내용은 react-router-dom v6을 사용하였으며, v5와는 차이가 있습니다.

 

일반적인 화면 이동은 Link를 사용하여 아래와 같이 구현하면 된다.

 

<Link to="/">
    <Button variant="secondary">
        취소
    </Button>
</Link>
<Link to="/write">
    <Button variant="secondary">
        글쓰기
    </Button>
</Link>

 

그런데 특정 기능을 실행한 후 화면을 이동해야 하는 케이스가 있다.

나로 예를 들면 글 저장 후 목록 화면으로 되돌아가는 기능이다.

 

그래서 버튼 클릭 이벤트에 아래 함수를 호출하도록 했다.

 

// import useNavigate
import { useNavigate } from "react-router-dom";

// click function
write = () => {
    Axios.post("http://localhost:8000/insert", {
        title: this.state.title,
        content: this.state.content,
    })
        .then(() => {
            const navigate = useNavigate(); // 에러난 부분

            navigate("/");
        })
        .catch((e) => {
            console.error(e);
        });
};

 

반응형

 

글 저장이 정상적으로 된 이후에 console에 아래와 같은 오류가 발생했다.

 

Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

 

하나씩 살펴보자.

 

1. You might have mismatching versions of React and the renderer (such as React DOM)

react-dom의 버전이 Hook을 사용할 수 있는 버전인 16.8.0보다 낮을 때 이러한 에러가 뜰 수 있다. 

 

package.json을 확인해보니 내 react-dom 버전은 17.0.2였다.

 

 

2. You might be breaking the Rules of Hooks

내 경우에는 여기에 해당됐다.

해당 내용을 공식문서에서 찾아보니.. 아래 규칙을 준수하라는 얘기이다.

 

  • ✅ 함수 컴포넌트 본문의 최상위 레벨에서 호출하세요.
  •  사용자 정의 Hook 본체의 최상위 레벨에서 호출하세요.
  • 🔴 클래스 컴포넌트에서 Hooks를 호출하지 마세요.
  • 🔴 이벤트 핸들러에서 호출하지 마세요.
  • 🔴 useMemo, useReducer 또는 useEffect에 전달된 함수 내에서 Hooks를 호출하지 마세요.

출처: https://ko.reactjs.org/warnings/invalid-hook-call-warning.html

 

 

우선 소스를 보고 문제 되는 부분을 짚어보자

전체 소스는 불필요하니 렌더링 하는 부분은 제외하겠다.

 

import { useState } from "react";
import Axios from "axios";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import { Link, useNavigate } from "react-router-dom";

/**
 *
 * @param {any} props
 * @return {SS} Component
 */
function Write(props: any) {
    const [inputs, setInputs] = useState({
        title: "",
        content: "",
    });
    const { title, content } = inputs;

    const write = () => {
        Axios.post("http://localhost:8000/insert", {
            title: title,
            content: content,
        })
            .then(() => {
                const navigate = useNavigate();
                navigate("/");
            })
            .catch((e) => {
                console.error(e);
            });
    };

    const update = () => {
        Axios.post("http://localhost:8000/update", {
            title: title,
            content: content,
            id: props.boardId,
        })
            .then(() => {
                props.handleCancel();
            })
            .catch((e) => {
                console.error(e);
            });
    };

    const handleChange = (e: any) => {
        const { name, value } = e.target;

        const nextInputs = {
            ...inputs,
            [name]: value,
        };
        setInputs(nextInputs);
    };
}

export default Write;

 

문제점 설명

  • ✅ 함수 컴포넌트 본문의 최상위 레벨에서 호출하세요.

나의 경우에 write함수에서 저장 완료 후 useNavigate를 호출하여 사용하고 있었다.

하지만 함수 컴포넌트 본문의 최상위 레벨이라고 하면, 함수형 컴포넌트인 Write를 말한다.

즉, const navigate = useNavigate(); 선언부를 밖으로 빼면 된다.

 

아래 바뀐 내용을 살펴보자.

 

// 변경전
function Write(props: any) {
    const [inputs, setInputs] = useState({
        title: "",
        content: "",
    });
    const { title, content } = inputs;

    const write = () => {
        Axios.post("http://localhost:8000/insert", {
            title: title,
            content: content,
        })
            .then(() => {
                const navigate = useNavigate();
                navigate("/");
            })
            .catch((e) => {
                console.error(e);
            });
    };
    ... 중략
}

// 변경후
function Write(props: any) {
    const [inputs, setInputs] = useState({
        title: "",
        content: "",
    });
    const { title, content } = inputs;
    const navigate = useNavigate();

    const write = () => {
        Axios.post("http://localhost:8000/insert", {
            title: title,
            content: content,
        })
            .then(() => {
                navigate("/");
            })
            .catch((e) => {
                console.error(e);
            });
    };
    ... 중략
}

 

이거 해결하려고 별짓을 다했다.. 자세한 내용은 패스.

아마 리액트 라이프사이클에 대한 이해도가 없는 상태에서 부딪히고 있는 상황이라 그런 것 같다.

그래도 일단 계속 뭐라도 해볼 생각이다.

 

 

3. You might have more than one copy of React in the same app

내 경우는 아니지만 혹시 여기에 해당하시는 분들이 계실 수도 있으니 방법을 공유한다.

 

확인방법

하나 이상의 react가 있는지 확인한다.

 

npm ls react

 

또는 공식 문서에서는 아래와 같이 제안한다.

 

// node_modules/react-dom/index.js에 아래를 추가하세요.
window.React1 = require('react');

// 컴포넌트 파일에 아래를 추가하세요.
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2);

 

첫 번째 무식한 방법

node_modules를 삭제한 후 npm install을 다시 한다.

 

두 번째 깔끔한 방법

중복된 react를 npm uninstall 하여 제거한다.

 

그럼.. 도움이 되시길 바랍니다.

반응형