データのフェッチ中にReact Reduxアプリで読み込みインジケーターを表示するにはどうすればよいですか?[閉まっている]


107

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

ホームページ(Lifecycle)が言うように、フェッチAPIライフサイクルは、CALL_APIアクションのディスパッチで始まり、FSAアクションのディスパッチで終わります。

したがって、私の最初のケースは、APIをフェッチするときにプリローダーを表示/非表示にすることです。ミドルウェアは、最初にFSAアクションをディスパッチし、最後にFSAアクションをディスパッチします。両方のアクションは、一部の通常のデータ処理のみを実行するレデューサーによって受信されます。UI操作や操作はありません。多分私は状態で処理ステータスを保存し、ストアの更新時にそれらをレンダリングする必要があります。

しかし、これを行う方法は?ページ全体に反応するコンポーネントのフロー?他のアクションからストアを更新するとどうなりますか?つまり、州というよりイベントのようなものです。

さらに悪いケースとして、redux / reactアプリでネイティブの確認ダイアログまたはアラートダイアログを使用する必要がある場合はどうすればよいですか?それらはどこに置くべきですか、行動または減力剤?

ご多幸を祈る!返信をお願いします。


1
この質問に対する最後の編集をロールバックしました。これにより、質問と回答の全体のポイントが変更されました。
Gregg B

イベントは状態の変化です!
企业应用架构模式大师

Questrarを見てください。github.com/orar/questrar
ORAR

回答:


152

つまり、州というよりイベントのようなものです。

そうは思いません。読み込みインジケーターは、状態の関数として簡単に説明できるUIの素晴らしいケースだと思います。この場合は、ブール変数です。一方で、この答えが正しいですが、私はそれと一緒に行くためにいくつかのコードを提供したいと思います。

asyncRedux repo例では、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

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

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

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

最後に、コンポーネントは関数でisFetchingpropを使用render()して、「Loading ...」ラベルをレンダリングします(代わりにスピナーになる可能性があります)。

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

さらに悪いケースとして、redux / reactアプリでネイティブの確認ダイアログまたはアラートダイアログを使用する必要がある場合はどうすればよいですか?それらはどこに置くべきですか、行動または減力剤?

副作用(およびダイアログの表示は間違いなく副作用)はレデューサーに属しません。レデューサーは、受動的な「国家の建設者」と考えてください。彼らは実際に「行う」わけではありません。

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

すべてのルールについて、例外があります。場合によっては、副作用ロジックが非常に複雑なので、実際にそれらを特定のアクションタイプまたは特定のレデューサーに結合したいことがあります。この場合、Redux SagaRedux Loopなどの高度なプロジェクトをチェックしてください。これを行うのは、バニラReduxに慣れていて、より扱いやすくしたい副作用が散在しているという本当の問題がある場合のみにしてください。


16
複数のフェッチを行っている場合はどうなりますか?しかし、1つの変数では十分ではありません。
philk 2016

1
@philk複数のフェッチがある場合は、それらをPromise.all1つのpromiseにグループ化し、すべてのフェッチに対して1つのアクションをディスパッチできます。またはisFetching、状態で複数の変数を維持する必要があります。
Sebastien Lorber 2016

2
リンク先の例をよく見てください。複数のisFetchingフラグがあります。フェッチされているオブジェクトのすべてのセットに設定されます。あなたはそれを実装するためにリデューサー構成を使うことができます。
Dan Abramov 2016

3
リクエストが失敗し、RECEIVE_POSTSトリガーされない場合、error loadingメッセージを表示するためのタイムアウトを作成しない限り、読み込みのサインはそのまま残ります。
James111

2
@TomiS-私が使用しているredux永続性から、すべてのisFetchingプロパティを明示的にブラックリストに登録します。
duhseekoh

22

素晴らしい答えダンアブラモフ!私のアプリの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

