我有一个用打字稿编写的react hooks应用程序,其中包含多个reducer,并且将它们与上下文API一起使用。
我需要在化简器之间共享错误状态,因为我只需要在应用程序中显示一个错误状态,即可使用errorReducer
清除/设置。
麻烦的是,我需要从“其他”减速器设置状态的错误部分(通过“其他”,我的意思不是errorReducer
)。
如果我尝试在“其他”减速器中使用useReducer钩子(或我自己的useAsyncReducer
钩子)来设置错误,我会得到
错误:无效的挂钩调用。挂钩只能在身体内部使用 功能组件的说明
,如下所示。
如何在减速器之间共享状态以进行反应?(请参阅下面的“待办事项:这是我需要的”)。
请注意,我不想使用redux。
export type Actor = {
_id?: string,
firstName?: string,
lastName?: string,
selectedForDelete?: boolean
}
// Compound state for 3 reducers (activeEntityTypeReducer, actorReducer, errorReducer)
export type State = {
activeEntityType: string,
actors: Actor[],
error: string | null
}
export const EMPTY_INITIAL_STATE: State = {
activeEntityType: EntityType.ACTOR,
actors: [],
error: null
};
// ERROR REDUCER:
export const errorReducer = async (state: string | null, action: ErrorActions) => {
switch (action.type) {
case ErrorActionType.SET_ERROR: {
return action.payload;
}
default:
return state;
}
};
// ACTOR REDUCER:
export const actorReducer = async (state: Actor[], action: ActorActions) => {
// I cannot use here a hook like this because it triggers: "Error: Invalid hook call. Hooks can only be called inside of the body of a function component"
// const { dispatch: dispatchError } = useAsyncReducer(errorReducer, EMPTY_INITIAL_STATE);
switch (action.type) {
//... other actions
case ActorActionType.SEARCH_ACTORS: {
return fetch(
"http://localhost:3000/api/actors?pagesize=100"
).then(response => response.json())
.then(response => response['data']);
/* // TODO: THIS IS WHAT I NEED: continue the above line with a catch inside which I dispatch the error
.catch((error) => dispatchError({
type: ErrorActionType.SET_ERROR,
payload: error
}))
*/
}
default:
return state;
}
};
// MAIN (COMPOSED) REDUCER:
export const mainReducer = async ({ activeEntityType, actors, error }: State,
action: ActiveEntityTypeActions | ActorActions | ErrorActions) => (
{
actors: await actorReducer(actors, action as ActorActions),
activeEntityType: activeEntityTypeReducer(activeEntityType, action as ActiveEntityTypeActions),
// more reducers here and all need to set the error
error: await errorReducer(error, action as ErrorActions)
});
答案 0 :(得分:0)
使用上下文将错误状态存储在父组件中,然后使用useContext
访问存储在上下文中的状态。由于您需要组件来更新上下文中的状态,因此包含了一种更新状态的方法-我不使用Typescript
const ErrorContext = React.createContext({
error: null,
setError: err => {
this.error = err;
}
});
let ComponentOne = () => {
const [link, setLink] = useState("");
const errorState = useContext(ErrorContext);
// http fetching is an impure action, it does not belong in reducers
// in redux we place asnc actions in middlewares such as thunk and saga
let fetchLink = () => {
fetch(link)
.then(res => res.json())
.then(response => {
// if data, pass action to reducer to update state
console.log("++ found something: ", response);
})
.catch(exc => {
console.error("++ exc: ", exc);
// here we use context to set error state
errorState.setError("From Component1: " + exc.message);
});
};
return (
<>
<input type="text" name="link" onChange={e => setLink(e.target.value)} />
<button onClick={fetchLink}>fetch content from ComponentTwo</button>
</>
);
};
let ComponentTwo = () => {
const [link, setLink] = useState("");
const errorState = useContext(ErrorContext);
// http fetching is an impure action, it does not belong in reducers
// in redux we place asnc actions in middlewares such as thunk and saga
let fetchLink = () => {
fetch(link)
.then(res => res.json())
.then(response => {
// if data, pass action to reducer to update state
console.log("++ found something: ", response);
})
.catch(exc => {
console.error("++ exc: ", exc);
// here we use context to set error state
errorState.setError("From Component2: " + exc.message);
});
};
return (
<>
<input type="text" name="link" onChange={e => setLink(e.target.value)} />
<button onClick={fetchLink}>fetch content from ComponentTwo</button>
</>
);
};
export default function App() {
const [error, setError] = useState("");
return (
<ErrorContext.Provider
value={{
error,
setError
}}
>
<div className="App">
<h1>Error: {error}</h1>
<hr />
<ComponentOne />
<br />
<ComponentTwo />
</div>
</ErrorContext.Provider>
);
}
此外,API调用不属于reducer,最好在组件内进行所有异步调用,并在有结果或遇到异常时发出操作。
注意:我有一个codesandbox
答案 1 :(得分:0)
我找到了解决方法:
我也更改了actorReducer
来更新状态的error
部分。这意味着actorReducer
现在同时接收状态的actors
和error
部分,并同时返回它们。
为简单起见,我将操作名称重命名为以标识化简器的单词开头,以便通过检查即可轻松确定mainReducer
(组合化简器)中将哪个动作转发给哪个归化器。传入操作的前缀(因此ActorActionType.SEARCH_ACTORS
重命名为ActorActionType.ACTOR__SEARCH
,依此类推)。
这是代码的一部分:
type State = {
activeEntityType: string,
actors: Actor[],
error: string | null
}
// For actorReducer
enum ActorActionType {
ACTOR__ADD = 'ACTOR__ADD',
ACTOR__SELECT_FOR_DELETING = 'ACTOR__SELECT_FOR_DELETING',
ACTOR__DELETE = 'ACTOR__DELETE',
ACTOR__SEARCH = 'ACTOR__SEARCH'
}
// For errorReducer
enum ErrorActionType {
ERROR__SET = 'ERROR__SET',
}
// for activeEntityTypeReducer
const ACTIVE_ENTITY_TYPE__SET: string = "ACTIVE_ENTITY_TYPE__SET";
const actorReducer = async (actors: Actor[], error: string | null, action: ActorActions) => {
case ActorActionType.ACTOR__SEARCH: {
// fetch and error handling and fill newActors and newOrOldError
return { actors: newActors, error: newOrOldError};
}
}
// The combined (main) reducer:
const mainReducer = async ({ activeEntityType, actors, error }: State,
action: ActiveEntityTypeActions | ActorActions | ErrorActions) => {
if (action.type.startsWith('ACTOR__')) {
const actorsAndError = (await actorReducer(actors, error, action as ActorActions));
return {...actorsAndError, activeEntityType};
}
if (action.type.startsWith('ACTIVE_ENTITY_TYPE__')) {
// No need for now to pass in and return the error for activeEntityTypeReducer, as it can never error
return {activeEntityType: activeEntityTypeReducer(activeEntityType, action as ActiveEntityTypeActions), actors, error};
}
// Can only be the errorReducer case:
return {activeEntityType, actors, error: await errorReducer(error, action as ErrorActions)};
};