为什么在我不更改 useEffect 挂钩的任何依赖项时触发 'exhaustive-deps' 规则?
Why is the 'exhaustive-deps' rule being triggered while I don't change any dependancies of my useEffect hook?
我已经构建了这个通用的 dropdown/select 组件,其中包含用于获取数据集的异步函数。出于某种原因,我收到消息 The 'fetchData' function makes dependencies of useEffect Hook (at line 48) change on every render。要解决此问题,请将 'fetchData' 的定义包装在它自己的 useCallback() Hook 中。eslintreact-hooks/exhaustive-deps.
我没有从我的 useEffect 挂钩中更改依赖项的任何值,因为这些属性由我的 redux 切片控制....
Select 分量:
import React, { useEffect } from 'react';
import { Form, Select, Typography } from 'antd';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const StyledSelect = styled(Select)`
&.ant-select-loading .ant-select-selection-item {
display: none;
}
`;
const { Text } = Typography;
const CustomSelect = ({
endpointKey,
dataKey,
customLabel,
required = false,
dataSet,
fetchDataSet,
disabled = false,
fullOptionHeight = false,
onChange = null,
showSearch = false,
setLoading = null,
}) => {
const fetchData = async (searchText) => {
if (setLoading) {
setLoading(true);
}
await fetchDataSet({ endpointKey, searchText });
if (setLoading) {
setLoading(false);
}
};
useEffect(() => {
const dataSetPresent = !!dataSet.data && !!Object.keys(dataSet.data).length;
const hasError = dataSet.errorMessage && dataSet.errorMessage.length;
if (
!dataSetPresent &&
dataSet.loadingStatus !== 'loading' &&
dataSet.loadingStatus !== 'loaded' &&
!hasError
) {
fetchData();
}
}, [fetchData, dataSet]);
const { loadingStatus, data, errorMessage } = dataSet;
const label = customLabel || endpointKey;
const formErrorMessage = 'Please select ' + label.toLowerCase();
const placeholder = `-- Select a ${label.toLowerCase()} --`;
const renderSelect = () => {
if (errorMessage !== '') {
return <Text type="danger">{errorMessage}</Text>;
}
return (
<StyledSelect
disabled={loadingStatus === 'loading' || disabled}
loading={loadingStatus === 'loading'}
placeholder={placeholder}
optionFilterProp="children"
style={{ maxWidth: '500px' }}
size="large"
onChange={onChange}
showSearch={showSearch}
onSearch={(value) => {
if (showSearch) {
fetchData(value);
}
}}
>
{Object.keys(data).map((dataObject) => {
return (
<Select.Option
className={`${fullOptionHeight ? 'full-option-height' : ''}`}
value={dataObject}
key={dataObject}
>
{data[dataObject]}
</Select.Option>
);
})}
</StyledSelect>
);
};
return (
<Form.Item
label={label}
name={dataKey}
rules={[{ required, message: formErrorMessage }]}
>
{renderSelect()}
</Form.Item>
);
};
CustomSelect.propTypes = {
endpointKey: PropTypes.string.isRequired,
dataKey: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
customLabel: PropTypes.string,
required: PropTypes.bool,
fetchDataSet: PropTypes.func,
showSearch: PropTypes.bool,
dataSet: PropTypes.object,
disabled: PropTypes.bool,
fullOptionHeight: PropTypes.bool,
onChange: PropTypes.func,
setLoading: PropTypes.func,
};
export default CustomSelect;
使用异步挂钩和状态更改处理的 React Slice:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import callApi from 'utils/api';
export const SELECT_KEY = 'select';
export const fetchDataSet = createAsyncThunk(
'select/request-data',
async ({ endpointKey, searchText }) => {
const endpoint = `data/${endpointKey}`;
try {
const { data } = await callApi({
endpoint,
});
return data;
} catch (error) {
console.error('ERROR', error);
throw error;
}
}
);
export const selectSlice = createSlice({
name: SELECT_KEY,
initialState: {},
reducers: {},
extraReducers: {
[fetchDataSet.pending]: (state, action) => {
const key = action.meta.arg.endpointKey;
return {
...state,
[key]: {
loadingStatus: 'loading',
errorMessage: '',
data: {},
},
};
},
[fetchDataSet.fulfilled]: (state, action) => {
const key = action.meta.arg.endpointKey;
return {
...state,
[key]: {
loadingStatus: 'loaded',
errorMessage: '',
data: action.payload,
},
};
},
[fetchDataSet.rejected]: (state, action) => {
const key = action.meta.arg.endpointKey;
return {
...state,
[key]: {
loadingStatus: 'error',
errorMessage: action.error.message,
data: {},
},
};
},
},
});
export const selectReducer = selectSlice.reducer;
export const dataSetSelector = (state, key) => state.select[key] || {};
您实际上在每个渲染周期都更改了依赖项的值,因为您在每个周期都重新声明 fetchData
。该警告建议您简单地记住此回调函数,以便为效果挂钩提供稳定的参考。
const fetchData = useCallback(async (searchText) => {
if (setLoading) {
setLoading(true);
}
await fetchDataSet({ endpointKey, searchText });
if (setLoading) {
setLoading(false);
}
}, [endpointKey]); // <-- add any other necessary dependencies for the callback
我已经构建了这个通用的 dropdown/select 组件,其中包含用于获取数据集的异步函数。出于某种原因,我收到消息 The 'fetchData' function makes dependencies of useEffect Hook (at line 48) change on every render。要解决此问题,请将 'fetchData' 的定义包装在它自己的 useCallback() Hook 中。eslintreact-hooks/exhaustive-deps.
我没有从我的 useEffect 挂钩中更改依赖项的任何值,因为这些属性由我的 redux 切片控制....
Select 分量:
import React, { useEffect } from 'react';
import { Form, Select, Typography } from 'antd';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const StyledSelect = styled(Select)`
&.ant-select-loading .ant-select-selection-item {
display: none;
}
`;
const { Text } = Typography;
const CustomSelect = ({
endpointKey,
dataKey,
customLabel,
required = false,
dataSet,
fetchDataSet,
disabled = false,
fullOptionHeight = false,
onChange = null,
showSearch = false,
setLoading = null,
}) => {
const fetchData = async (searchText) => {
if (setLoading) {
setLoading(true);
}
await fetchDataSet({ endpointKey, searchText });
if (setLoading) {
setLoading(false);
}
};
useEffect(() => {
const dataSetPresent = !!dataSet.data && !!Object.keys(dataSet.data).length;
const hasError = dataSet.errorMessage && dataSet.errorMessage.length;
if (
!dataSetPresent &&
dataSet.loadingStatus !== 'loading' &&
dataSet.loadingStatus !== 'loaded' &&
!hasError
) {
fetchData();
}
}, [fetchData, dataSet]);
const { loadingStatus, data, errorMessage } = dataSet;
const label = customLabel || endpointKey;
const formErrorMessage = 'Please select ' + label.toLowerCase();
const placeholder = `-- Select a ${label.toLowerCase()} --`;
const renderSelect = () => {
if (errorMessage !== '') {
return <Text type="danger">{errorMessage}</Text>;
}
return (
<StyledSelect
disabled={loadingStatus === 'loading' || disabled}
loading={loadingStatus === 'loading'}
placeholder={placeholder}
optionFilterProp="children"
style={{ maxWidth: '500px' }}
size="large"
onChange={onChange}
showSearch={showSearch}
onSearch={(value) => {
if (showSearch) {
fetchData(value);
}
}}
>
{Object.keys(data).map((dataObject) => {
return (
<Select.Option
className={`${fullOptionHeight ? 'full-option-height' : ''}`}
value={dataObject}
key={dataObject}
>
{data[dataObject]}
</Select.Option>
);
})}
</StyledSelect>
);
};
return (
<Form.Item
label={label}
name={dataKey}
rules={[{ required, message: formErrorMessage }]}
>
{renderSelect()}
</Form.Item>
);
};
CustomSelect.propTypes = {
endpointKey: PropTypes.string.isRequired,
dataKey: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
customLabel: PropTypes.string,
required: PropTypes.bool,
fetchDataSet: PropTypes.func,
showSearch: PropTypes.bool,
dataSet: PropTypes.object,
disabled: PropTypes.bool,
fullOptionHeight: PropTypes.bool,
onChange: PropTypes.func,
setLoading: PropTypes.func,
};
export default CustomSelect;
使用异步挂钩和状态更改处理的 React Slice:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import callApi from 'utils/api';
export const SELECT_KEY = 'select';
export const fetchDataSet = createAsyncThunk(
'select/request-data',
async ({ endpointKey, searchText }) => {
const endpoint = `data/${endpointKey}`;
try {
const { data } = await callApi({
endpoint,
});
return data;
} catch (error) {
console.error('ERROR', error);
throw error;
}
}
);
export const selectSlice = createSlice({
name: SELECT_KEY,
initialState: {},
reducers: {},
extraReducers: {
[fetchDataSet.pending]: (state, action) => {
const key = action.meta.arg.endpointKey;
return {
...state,
[key]: {
loadingStatus: 'loading',
errorMessage: '',
data: {},
},
};
},
[fetchDataSet.fulfilled]: (state, action) => {
const key = action.meta.arg.endpointKey;
return {
...state,
[key]: {
loadingStatus: 'loaded',
errorMessage: '',
data: action.payload,
},
};
},
[fetchDataSet.rejected]: (state, action) => {
const key = action.meta.arg.endpointKey;
return {
...state,
[key]: {
loadingStatus: 'error',
errorMessage: action.error.message,
data: {},
},
};
},
},
});
export const selectReducer = selectSlice.reducer;
export const dataSetSelector = (state, key) => state.select[key] || {};
您实际上在每个渲染周期都更改了依赖项的值,因为您在每个周期都重新声明 fetchData
。该警告建议您简单地记住此回调函数,以便为效果挂钩提供稳定的参考。
const fetchData = useCallback(async (searchText) => {
if (setLoading) {
setLoading(true);
}
await fetchDataSet({ endpointKey, searchText });
if (setLoading) {
setLoading(false);
}
}, [endpointKey]); // <-- add any other necessary dependencies for the callback