2
これは合理的です。ただし、ほとんどの場合、フラグに加えて、フェッチしたデータも格納する必要があります。この時点で、isFetchingフラグを持つ複数のオブジェクトが必要になります。リンクした例をよく見ると、isFetched1つのsubredditごとに1 つだけのオブジェクトがあります。
Dan Abramov、2016

2
ああ。ええ、気づきませんでした。ただし、私の場合、状態に1つのグローバルisFetchingエントリと、フェッチされたデータが格納されるキャッシュエントリがあります。私の目的では、一部のネットワークアクティビティが発生していることだけを気にしているので、何も問題ではありません
Nuno Campos

4
うん!UIの1つまたは多くの場所にフェッチインジケータを表示するかどうかによって異なります。実際、2つのアプローチを組み合わせて、画面上部の進行状況バーのグローバルと、リストとページのいくつかの特定のフラグの両方を持つことができます。fetchCounterisFetching
Dan Abramov、2016

複数のファイルにPOSTリクエストがある場合、isFetchingの状態を設定して現在の状態を追跡するにはどうすればよいですか?
user989988 2018年

13

追加したいのですが。実際の例ではisFetching、ストアのフィールドを使用して、アイテムのコレクションがいつフェッチされているかを表します。コレクションはpagination、コンポーネントに接続して状態を追跡し、コレクションがロードされているかどうかを示すことができるレデューサーに一般化されます。

偶然、ページ分割パターンに適合しない特定のエンティティの詳細をフェッチしたいと思いました。詳細がサーバーからフェッチされているかどうかを表す状態にしたかっただけでなく、そのためだけのレデューサーを使用したくありませんでした。

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


2
答えてくれてありがとう!個々のアイテムの読み込みとそのステータスの処理についてはめったに議論されません!
Gilad Peleg

別のアクションに依存するコンポーネントが1つある場合、mapStateToPropsでこれらのコンポーネントを次のようにすばやく簡単に組み合わせます。isFetching:posts.isFetching || comments.isFetching-どちらか一方が更新されているときに、両方のコンポーネントのユーザー操作をブロックできるようになりました。
Philip Murphy、

5

私は今までこの質問に出くわしませんでしたが、答えが受け入れられなかったので、私は帽子を投げます。私は、まさにこの仕事のためのツールを書きました:react-loader-factory。それはアブラモフのソリューションより少し進んでいますが、私はそれを書いた後で考えたくなかったので、よりモジュール化されており、便利です。

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

  • ファクトリー・パターン:これにより、同じ関数を素早く呼び出して、コンポーネントの「ロード」を意味する状態と、ディスパッチするアクションをセットアップできます。(これは、コンポーネントが待機するアクションを開始する責任があることを前提としています。)const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • ラッパー:ファクトリーが生成するコンポーネントは「高次コンポーネント」(connect()Reduxで返されるものなど)なので、既存のものにボルトで固定することができます。const LoadingChild = loaderWrapper(ChildComponent);
  • アクション/リデューサーの相互作用:ラッパーは、プラグインされているリデューサーに、データを必要とするコンポーネントにパススルーしないように指示するキーワードが含まれているかどうかを確認します。アクションは、関連するキーワード(方法Reduxの-API-ミドルウェアディスパッチ生成するために期待されているラッパーによって送出ACTION_SUCCESSACTION_REQUEST、たとえば)。(もちろん、アクションを他の場所にディスパッチし、必要に応じてラッパーから監視することもできます。)
  • Throbber:コンポーネントが依存するデータの準備ができていないときに表示するコンポーネント。そこに小さな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. あなたの直感の残りは私には正しいようです。

4

ロードインジケーターはReduxストアに属していないと私が考えているのは私だけですか?つまり、それ自体はアプリケーションの状態の一部ではないと思います。

今、私はAngular2を使用しています。私がしていることは、RxJS BehaviourSubjectsを介してさまざまな読み込みインジケーターを公開する「読み込み」サービスがあることです。メカニズムは同じだと思いますが、情報をReduxに保存しません。

LoadingServiceのユーザーは、聞きたいイベントをサブスクライブするだけです。

