web-dev-qa-db-ja.com

データをフェッチしている間にReact Reduxアプリでローディングインジケーターを表示するにはどうすればいいですか?

React/Reduxは初めてです。私はAPIを処理するためにReduxアプリでフェッチAPIミドルウェアを使用します。それは https://github.com/agraboso/redux-api-middleware です。非同期APIアクションを処理するのは良い方法だと思います。しかし、私は自分では解決できないいくつかのケースを見つけます。

ホームページ https://github.com/agraboso/redux-api-middleware#lifecycle によると、フェッチAPIのライフサイクルはCALL_APIアクションのディスパッチで始まり、FSAアクションのディスパッチで終わります。

だから私の最初のケースは、APIを取得するときにプリローダーを表示/非表示にすることです。ミドルウェアは最初にFSAアクションをディスパッチし、最後にFSAアクションをディスパッチします。どちらのアクションも、通常のデータ処理を実行するだけのはずのリデューサーによって受け取られます。 UI操作がなく、操作も不要です。たぶん私は処理の状態をstateに保存し、ストアの更新時にそれらをレンダリングするべきです。

しかし、これをどのように行うのですか?ページ全体に反応するコンポーネントが流れますか。他のアクションからストアを更新するとどうなりますか?私は彼らが州よりもイベントに近いということを意味します!

最悪の場合でも、redux/reactアプリでネイティブの確認ダイアログまたは警告ダイアログを使用する必要がある場合はどうすればよいですか。それらはどこに置くべきですか、行動または減力剤?

ご多幸を祈る!返信してほしい。

私は彼らが州よりもイベントに近いということを意味します!

私はそうは思わないでしょう。ローディングインジケータは、状態の関数、つまりこの場合はブール変数の関数として簡単に説明できるUIの優れたケースです。 この答え は正しいのですが、それに合わせてコードをいくつか提供したいと思います。

Redux repoのasyncの例 、reducer isFetchingというフィールドを更新します

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: true,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: false,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

コンポーネントはReact Reduxのconnect()を使用してストアの状態をサブスクライブし、 mapStateToProps()の戻り値の一部としてisFetchingを返す を使用するので、接続されたコンポーネントの小道具で使用できます。

