Gotti27
Gotti27

Reputation: 33

React Router v6 changes URL but doesn't render

I'm working to a web app with React Router V6. Each user has several projects, according with React Router documentation, I declared routes in this way:

<Routes>
    <Route index path='/' element={<GeneralOverview user={props.user}> </GeneralOverview>}/>
    ... 
    <Route path='/users/:userId' element={<UserPage></UserPage>} />
    <Route path='/create-project' element={<CreateProject></CreateProject>} />
    <Route path='/projects/*'>
        <Route path=':projectId' element={<ProjectOverview />} />
    <Route path=':projectId/repos' element={<ProjectRepos></ProjectRepos>} />
    <Route path=':projectId/issues' element={<ProjectIssues></ProjectIssues>}/>
    <Route path=':projectId/issues/:issueId' element={<IssuePage></IssuePage>}></Route>
    ... 
</Routes>

I've build a sidebar, which shows a list of all user's projects with clickable elements to navigate to the project page. This is a child of the sidebar component, that renders a list of projects:

let ProjectsList = props => {
    let navigate = useNavigate();
    return props.projects.map(project =>
    <CNavItem key={project._id} href='#' onClick={event => {
        event.preventDefault();
        navigate("/projects/" + project._id);
    }}>
        {project.name}
    </CNavItem>)
}

Clicking the item, the URL changes from /projects/<old-id> to /projects/<new-id> (correct), but the component is still showing information about the previous project.

I've already tried several fix, like using navigate("/projects/" + project._id, {replace: true}); , but nothing worked. The only way seems to be using href, but I want this project to be a single page web app. So I just need to navigate without reloading the entire document.

--- edit: ProjectOverview component code ---

As requested, here's project overview code, omitted some "useless" parts.

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Link, Navigate, useLocation, useNavigate, useParams } from "react-router-dom";
import { CContainer, CAlert, CCard, CRow, CCol, CHeader, CHeaderBrand,
    CCardBody, CCardHeader, CCardTitle, CButton, CModal, CModalHeader, 
    CModalTitle, CModalBody, CFormInput, CModalFooter, CCardText, CFormLabel, 
    CFormSelect, CBadge, CHeaderDivider, CDropdownDivider} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import * as icon from '@coreui/icons';
import { CChart } from '@coreui/react-chartjs';


const ProjectOverview = props => {
    let { projectId } = useParams();
    let navigate = useNavigate();
    
    let [project, setProject] = useState();
    let [owner, setOwner] = useState();
    let [repos, setRepos] = useState();

    /**
     * some state variables for errors, handlers and data distribution
     */

    useEffect(() => {
        axios.get('http://localhost:4000/projects/' + projectId, {withCredentials:true}).then(res => {
            setProject(res.data);
            axios.get('http://localhost:4000/projects/' + projectId + '/owner', {withCredentials:true}).then(res => {
                setOwner(res.data);
                axios.get('http://localhost:4000/projects/' + projectId + '/repos', {withCredentials:true}).then(res => {
                    setRepos(res.data);
                    axios.get('http://localhost:4000/projects/' + projectId + '/issues', {withCredentials:true}).then(res => {
                        /**
                         * distributing data for charts
                         */
                        let repoDistrubution = [0,0,0,0];
                        let csetDistribution = [0,0,0,0];
                        let cSum = 0, rSum = 0;
                        for (const issue of res.data) {
                            if (issue.repoId) {
                                switch (issue.status) {
                                    case 'open': repoDistrubution[0]++; break;
                                    case 'ignored': repoDistrubution[1]++; break;
                                    case 'working on': repoDistrubution[2]++; break;
                                    case 'fixed': repoDistrubution[3]++; break;
                                }
                                rSum++;
                            } else {
                                switch (issue.status) {
                                    case 'open': csetDistribution[0]++; break;
                                    case 'ignored': csetDistribution[1]++; break;
                                    case 'working on': csetDistribution[2]++; break;
                                    case 'fixed': csetDistribution[3]++; break;
                                }
                                cSum++;
                            }
                        }
                        setCsetIssueDistribution(csetDistribution);
                        setReposIssueDistribution(repoDistrubution);
                        setCsetSum(cSum); setReposSum(rSum);
                    })
                });
            });
        }).catch(err => {
            if (err.status === 404) {
                setProjectNotFound(true);
            }
            console.log(err)
        });
        axios.get('https://api.github.com/user/repos', {headers: {
            'Authorization': 'Bearer ' + localStorage.getItem('github_token')
        }, params: {'per_page': 100, 'page':1}}
        ).then(res => {
            let repos = ['select one of your repos'];
            for (const repo of res.data) {
                repos.push(repo.full_name)
            }
            setAvailableRepos(repos);
        }).catch(err => console.log(err));
        axios.get('http://localhost:4000/avaiable-assessment', {withCredentials: true}).then(res => {
            let assessments = []
            for (const assessment of res.data) {
                assessments.push({
                    id: assessment.Id,
                    name: assessment.Assessment_Name
                })
            }
            setAvaiableAssessments(assessments);
        })
    }, []);

    /**
     * some handlers
     */

    return <CContainer>

        {project && repos ? (
            <div>
                <CHeader className="" style={{paddingTop:"0rem"}}>
                    <CCol>
                        <CRow>
                        <CCol md='auto' style={{borderRight: '1px solid grey'}}>   
                        <CHeaderBrand >Project name: {project.name}</CHeaderBrand>
                        </CCol>
                        <CCol md='auto' style={{borderRight: '1px solid grey', marginLeft:'1rem'}}>
                        <CHeaderBrand>Owner: {owner && owner.username}</CHeaderBrand>
                        </CCol>
                        <CCol md='auto' style={{marginLeft:'1rem'}}>
                        <CHeaderBrand>Status: {project.status}</CHeaderBrand>
                        </CCol>
                        </CRow>
                    </CCol>
                    <CCol md='auto' style={{margileft:'1rem', marginRight:'1rem'}}>
                        // buttons and handlers
                    </CCol>
                </CHeader><br/>
                <CRow>
                    <CCol>
                        <CCard>
                            <CCardBody>
                                <CCardTitle>Details</CCardTitle>
                                <CCardText>
                                    Description: {project.description}
                                </CCardText>
                                <CCardText>
                                    <CBadge style={{marginRight:'1rem'}} className="bg-dark">{project.issues.length}</CBadge> 
                                    <Link to={'/projects/' + project._id + '/issues'}
                                        state={{project: project}}
                                        style={{ textDecoration: 'none'}}>
                                        Issues
                                    </Link>
                                </CCardText>
                            </CCardBody>
                        </CCard>
                    </CCol>
                    <CCol>
                        <CCard>
                            <CCardBody>
                                <CCardTitle>
                                    Repositories
                                    <CButton onClick={() => setAddingRepo(true)}>
                                        <CIcon icon={icon.cilPlus}></CIcon> 
                                    </CButton> 
                                </CCardTitle>
                                <RepoList setProject={setProject} project={project} repos={repos}></RepoList>
                                // just prints the list of repos and handles thier removal
                            </CCardBody>
                        </CCard>
                    </CCol>
                    <CCol>
                        <CCard>
                            <CCardBody>
                                <CCardTitle>
                                    Collaborators
                                    <CButton onClick={() => setAddingCollab(true)}>
                                        <CIcon icon={icon.cilPlus}></CIcon> 
                                    </CButton> 
                                </CCardTitle>
                                <CollaboratorsList setProject={setProject} project={project} ></CollaboratorsList>
                                // just prints the list of collaborators and handles thier removal
                            </CCardBody>
                        </CCard>
                    </CCol>
                </CRow>
            </div>
        ) : (
            <div> skeleton (TODO)</div>
        ) }
    </CContainer>
}