私のReduxアクション作成者は、物事を変更する必要があるときはいつでもLoadingServiceを呼び出します。UXコンポーネントは公開されたオブザーバブルをサブスクライブします...


これが、すべてのアクションをポーリングできるストアの考え方(ngrxおよびredux-logic)、サービスが機能していない、redux-logicが機能している理由です。良い読書
srghma

20
こんにちは。1年以上経ってからチェックしましたが、私は非常に間違っていました。もちろん、UX状態はアプリケーション状態に属します。私はどれほど愚かなことができますか?
Spock

3

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

alertそして、confirm問題になることはありません。それらはブロックしており、アラートはユーザーからの入力さえも受けません。を使用confirmすると、ユーザーの選択がコンポーネントのレンダリングに影響を与える場合に、ユーザーが何をクリックしたかに基づいて状態を設定できます。そうでない場合は、後で使用するためにコンポーネントメンバー変数として選択を保存できます。


アラート/確認コードについて、どこに配置する必要があるか、アクションまたはレデューサーか?
企业应用架构模式大师

それらをどのように処理するかによって異なりますが、データレイヤーではなくUIの一部であるため、ほとんどの場合、コンポーネントコードに含めます。
ミロシュRašić

一部のUIコンポーネントは、ステータス自体ではなく、イベント(ステータスを変更するイベント)をトリガーして動作します。アニメーション、プリローダーの表示/非表示など。それらをどのように処理しますか?
企业应用架构模式大师

反応アプリで非反応コンポーネントを使用する場合、一般的に使用されるソリューションは、ラッパーを反応コンポーネントにしてから、そのライフサイクルメソッドを使用して非反応コンポーネントのインスタンスを初期化、更新、および破棄することです。このようなコンポーネントのほとんどは、DOMのプレースホルダー要素を使用して初期化し、reactコンポーネントのrenderメソッドでそれらをレンダリングします。あなたはここにライフサイクルメソッドの詳細読むことができます:facebook.github.io/react/docs/component-specs.html
ミロシュRašić

ケースがあります:右上隅の通知領域には1つの通知メッセージが含まれ、各メッセージが表示されてから5秒後に消えます。このコンポーネントは、ホストのネイティブアプリによって提供されるWebビューの外にあります。のようなjsインターフェースを提供しますaddNofication(message)。別のケースは、ホストネイティブアプリによって提供され、そのJavaScript APIによってトリガーされるプリローダーです。これらのAPIのラッパーをcomponentDidUpdateReactコンポーネントに追加します。このコンポーネントの小道具または状態をどのように設計しますか?
企业应用架构模式大师

3

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

  1. 読み込みインジケーター(プロップに基づくモーダルまたは非モーダル)
  2. エラーポップアップ(モーダル)
  3. 通知スナックバー(非モーダル、自動終了)

これら3つはすべてアプリの最上位(メイン)にあり、以下のコードスニペットに示すようにReduxを介して配線されています。これらの小道具は、対応する側面の表示を制御します。

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

通知の種類を提供する必要があるアプリ内の他の場所は、適切なアクションをインポートするだけです。スナックバーとエラーには、表示するメッセージのパラメーターがあります。

@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 {


私はローダー/通知を表示する同様の設定に取り組んでいます。問題が発生しています。これらのタスクを実行する方法の要点または例はありますか?
アイメン、

2

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

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

そして、(reselectを介して)記憶されたセレクターがあります。

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

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

インジケーターを表示したい場合は、単にgetFetchCount変数を使用します


1
あなたは置き換えることができますObject.keys(items).filter(item => items[item] === true).length > 0 ? true : falseによるObject.keys(items).every(item => items[item])方法で。
Alexandre Annic

1
some代わりにあなたが意味したと思いますeveryが、そうです、最初に提案されたソリューションでは比較が必要ではありません。 Object.entries(items).some(([url, fetching]) => fetching);
ラファエルポラスルセナ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.