[英]useReducer and useEffect hooks to fetch data
我正在试验useEffect
钩子,但我对如何使用useEffect
和useReducer
感到有些困惑。
我有一个带有几个复选框的简单页面,在更改每个复选框时,我应该重新查询 api 以加载新数据。
我的想法是对组件进行如下编码:
export const Page: React.FunctionComponent = () => {
// state to manage the list returned by the api
const [list, setList] = useState<any[]>([]);
// this should manage the state related to the checkboxes
const [checkboxesState, dispatch] = useReducer(reducer, initialCheckboxesState);
useEffect(() => {
// load data from api
loadProducts(checkboxesState);
}, [checkboxesState]); // my idea here is that when checkboxesState changes, it should trigger the 'useEffect' hook
}
现在关于 useReducer
// initial state
const initialFiltersState: CheckboxesState = {
country: [], // array of strings for countries ie ['USA', 'Brazil', 'India']
};
function reducer(
checkboxesState: CheckboxesState,
action: { type: string; value: string; checked: boolean }
) {
const index = checkboxesState[action.type].indexOf(action.value);
// if checkbox is ticked and it's not already present in the array, insert it
if (action.checked && index === -1) {
checkboxesState[action.type].push(action.value);
} else if (!action.checked && index > -1) {
// if it's present and we are unchecking it, remove it from the array
checkboxesState[action.type].splice(index, 1);
}
// TODO: this is not the best way to handle reload. I don't want to do it here, this should just trigger the useEffect
loadProducts(productFilters);
return checkboxesState;
}
// this is the handler for checkbox changes
const onChangeHandler = (event: any): void => {
dispatch({
type: event.target.name, // eg. country
value: event.target.value, // eg. Australia
checked: event.target.checked,// bool
});
};
现在我知道我在做的事情可能很愚蠢,但我已经坚持了很长一段时间。 知道为什么在checkboxesState
更改时不调用 useEffect 吗? 还是我完全偏离了轨道?
我试图将JSON.stringify(checkboxesState)
传递给 useEffect,但也没有运气。
我的组件中有更多不相关的内容,因此我尝试仅将与特定任务相关的代码放在这里。 我希望我清楚地解释了自己
您没有正确更新状态。
状态更新时未触发useEffect
原因是因为您正在直接改变状态。 您正在从减速器返回相同的状态对象。 因此,就 React 而言,reducer 函数在调用时不会更新状态。 因此,不会再次触发useEffect
。
您需要从 reducer 返回一个新的 state 对象,以便 React 知道该 state 已被 reducer 更新。 修改您的减速器以在每次更新状态时返回一个新对象。
另一种解决方案可能是使用 thunk 动作,这些动作总是可以访问最近的状态并且可以分派多个动作。 Thunk 动作甚至可以调用其他 thunk 动作并等待它们(如果 thunk 动作返回承诺): thunkAction(arg)(dispatch,getState).then(thunkActionResult=>otherStuff)
。
Thunk 有很好的文档记录并且很容易被其他开发人员识别。
Thunk 是 Redux 的中间件,但可以通过自定义钩子应用于 useReducer:
const { useRef, useState } = React; //custom hook, you can define in different file const compose = (...fns) => fns.reduce((result, fn) => (...args) => fn(result(...args)) ); const mw = () => (next) => (action) => next(action); const createMiddleware = (...middlewareFunctions) => ( store ) => compose( ...middlewareFunctions .concat(mw) .reverse() .map((fn) => fn(store)) ); const useMiddlewareReducer = ( reducer, initialState, middleware = () => (b) => (c) => b(c) ) => { const stateContainer = useRef(initialState); const [state, setState] = useState(initialState); const dispatch = (action) => { const next = (action) => { stateContainer.current = reducer( stateContainer.current, action ); return setState(stateContainer.current); }; const store = { dispatch, getState: () => stateContainer.current, }; return middleware(store)(next)(action); }; return [state, dispatch]; }; //middleware const thunkMiddleWare = ({ getState, dispatch }) => ( next ) => (action) => typeof action === 'function' ? action(dispatch, getState) : next(action); const logMiddleware = ({ getState }) => (next) => ( action ) => { console.log('in log middleware', action, getState()); Promise.resolve().then(() => console.log('after action:', action.type, getState()) ); return next(action); }; //your actions const init = { value: 'A' }; const TOGGLE = 'TOGGLE'; const later = () => new Promise((r) => setTimeout(() => r(), 500)); const otherThunk = () => (dispatch, getState) => { dispatch({ type: 'not relevant action form otherTunk' }); console.log('in other thunk, state is:', getState()); //note that I am returning a promise return later(); }; const thunkToggle = () => (dispatch, getState) => { //dispatching other thunk and waiting for it to finish otherThunk()(dispatch, getState).then(() => dispatch({ type: TOGGLE }) ); }; //your component code const reducer = (state, { type }) => { console.log(`in reducer action type: ${type}`); //toggle state.value between A and B if (type === TOGGLE) { return { value: state.value === 'A' ? 'B' : 'A' }; } return state; }; const middleware = createMiddleware( thunkMiddleWare, logMiddleware ); const App = () => { const [state, dispatch] = useMiddlewareReducer( reducer, init, middleware ); return ( <div> <button onClick={() => dispatch(thunkToggle())}> toggle </button> <pre>{JSON.stringify(state, undefined, 2)}</pre> </div> ); }; ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>
使用 thunk,您可以将逻辑移出组件并执行dispatch(fetchData(args))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.