我目前正在学习React / hooks / redux。为此,我正在构建一个从气候API接收数据的react应用。
我遇到的问题是正确设置useEffect中的几个项目的状态。一种状态依赖于另一种状态,因此我试图找出如何正确调用useEffect的方法,这样就不会出现无限循环并遵循最佳实践。
下面的代码之前有一点背景:
-用户创建一个项目,然后选择一个城市。这将产生一个cityId,我将其存储在“项目”状态下。
-在用户的仪表板上,他们可以单击一个项目,该项目将queryString中的项目ID发送到我的ClimateData组件。
-ClimateData将项目ID queryString传递到“ getProjectByID” redux操作以获取项目状态,包括城市ID。
-ClimateData包含IndicatorList组件,该组件会列出所有气候数据突破的列表。我希望用户单击这些列表项之一,并设置ClimateData的“ indicatorByCityData”状态。因此,我将ClimateData的setState函数传递给了IndicatorList,并使用onClicks进行了列表调用。 是否有更好的方法?
-在ClimateData上,一旦有了项目的cityId和从IndicatorList中选择的项目,我就需要调用“ getIndicatorByCity”并传递cityId和indicator以将结果保存在“ indicatorByCityData”状态
我一直在尝试更改ClimateData的useEffect的编写方式,但是我遇到了无限循环或错误。我怎样才能最好地更改此设置以设置两种状态并遵循最佳做法?
redux动作和减速器已经在其他地方进行了测试,并且可以正常工作,因此,为了简洁起见,在此将它们排除在外,而只关注我的ClimateData和IndicatorList组件:
import React, { Fragment, useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import IndicatorList from './IndicatorList';
import Spinner from '../layout/Spinner';
import { getProjectById } from '../../actions/projects';
import { getIndicatorByCity } from '../../actions/climate';
const ClimateData = ({
getProjectById,
getIndicatorByCity,
project: { project, loading },
auth,
match
}) => {
const [indicatorByCityData, setIndicatorByCityData] = useState({});
const nullProject = !project;
useEffect(() => {
if (!project) getProjectById(match.params.id);
// Once we have the cityID, set the indicatorByCityData state, with a default selected Indicator
if (!loading) setIndicatorByCityData(getIndicatorByCity(project.cityId));
}, []);
// Get the selected indicator from IndicatorList and update the indicatorByCityData state
const setIndicator = indicator => {
setIndicatorByCityData(getIndicatorByCity(project.cityId, null, indicator));
};
return (
<Fragment>
{project === null || loading || !indicatorByCityData ? (
<Spinner />
) : (
<Fragment>
<Link to='/dashboard' className='btn btn-light'>
Back To Dashboard
</Link>
<h1 className='large text-primary'>{`Climate Data for ${project.city}`}</h1>
<IndicatorList setIndicator={setIndicator} />
</Fragment>
)}
</Fragment>
);
};
ClimateData.propTypes = {
getProjectById: PropTypes.func.isRequired,
getIndicatorByCity: PropTypes.func.isRequired,
project: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
project: state.projects,
auth: state.auth
});
export default connect(mapStateToProps, { getProjectById, getIndicatorByCity })(
ClimateData
);
/******************************************************************/
import React, { useEffect, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Spinner from '../layout/Spinner';
import { getIndicatorList } from '../../actions/climate';
const IndicatorList = ({
getIndicatorList,
auth: { user },
climateList: { indicatorList, loading },
setIndicator
}) => {
useEffect(() => {
getIndicatorList();
}, [getIndicatorList]);
return loading ? (
<Spinner />
) : (
<Fragment>
{indicatorList.length > 0 ? (
<Fragment>
<ul>
{indicatorList.map(indicator => (
<li key={indicator.name}>
<a href='#!' onClick={() => setIndicator(indicator.name)}>
{indicator.label}
</a>
<br />- {indicator.description}
</li>
))}
</ul>
</Fragment>
) : (
<h4>No climate indicators loaded</h4>
)}
</Fragment>
);
};
IndicatorList.propTypes = {
auth: PropTypes.object.isRequired,
climateList: PropTypes.object.isRequired,
setIndicator: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
climateList: state.climate
});
export default connect(mapStateToProps, { getIndicatorList })(IndicatorList);