React Router v6 更改 URL 但不呈现
React Router v6 changes URL but doesn't render
我正在使用 React Router V6 开发一个网络应用程序。每个用户都有几个项目,根据 React Router 文档,我是这样声明路由的:
<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>
我构建了一个边栏,它显示了所有用户项目的列表,其中包含用于导航到项目页面的可点击元素。这是侧边栏组件的子组件,呈现项目列表:
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>)
}
单击该项目,URL 从 /projects/<old-id>
变为 /projects/<new-id>
(正确),但该组件仍显示有关上一个项目的信息。
我已经尝试了几个修复方法,比如使用 navigate("/projects/" + project._id, {replace: true});
,但没有任何效果。唯一的方法似乎是使用 href,但我希望这个项目是一个单页 Web 应用程序。所以我只需要在不重新加载整个文档的情况下进行导航。
--- 编辑:ProjectOverview 组件代码 ---
根据要求,这里是项目概览代码,省略了一些“无用”的部分。
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;
<Routes>
没有结束标签
当呈现 ProjectOverview
组件并且路由路径发生变化并且 projectId
发生变化时,useEffect
挂钩不会再次触发以获取任何数据。
将 projectId
添加到 useEffect
挂钩的依赖数组中,这样当 projectId
值改变时效果又是 运行。
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]);
此外,仅作为建议,您应该展平您的 Promise 链。 Promise 链的创建是为了解决称为“嵌套地狱”的问题,该问题涉及嵌套异步回调,而您并未逃脱嵌套问题。
示例:
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);
});
我正在使用 React Router V6 开发一个网络应用程序。每个用户都有几个项目,根据 React Router 文档,我是这样声明路由的:
<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>
我构建了一个边栏,它显示了所有用户项目的列表,其中包含用于导航到项目页面的可点击元素。这是侧边栏组件的子组件,呈现项目列表:
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>)
}
单击该项目,URL 从 /projects/<old-id>
变为 /projects/<new-id>
(正确),但该组件仍显示有关上一个项目的信息。
我已经尝试了几个修复方法,比如使用 navigate("/projects/" + project._id, {replace: true});
,但没有任何效果。唯一的方法似乎是使用 href,但我希望这个项目是一个单页 Web 应用程序。所以我只需要在不重新加载整个文档的情况下进行导航。
--- 编辑:ProjectOverview 组件代码 ---
根据要求,这里是项目概览代码,省略了一些“无用”的部分。
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;
<Routes>
当呈现 ProjectOverview
组件并且路由路径发生变化并且 projectId
发生变化时,useEffect
挂钩不会再次触发以获取任何数据。
将 projectId
添加到 useEffect
挂钩的依赖数组中,这样当 projectId
值改变时效果又是 运行。
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]);
此外,仅作为建议,您应该展平您的 Promise 链。 Promise 链的创建是为了解决称为“嵌套地狱”的问题,该问题涉及嵌套异步回调,而您并未逃脱嵌套问题。
示例:
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);
});