[React recoil] Warning: You passed a container to the second argument of root.render(...). You don't need to pass it again since you already passed it to create the root. 에러 해결
1. 에러 발생
recoil 라이브러리로 비동기 통신을 하면서 위와 같은 에러가 발생하였다.
포켓몬 api 사이트에서 특정 포켓몬에 대한 데이터를 가져오려는 시도를 했다.
console창을 보면 정상적으로 데이터를 가져오는데도 불구하고 렌더링이 되지 않고 loading상태에서 머물러 있는 것을 확인할 수 있었다.
대략적인 코드는 다음과 같았다.
<포켓몬 api와 통신하는 selector 코드>
export const pokemonInfoQuery = (id) =>
selector({
key: `pokemonInfoQuery-${id}`,
get: async () => {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const data = await response.json();
console.log(data);
return data;
},
});
<pokemonInfoQuery의 selector에 반환된 값을 받아오는 코드>
const PokemonDetailPage = () => {
const { id } = useParams();
const navigate = useNavigate();
const pokemonId = parseInt(id);
const pokemonInfoLoadable = useRecoilValueLoadable(
pokemonInfoQuery(pokemonId)
);
const handleBack = () => {
navigate(-1);
};
const types =
pokemonInfoLoadable.state === 'hasValue'
? pokemonInfoLoadable.contents.types
: [];
return (
<div>
<Title>Pokémon Wiki</Title>
<Container>
<ImageContainer>
<PokemonImage
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemonId}.png`}
alt={pokemonInfoLoadable.contents.name}
/>
</ImageContainer>
{console.log(pokemonInfoLoadable.state)}
{pokemonInfoLoadable.state === 'loading' && <p>Loading...</p>}
{pokemonInfoLoadable.state === 'hasValue' && (
<>
<PokemonName>{pokemonInfoLoadable.contents.name}</PokemonName>
<PokemonId>ID: {pokemonInfoLoadable.contents.id}</PokemonId>
{types.length > 0 && (
<TypeContainer>
{types.map((type) => (
<Type key={type.slot}>{type.type.name}</Type>
))}
</TypeContainer>
)}
</>
)}
{pokemonInfoLoadable.state === 'hasError' && (
<p>Error: {pokemonInfoLoadable.contents.message}</p>
)}
</Container>
<button onClick={handleBack}>Back</button>
</div>
);
};
export default PokemonDetailPage;
2. 접근 방법
포켓몬 이미지의 경우, useRecoilLoadable의 state값과 상관없이 url로 부터 이미지를 가져오기 때문에 이상이 없었으나, state값이 계속 loading상태에 머물러 있기 때문에 데이터를 정상적으로 가져왔음에도 불구하고 렌더링이 되지 않는 것이었다.
다행히도 크롬 개발자 도구의 콘솔창에 나타난
Warning: You passed a container to the second argument of root.render(...). You don't need to pass it again since you already passed it to create the root.
라는 에러를 보고 문제를 해결할 수 있었다.
원인은 index.js에 있었다.
기존 코드
import ReactDOM from 'react-dom/client';
import { RecoilRoot } from 'recoil';
import App from './App';
import reportWebVitals from './reportWebVitals';
import React from 'react';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
이 에러는 React-dom 패키지가 React 응용 프로그램을 렌더링하는 방법과 관련이 있다.
내가 이미 createRoot() 메서드에 root.render()메서드를 전달해놓고 거기다가 또 container를 전달하려고 했기 때문에 에러가 발생한 것이다.
ReciolRoot와 App 컴포넌트를 하위로 전달하되, root 컴포넌트를 다시 전달하도록 하면 안되었다.
3. 문제 해결
해결한 코드
(index.js 코드를 다음과 같이 수정한다.)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RecoilRoot } from 'recoil';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = document.getElementById('root');
ReactDOM.createRoot(root).render(
<RecoilRoot>
<App />
</RecoilRoot>
);
reportWebVitals();