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;