function mapStateToProps(state) {
  const { selectedReddit, postsByReddit } = state
  const {
    isFetching,
    lastUpdated,
    items: posts
  } = postsByReddit[selectedReddit] || {
    isFetching: true,
    items: []
  }

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

最後に、コンポーネント render()関数でisFetching propを使用 “ Loading ...”ラベルをレンダリングする(代わりにスピナーにすることもできます):

{isEmpty
  ? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
  : <div style={{ opacity: isFetching ? 0.5 : 1 }}>
      <Posts posts={posts} />
    </div>
}

最悪の場合でも、redux/reactアプリでネイティブの確認ダイアログまたは警告ダイアログを使用する必要がある場合はどうすればよいですか。それらはどこに置くべきですか、行動または減力剤?

どんな副作用(そしてダイアログを表示することはほとんど確実に副作用です)は減力剤に属しません。減力剤を受動的な「国家の建設者」と考えてください。彼らは本当に物事を「する」のではありません。

警告を表示したい場合は、アクションをディスパッチする前にコンポーネントから実行するか、アクション作成者から実行してください。アクションがディスパッチされるまでに、それに応答して副作用を実行するには遅すぎます。

すべての規則について、例外があります。時にはあなたの副作用ロジックが非常に複雑なので、実際にはそれらを特定のアクションタイプか特定のリデューサーに結合したいことを望みます。この場合、 Redux Saga および Redux Loop のような高度なプロジェクトをチェックしてください。あなたがVanilla Reduxに慣れていて、あなたがより扱いやすいものにしたいと思う散在する副作用の本当の問題を抱えているときだけこれをしてください。

146
Dan Abramov

素晴らしい回答Dan Abramov!私のアプリケーションの1つで多少正確に同じことをしていて(isFetchingをブール値として保持している)、複数の同時をサポートするために整数(未解決の要求数として読み取ることになる)リクエスト。

ブール値の場合:

リクエスト1が開始 - >スピナーがオン - >リクエスト2が開始 - >リクエスト1が終了 - >スピナーがオフ - >リクエスト2が終了

整数の場合

リクエスト1が開始 - >スピナーがオン - >リクエスト2が開始 - >リクエスト1が終了 - >リクエスト2が終了 - >スピナーがオフ

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching + 1,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching - 1,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt
19
Nuno Campos

何か追加したいのですが。実世界の例では、ストア内のフィールドisFetchingを使用して、項目のcollectionがフェッチされている時期を表しています。どのコレクションもpaginationリデューサーに一般化され、コンポーネントに接続して状態を追跡し、コレクションがロードされているかどうかを確認できます。

改ページパターンに収まらない特定のエンティティの詳細を取得したいと思いました。詳細がサーバーから取得されているかどうかを表す状態を持つことを望みましたが、それだけのためにリデューサーを取得することもしたくありませんでした。

これを解決するために、私はfetchingと呼ばれるもう一つの一般的な減数を追加しました。これはページネーションリデューサーと同じように機能し、その役割は監視一連のアクションを実行し、ペアで新しい状態を生成することだけです[entity, isFetching]。これにより、任意のコンポーネントへのリデューサーをconnectすること、およびアプリが現在コレクションだけでなく特定のエンティティについてデータをフェッチしているかどうかを知ることができます。

13
javivelasco

私は今までこの質問には起こりませんでした、しかし答えが受け入れられないので私は私の帽子を投げます。私はこのまさにその仕事のためのツールを書きました: react-loader-factory 。それはAbramovの解決策よりもやや進行していますが、書いた後で考える必要がないので、よりモジュール化されていて便利です。

4つの大きな部分があります。

  • ファクトリパターン:これはあなたがあなたのコンポーネントのための "ロード"を意味するどの状態、そしてどのアクションをディスパッチするかを設定するために同じ関数を素早く呼び出すことを可能にします。 (これは、コンポーネントが待機しているアクションの開始を担当していることを前提としています。)const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • ラッパー:Factoryが生成するコンポーネントは(connect()がReduxで返すもののような)「高次コンポーネント」であるため、既存のものに追加することができます。 const LoadingChild = loaderWrapper(ChildComponent);
  • アクション/リデューサーの相互作用:ラッパーは、プラグインされているリデューサーに、データが必要なコンポーネントにパススルーしないように指示するキーワードが含まれているかどうかを確認します。ラッパーによってディスパッチされたアクションは、関連キーワードを生成することが期待されています(たとえば、redux-api-middlewareがACTION_SUCCESSおよびACTION_REQUESTをディスパッチする方法)。 (もちろん、他の場所にアクションをディスパッチし、必要に応じてラッパーから監視することもできます。)
  • ズキズキ:コンポーネントが依存しているデータが表示されている間に表示したいコンポーネントは準備できていません。私はそこに少しdivを追加したので、あなたはそれを準備する必要なしにそれをテストすることができます。

モジュール自体はredux-api-middlewareから独立していますが、それが私が使用しているものなので、READMEのサンプルコードを次に示します。

それをラップするローダーを持つコンポーネント:

import React from 'react';
import { myAsyncAction } from '../actions';
import loaderFactory from 'react-loader-factory';
import ChildComponent from './ChildComponent';

const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates);

const LoadingChild = loaderWrapper(ChildComponent);

const containingComponent = props => {
  // Do whatever you need to do with your usual containing component 

  const childProps = { someProps: 'props' };

  return <LoadingChild { ...childProps } />;
}

ローダーが監視するためのリデューサー(あなたがすることができますが 違う方法で配線する =)

export function activeRequests(state = [], action) {
  const newState = state.slice();

  // regex that tests for an API action string ending with _REQUEST 
  const reqReg = new RegExp(/^[A-Z]+\_REQUEST$/g);
  // regex that tests for a API action string ending with _SUCCESS 
  const sucReg = new RegExp(/^[A-Z]+\_SUCCESS$/g);

  // if a _REQUEST comes in, add it to the activeRequests list 
  if (reqReg.test(action.type)) {
    newState.Push(action.type);
  }

  // if a _SUCCESS comes in, delete its corresponding _REQUEST 
  if (sucReg.test(action.type)) {
    const reqType = action.type.split('_')[0].concat('_REQUEST');
    const deleteInd = state.indexOf(reqType);

    if (deleteInd !== -1) {
      newState.splice(deleteInd, 1);
    }
  }

  return newState;
}

