Joon K
Joon K

Reputation: 159

How can I resolver " useHref() may be used only in the context of a <Router> component" when component is already inside <Router>?

I am currently writing Jest tests for my react app.

When I run the tests for <Container />, the error message shows:

" useHref() may be used only in the context of a component."

However, <Container /> is already inside <Router/>.

I have read other similar questions on here, and the answers all say to put the component inside the <Router/>, which is what I did.

Container.test.js

import { render, unmountComponentAtNode } from 'react-dom';
import Container from '../components/Container.tsx';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import { configure, shallow, mount } from 'enzyme';
import { Provider, useDispatch } from 'react-redux';
import { act } from "react-dom/test-utils";
import configureStore from 'redux-mock-store';

configure({adapter: new Adapter()});

const initState = { allCard: [], expansions: [] };
const mockStore = configureStore();
const wrapper = mount(<Provider store={mockStore(initState)}><Container/></Provider>);

describe('Container', () => {

    let container = null;
    beforeEach(() => {
    // setup a DOM element as a render target
    container = document.createElement("div");
    document.body.appendChild(container);
    });

    afterEach(() => {
    // cleanup on exiting
    unmountComponentAtNode(container);
    container.remove();
    container = null;
    });

    it('renders itself and its children without crashing', () => {
        render(<Provider store={mockStore(initState)}><Container /></Provider>, container);
    });

    it('renders itself without crashing', () => {
        const tree = wrapper.render();
        expect(tree.toJSON).toMatchSnapshot();
        });
});

App.js

import './App.css';
import Container from './components/Container';
import About from './components/RouteComponents/About';
import NavBar from './components/NavBar';
import Tutorials from './components/RouteComponents/Tutorials';
import Suggest from './components/RouteComponents/Suggest';
import Report from './components/RouteComponents/Report';
import { Route, Routes, BrowserRouter as Router } from 'react-router-dom';
import GlobalStyles from './components/styled/Globals.styled';
import { useState, createContext } from 'react';

function App() {
  const [ darkMode, setDarkMode ] = useState(false);

  return (
    <Router> 
      {darkMode? <GlobalStyles/> : null}
        <div className="App">
          <NavBar darkMode={darkMode} setDarkMode={setDarkMode}/>
          <Routes>
            <Route path='/' element= {<UserContext.Provider value={darkMode}>
                                          <Container />
                                      </UserContext.Provider>} />
            <Route path='/about' element={<About />} />
            <Route path='/tutorial' element= {<Tutorials />} />
            <Route path='/suggest' element={<Suggest />} />
            <Route path='/report' element= {<Report />} />
          </Routes>
        </div>
    </Router>
  );
}
export const UserContext = createContext(null);
export default App;

Container.tsx

import { useEffect, ReactElement } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { useDispatch } from 'react-redux'
import ErrorBoundary from './ErrorBoundary';
import ExpansionView from './ExpansionView';
import DisplayView from './DisplayView';
import SearchCard from './SearchCard';
import MurlocTidehunter from '../image/murlocTidehunter.png';
import SelectExpanionsMurloc from '../image/selectExpansions.png';
import { StyledContainer } from './styled/Container.styled';
import { eliminateMurloc } from '../utils/eliminateMurloc';

// Component - parent component that fetches allCard once when app launches
const Container = (): ReactElement => {
    const dispatch = useDispatch();
    
    useEffect(() => {
        const options: object = {
            method: 'GET',
            url: 'https://omgvamp-hearthstone-v1.p.rapidapi.com/cards',
            params: {collectible: '1'},
            headers: {
            'x-rapidapi-host': 'omgvamp-hearthstone-v1.p.rapidapi.com',
            'x-rapidapi-key': 'xxxxxxxxxx'
            }
        };
       
        // function to fetch all collectible cards from hearthstone api
        const fetchAllCards = async (): Promise<any> => {
            try {
                const response = await axios.request(options); 
                dispatch({type: 'SET_ALL_CARD', payload: response.data})
            } catch(e) {
                console.log(e)
            }
        }
        fetchAllCards();
    }, []);

    return (
        <StyledContainer>
            <h1 id='hearthfin'>Hearthfin</h1>
            <img id='tideHunter' className='deletableMurloc' src={MurlocTidehunter} alt="Hearthstone Murloc Analisis Videojuegos Zehngames - [email protected]" />
            <img id='selectExpMurloc' className='deletableMurloc' onClick={() => eliminateMurloc('selectExpMurloc')} src={SelectExpanionsMurloc} alt="Murloc that say select expansions first!" />
            <ErrorBoundary>
                <ExpansionView /> 
            </ErrorBoundary>
            <ErrorBoundary>
                <DisplayView />
            </ErrorBoundary>
            <p id='missingCard'>See a missing AOE card? Make a suggestion <Link to='suggest'>here!</Link></p>
            <ErrorBoundary>
                <SearchCard />
            </ErrorBoundary>
            <br/>
           
        </StyledContainer>
    );
}

export default Container;

Upvotes: 1

Views: 3393

Answers (3)

Dan Sterrett
Dan Sterrett

Reputation: 1191

You need to mock the Link component like. Add this before any of your tests:

jest.mock('react-router-dom', () => ({
  Link: (props) => {
    return <a {...props} href={props.to} />;
  },
}));

Upvotes: 2

Hasan
Hasan

Reputation: 3

In your "Container.tsx" file, wrap the component like this:

import { BrowserRouter as Router } from 'react-router-dom';

<Router> 
 <StyledContainer>
    //jsx elements
 </StyledContainer>
<Router> 

Now "Link" of the Container component will not get confused as Link will find Router as a wrapper of its component.

Don't forget to remove from the "app.js" file. The rest will be the same as before.

Upvotes: 0

MouadAmzil
MouadAmzil

Reputation: 21

You are rendering the navbar outside the routing context. The Router isn't aware of what routes the links are attempting to link t that it is managing. The reason routing works when directly navigating to "/" is because the Router is aware of the URL when the app mounts

Upvotes: 2

Related Questions