web-dev-qa-db-ja.com

React useReducer非同期データフェッチ

私は新しいreact useReducer APIでいくつかのデータを取得しようとしていますが、非同期で取得する必要がある段階で立ち往生しています。方法がわからない:/

Switch文にデータフェッチを配置する方法、またはそれを行う方法ではありませんか?

import React from 'react'

const ProfileContext = React.createContext()

const initialState = {
  data: false
}

let reducer = async (state, action) => {
  switch (action.type) {
    case 'unload':
      return initialState
    case 'reload':
      return { data: reloadProfile() } //how to do it???
  }
}


const reloadProfile = async () => {
  try {
    let profileData = await fetch('/profile')
    profileData = await profileData.json()

    return profileData
  } catch (error) {
    console.log(error)
  }
}

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState)

  return (
    <ProfileContext.Provider value={{ profile, profileR }}>
      {props.children}
    </ProfileContext.Provider>
  )
}

export { ProfileContext, ProfileContextProvider }

私はこのようにしようとしていましたが、非同期で動作していません;(

let reducer = async (state, action) => {
  switch (action.type) {
    case 'unload':
      return initialState
    case 'reload': {
      return await { data: 2 }
    }
  }
}
26
RTW

これは、useReducerの例が触れていない興味深いケースです。減速機は非同期にロードするのに適した場所ではないと思います。 Reduxの考え方からは、通常はサンク、オブザーバブル(例:redux-observable)、またはcomponentDidMountなどのライフサイクルイベントのいずれかでデータをロードします。新しいuseReducerを使用すると、componentDidMountを使用してuseEffectアプローチを使用できます。効果は次のようになります。

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState);

  useEffect(() => {
    reloadProfile().then((profileData) => {
      profileR({
        type: "profileReady",
        payload: profileData
      });
    });
  }, []); // The empty array causes this effect to only run on mount

  return (
    <ProfileContext.Provider value={{ profile, profileR }}>
      {props.children}
    </ProfileContext.Provider>
  );
}

また、ここでの作業例: https://codesandbox.io/s/r4ml2x864m

reloadProfile関数にプロップまたはステートを渡す必要がある場合は、2番目の引数をuseEffect(この例では空の配列)に調整して、必要な場合にのみ実行されるようにすることができます。前の値をチェックするか、何らかのキャッシュを実装して、不要なときにフェッチしないようにする必要があります。

更新-子から再読み込み

子コンポーネントからリロードできるようにする場合、いくつかの方法があります。最初のオプションは、ディスパッチをトリガーするコールバックを子コンポーネントに渡すことです。これは、コンテキストプロバイダーまたはコンポーネントプロパティを通じて実行できます。既にコンテキストプロバイダーを使用しているため、そのメソッドの例を次に示します。

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState);

  const onReloadNeeded = useCallback(async () => {
    const profileData = await reloadProfile();
    profileR({
      type: "profileReady",
      payload: profileData
    });
  }, []); // The empty array causes this callback to only be created once per component instance

  useEffect(() => {
    onReloadNeeded();
  }, []); // The empty array causes this effect to only run on mount

  return (
    <ProfileContext.Provider value={{ onReloadNeeded, profile }}>
      {props.children}
    </ProfileContext.Provider>
  );
}

really明示的なコールバックの代わりにディスパッチ関数を使用したい場合は、特別な関数を処理する高次関数でディスパッチをラップすることで行うことができますReduxの世界ではミドルウェアによって処理されていたアクション。以下にその例を示します。 profileRをコンテキストプロバイダーに直接渡すのではなく、ミドルウェアのように動作するカスタムプロバイダーを渡し、レデューサーが気にしない特別なアクションをインターセプトすることに注意してください。

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState);

  const customDispatch= useCallback(async (action) => {
    switch (action.type) {
      case "reload": {
        const profileData = await reloadProfile();
        profileR({
          type: "profileReady",
          payload: profileData
        });
        break;
      }
      default:
        // Not a special case, dispatch the action
        profileR(action);
    }
  }, []); // The empty array causes this callback to only be created once per component instance

  return (
    <ProfileContext.Provider value={{ profile, profileR: customDispatch }}>
      {props.children}
    </ProfileContext.Provider>
  );
}
18
Tyler

私は問題と可能な解決策について非常に詳細な説明を書きました。ダンアブラモフはソリューション3を提案しました。

注:Gistの例では、ファイル操作の例を示していますが、データフェッチについても同じアプローチを実装できます。

https://Gist.github.com/astoilkov/013c513e33fe95fa8846348038d8fe42

6
astoilkov

非同期アクションの問題を解決するために、ディスパッチメソッドをレイヤーでラップしました。

初期状態です。 loadingキーは、アプリケーションの現在の読み込み状態を記録します。アプリケーションがサーバーからデータを取得しているときに読み込みページを表示したい場合に便利です。

{
  value: 0,
  loading: false
}

4種類のアクションがあります。

function reducer(state, action) {
  switch (action.type) {
    case "click_async":
    case "click_sync":
      return { ...state, value: action.payload };
    case "loading_start":
      return { ...state, loading: true };
    case "loading_end":
      return { ...state, loading: false };
    default:
      throw new Error();
  }
}
function isPromise(obj) {
  return (
    !!obj &&
    (typeof obj === "object" || typeof obj === "function") &&
    typeof obj.then === "function"
  );
}

function wrapperDispatch(dispatch) {
  return function(action) {
    if (isPromise(action.payload)) {
      dispatch({ type: "loading_start" });
      action.payload.then(v => {
        dispatch({ type: action.type, payload: v });
        dispatch({ type: "loading_end" });
      });
    } else {
      dispatch(action);
    }
  };
}

非同期メソッドがあるとします

async function asyncFetch(p) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(p);
    }, 1000);
  });
}

wrapperDispatch(dispatch)({
  type: "click_async",
  payload: asyncFetch(new Date().getTime())
});

完全なサンプルコードは次のとおりです。

https://codesandbox.io/s/13qnv8ml7q

2
kaijun

非同期useReducerを使用したtodolistの例非同期関数をリデューサーに入れないでください。

https://codesandbox.io/s/zr3mx12zzx

0
GroteSmurf