react-router-bootstrap LinkContainer not redirecting/refreshing on click

Viewed 198

I'm trying to implement pagination on a route with react-router and react-router-bootstrap. The issue is that I'm using LinkContainers that should render the same route with a different search parameter that contains the 'page' I'm jumping to, but when i click it, it just updates the address bar in the browser bud doesn't re-render the route.

The setup:

{"dependencies": {
    "axios": "^0.26.0",
    "bootstrap": "^5.1.3",
    "react": "^17.0.2",
    "react-bootstrap": "^2.2.0",
    "react-dom": "^17.0.2",
    "react-router-bootstrap": "^0.26.0",
    "react-router-dom": "^6.2.2",
    "react-scripts": "5.0.0",
    "web-vitals": "^2.1.4"
  }}

App.js:

import { Container } from 'react-bootstrap'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import CompanyCreateScreen from './screens/CompanyCreateScreen'
import CompanyDirScreen from './screens/CompanyDirScreen'
function App() {
  return (
      <Router>
        <main className="py-3">
          <Container className="cont-width">
            <Routes>
              <Route path="/" exact element={<CompanyDirScreen />} />
              <Route path="/new_company" element={<CompanyCreateScreen />} />
            </Routes>
          </Container>
        </main>
      </Router>

  )
}

CompanyDirScreen.jsx :

(most of it)

import React, { useEffect, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { LinkContainer } from 'react-router-bootstrap'
import { Row, Col, Button } from 'react-bootstrap'
import UniContainer from '../components/UniContainer'
import Loader from '../components/Loader'
import Message from '../components/Message'
import CompanyList from '../components/CompanyList'
import Paginate from '../components/Paginate'

import axios from 'axios'

const API_URL = 'http://localhost:8000/api'

const CompanyDirScreen = () => {
  const [companies, setCompanies] = useState({})
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  const [page, setPage] = useState(0)
  const [pages, setPages] = useState(0)

  // Location info to determine the page to jump to
  const location = useLocation()
  let keywords = location.search

  useEffect(() => {
    console.log(keywords)
    getCompanies((keywords = keywords))
  }, [])

  // Get companies
  const getCompanies = async (keywords = '') => {
    setLoading(true)
    try {
      const { data } = await axios.get(`${API_URL}/companies/${keywords}`)
      setCompanies(data.companies)
      setPage(data.page)
      setPages(data.pages)
    } catch (error) {
      const e =
        error.response && error.response.data.detail
          ? error.response.data.detail
          : error.message
      setError(e)
    }
    setLoading(false)
  }

  // Delete single company
  const deleteCompany = async (id) => {
  }

  return (
    <UniContainer>
      <Row className="mb-3">
        <Col>
          <h3>Directorio de empresas</h3>
        </Col>
        <Col xs={3}>
          <LinkContainer to="/new_company">
            <Button>Nueva empresa</Button>
          </LinkContainer>
        </Col>
      </Row>

      {loading ? (
        <Loader />
      ) : error ? (
        <Message variant="danger">{error}</Message>
      ) : companies.length === 0 ? (
        <Message variant="secondary">
          Aun no hay empresas registradas, se el primero haciendo click en{' '}
          <b>"Añadir empresa"</b>
        </Message>
      ) : (
        <>
          <CompanyList companies={companies} handleDelete={deleteCompany} />
          <Paginate page={page} pages={pages} />
        </>
      )}
    </UniContainer>
  )
}

export default CompanyDirScreen

... pagination is a separate component

Paginate.jsx

import { Pagination } from 'react-bootstrap'
import { LinkContainer } from 'react-router-bootstrap'

const Paginate = ({ pages, page }) => {
  return (
    pages > 1 && (
      <Pagination className="justify-content-center">
        {page > 1 && (
          <LinkContainer to={`/?page=${page - 1}`}>
            <Pagination.Prev />
          </LinkContainer>
        )}
        {[...Array(pages).keys()].map((x) => (
          <LinkContainer key={x + 1} to={`/?page=${x + 1}`}>
            <Pagination.Item active={x + 1 === page}>{x + 1}</Pagination.Item>
          </LinkContainer>
        ))}
        {page !== pages && (
          <LinkContainer to={`/?page=${page + 1}`}>
            <Pagination.Next />
          </LinkContainer>
        )}
      </Pagination>
    )
  )
}

export default Paginate

Right now when clicking any of the 'pagination' links it updates the address bar in the browser like this: http://localhost:3000/ -> http://localhost:3000/?page=2 -> http://localhost:3000/?page= etc...

But it doesn't update the page contents and the console doesn't show any errors/warnings.

I can, however, select the url (eg http://localhost:3000/?page=2) and hit enter manually in the browser address bar and then it renders the desired page.

I'm suspecting it has something to do with the links being in a separate component, I think it's possible to create a function in CompanyDirScreen.jsx that 'navigates' to the requested page and pass it as a prop to Paginate.jsx although it feels a little messy, other option is to do the pagination directly in CompanyDirScreen.jsx but then I wouldn't be able to reuse the component in other routes I'm planning to.

Is there a way to use LinkContainer to navigate between pages as described?

1 Answers

The useEffect hook in CompanyDirScreen is using an empty dependency array, so the effect is run only once when the component mounts. This is why the URL works when you manually enter it in the address bar.

Add keywords to the dependency array so when the search param value updates the useEffect hook callback will be triggered again.

// Location info to determine the page to jump to
const { search: keywords } = useLocation();

useEffect(() => {
  console.log(keywords);
  getCompanies(keywords);
}, [keywords]);
Related