2

我有一个用 typescript 编写的 react hooks 应用程序,其中包含多个 reducer,我将它们与 context API 一起使用。

我需要在 reducer 之间共享错误状态,因为我只需要在应用程序中显示一个错误状态,可以使用errorReducer.

问题是,我需要从“其他”减速器设置状态的错误部分(“其他”我的意思不是errorReducer)。

如果我尝试useAsyncReducer在“其他”减速器中使用 useReducer 钩子(或我自己的钩子)来设置错误,我会得到

错误:无效的挂钩调用。Hooks 只能在函数组件的主体内部调用

,如下所示。

如何在react中的reducer之间共享状态?(请参阅下面的“待办事项:这是我需要的”)。

请注意,我不想使用 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)
    });
4

2 回答 2

0

使用 context 将错误状态存储在 Parent 组件中,然后用于useContext访问存储在 context 中的状态。因为您需要组件在上下文中更新状态,所以您包含一个更新状态的方法 - 我不使用 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,最好在组件内部执行所有异步调用,并在您有结果或遇到异常时发出操作。

注意:我有一个代码框

于 2020-05-27T10:51:52.893 回答
0

我找到了一个解决方案:

我也更改了actorReducer更新error状态的一部分。这意味着actorReducer现在接收状态的 theactorserror部分并返回它们。

为简单起见,我将动作名称重命名为以标识减速器的单词开头,这样我就可以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)};
};
于 2020-06-01T05:55:05.137 回答