6

状況

ユーザーが段階的なオンボーディングを行うオンボーディング シナリオがあります。Redux を使用して、ユーザーの進行状況のクライアント側の状態を管理したいと考えています。サーバーとクライアント間の同期はすでに Relay に実装されていますが、クライアント側の状態管理には Redux ストアが必要です。そのため、Relay-/Redux-Store の同期で問題が発生します。

私が今行っていることは、React コンポーネントを Redux でラップしてから Relay でラップすることです。

// OnboardProgressView.js
// ...      

// wrap React component with Redux 
const mapStateToProps = (state) => {
  return {
    onboardProgress: state.onboardProgress,
  }
}

const ReduxContainer = connect(
  mapStateToProps,
)(OnboardProgressView)

// this is only for convenience of access of the Relay data
const MappedOnboardProgressView = mapProps({
  params: (props) => props.params,
  user: (props) => props.viewer.user,
})(ReduxContainer)

// wrap Redux component with Relay
export default Relay.createContainer(MappedGettingStartedView, {
  fragments: {
    viewer: () => Relay.QL`
      fragment on Viewer {
        user {
          userId
          onboardProgressStep
        }
        # more stuff ...
      }
    `,
  },
})  

私の進歩

次のように、さまざまな操作を実行する方法を見つけました。

サーバーデータを使用した Redux ストアの初期化

非同期の生リレー クエリを使用してストアを作成した直後に、Redux の状態を初期化しています。それを可能にするために、redux-thunkミドルウェアも使用しています。Redux は、サーバーに問い合わせる Relay へのリクエストを開始します。視覚的表現 (矢印はデータ フローを示し、要素の順序は「呼び出し順序」を反映しています):Redux <= Relay <= Server

// app.js
const store = createStore(reducer, applyMiddleware(thunk))
store.dispatch(fetchOnboardProgress())

// onboardProgress.js
export function fetchOnboardProgress () {
  return function (dispatch) {
    var query = Relay.createQuery(Relay.QL`
      query {
        viewer {
          user {
            id
            onboardProgress
          }
        }
      }`, {})

    return new Promise(function (resolve, reject) {
      Relay.Store.primeCache({query}, ({done, error}) => {
        if (done) {
          const data = Relay.Store.readQuery(query)[0]
          dispatch(update(data.user.onboardProgress, data.user.id))
          resolve()
        } else if (error) {
          reject(Error('Error when fetching onboardProgress'))
        }
      })
    })
  }
}

Redux アクションのディスパッチ時にサーバー上のデータを更新する

Redux => Relay => Server

状態の変化に一貫性を持たせるために、ユーザーがオンボーディング プロセスを進めるときに Redux アクションを起動します。このアクションは、Relay ミューテーションも非同期で実行します。私もredux-thunkこの目的で使用しています。

function nextStep () {
  return function (dispatch, getState) {
    const currentStep = getState().onboardProgress.step
    const currentStepIndex = OnboardProgress.steps.indexOf(currentStep)
    const nextStep = OnboardProgress.steps[currentStepIndex + 1]
    const userId = getState().onboardProgress._userId

    return _updateReduxAndRelay(dispatch, nextStep, userId)
  }
}

function _updateReduxAndRelay (dispatch, step, userId) {
  return new Promise((resolve, reject) => {
    Relay.Store.commitUpdate(new UpdateUserMutation({
      userId: userId,
      onboardProgressStep: step,
    }), {
      onSuccess: () => {
        dispatch(update(step, userId))
        resolve()
      },
      onFailure: reject,
    })
  })
}

export function update (step, userId) {
  const payload = {onboardProgress: new OnboardProgress({step, userId})}
  return {type: UPDATE, payload}
}

未解決の問題

私はまだ次の状況へのアプローチを見つけていません:

Relay Store の更新時に Redux Store を更新する

サーバー上のデータへの変更には、アプリ内のユーザー アクションによってトリガーされない外部ソースが含まれる場合があります。Relay を使用すると、これを forceFetching またはポーリングで解決できます。リレー クエリは次のようになりますRelay <= Server。このデータ フローを追加したいと思います:Relay => Redux外部データが変更されたとき。

Redux ストアを新しいデータで更新する必要があるもう 1 つの理由は、Relay ストアに深くネストされているデータ、または複雑なクエリの一部を同期する場合です。

たとえば、ブログ投稿へのコメント数を考えてみてください。ユーザーが新しいコメントを投稿すると、コメント数を表示する別のコンポーネントも更新されます。

この情報を Redux で管理する場合、Relay クエリに新しい情報が含まれているときに Redux アクションをトリガーする方法が必要です。このようなコールバックや、この状況に対する別の解決策については知りません。

私の質問

この文脈で、私はそれらの質問があります:

  1. 既存のアプローチで何を改善できますか? 非常に危険な/矛盾につながるようなことをしたことがありますか? (私の進捗状況を参照)
  2. 何らかの理由で Relay ストアが更新されているときに Redux ストアを同期するにはどうすればよいですか。Redux アクションを Redux ストアに送信できる React コンポーネントのライフサイクル メソッドまたは Relay コールバックを探しています。(未解決の問題を参照)
4

1 に答える 1