近い将来、タイムアウトやエラーなどをモジュールに追加する予定ですが、パターンはそれほど変わらないでしょう。


あなたの質問に対する簡単な答えは:

  1. レンダリングをレンダリングコードに結び付ける - 上で示したようなデータでレンダリングする必要があるコンポーネントの周りにラッパーを使用します。
  2. 気になるかもしれないアプリの周りのリクエストのステータスを消化しやすいものにするためのリデューサーを追加してください。
  3. イベントと状態は、それほど違いはありません。
  4. あなたの直感の残りの部分は私には正しいようです。
5
bright-star

ロード指標がReduxストアに属していないと私が思うのは私だけですか?つまり、アプリケーション自体の状態の一部ではないと思います。

今、私はAngular 2を使用しています。RxJSBehaviourSubjectsを介してさまざまなローディングインジケータを公開する「ローディング」サービスを使用しています。メカニズムは同じだと思います。Reduxには情報を格納しません。

LoadingServiceのユーザーは、聴きたいイベントを購読するだけです。

私のReduxアクション作成者は、状況を変える必要があるときはいつでもLoadingServiceを呼び出します。 UXコンポーネントは公開されている観測量を購読しています...

3
Spock

アプリには3種類の通知があります。これらはすべてアスペクトとして設計されています。

  1. 積載区分(支柱に基づくモーダルまたは非モーダル)
  2. エラーポップアップ(モーダル)
  3. 通知スナックバー(非モーダル、自動終了)

これらの3つすべてが我々のアプリのトップレベル(Main)にあり、以下のコードスニペットに示すようにReduxを通して配線されています。これらの小道具は対応するアスペクトの表示を制御します。

私はすべてのAPI呼び出しを処理するプロキシを設計しました。したがって、すべてのisFetchingおよび(api)エラーは、プロキシにインポートしたactionCreatorsによって仲介されます。 (余談ですが、私はwebpackを使ってdev用のバッキングサービスのモックを注入しているので、サーバーに依存せずに作業することができます。)

あらゆる種類の通知を提供する必要があるアプリ内の他の場所は、単に適切なアクションをインポートするだけです。 Snackbar&Errorはメッセージを表示するためのパラメータを持っています。

@connect(
// map state to props
state => ({
    isFetching      :state.main.get('isFetching'),   // ProgressIndicator
    notification    :state.main.get('notification'), // Snackbar
    error           :state.main.get('error')         // ErrorPopup
}),
// mapDispatchToProps
(dispatch) => { return {
    actions: bindActionCreators(actionCreators, dispatch)
}}

)デフォルトクラスをエクスポートするMainはReact.Componentを拡張する

3
Dreculah

React Reduxのconnect()または低レベルのstore.subscribe()メソッドを使用して、ストアに変更リスナーを追加できます。ストアにローディングインジケータがあるはずです。ストア変更ハンドラはそれを確認してコンポーネントの状態を更新できます。その後、コンポーネントは状態に基づいて、必要に応じてプリローダーをレンダリングします。

alertconfirmは問題になりません。それらはブロックされていて、alertはユーザーからの入力も取りません。 confirmを使用すると、ユーザーの選択がコンポーネントのレンダリングに影響する場合は、ユーザーがクリックした内容に基づいて状態を設定できます。そうでない場合は、後で使用するために選択内容をコンポーネントメンバー変数として格納できます。

3
Miloš Rašić

::のようなURLを保存しています

isFetching: {
    /api/posts/1: true,
    api/posts/3: false,
    api/search?q=322: true,
}

それから私は(再選択を介して)記憶されたセレクタを持っています。

const getIsFetching = createSelector(
    state => state.isFetching,
    items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false
);

POSTの場合にURLを一意にするために、クエリとして変数を渡します。

そして私がインディケータを見せたいところで、私は単にgetFetchCount変数を使います

1
Sergiu