export default ProjectOverview;

Upvotes: 3

Views: 2754

Answers (2)

Drew Reese
Drew Reese

Reputation: 203418

When the ProjectOverview component is rendered and the route path changes and projectId changes the useEffect hook isn't triggered again to fetch any data.

Add projectId to the useEffect hook's dependency array so when projectId value changes the effect is run again.

const ProjectOverview = (props) => {
  const { projectId } = useParams();
  ...

  useEffect(() => {
    axios
      .get("http://localhost:4000/projects/" + projectId, {
        withCredentials: true
      })
      .then((res) => {
        setProject(res.data);
        axios
          .get("http://localhost:4000/projects/" + projectId + "/owner", {
            withCredentials: true
          })
          .then((res) => {
            setOwner(res.data);
            axios
              .get("http://localhost:4000/projects/" + projectId + "/repos", {
                withCredentials: true
              })
              .then((res) => {
                setRepos(res.data);
                axios
                  .get(
                    "http://localhost:4000/projects/" + projectId + "/issues",
                    { withCredentials: true }
                  )
                  .then((res) => {
                    ...
                  });
              });
          });
      })
      .catch((err) => {
        ...
      });
    ...
  }, [projectId]);

Additionally, as only a suggestion, you should flatten your Promise chain. Promise chains were created to resolve an issue known as "nesting hell" involving nesting asynchronous callbacks and you've not escaped the nesting issue.

Example:

const options = { withCredentials: true };
axios
  .get("http://localhost:4000/projects/" + projectId, options)
  .then((res) => {
    setProject(res.data);
    return axios.get(
      "http://localhost:4000/projects/" + projectId + "/owner",
      options
    );
  })
  .then((res) => {
    setOwner(res.data);
    return axios.get(
      "http://localhost:4000/projects/" + projectId + "/repos",
      options
    );
  })
  .then((res) => {
    setRepos(res.data);
    return axios.get(
      "http://localhost:4000/projects/" + projectId + "/issues",
      options
    );
  })
  .then((res) => {
    /**
     * distributing data for charts
     */
    const repoDistrubution = [0, 0, 0, 0];
    const csetDistribution = [0, 0, 0, 0];
    let cSum = 0;
    let rSum = 0;
    for (const issue of res.data) {
      ...
    }
    setCsetIssueDistribution(csetDistribution);
    setReposIssueDistribution(repoDistrubution);
    setCsetSum(cSum);
    setReposSum(rSum);
  })
  .catch((err) => {
    if (err.status === 404) {
      setProjectNotFound(true);
    }
    console.log(err);
  });

Upvotes: 2

Muhammad Ahab
Muhammad Ahab

Reputation: 133

there an issue that there is not a closing tag for <Routes>

Upvotes: 0

Related Questions