web-dev-qa-db-ja.com

ApolloGraphQLクエリが返された後にReduxストアを更新する方法

Reactapolloが提供するgraphqlHOCを使用してデータのリストを取得しています。例えば。:

_const fetchList = graphql(
  dataListQuery, {
    options: ({ listId }) => ({
      variables: {
        listId,
      },
    }),
    props: ({ data: { loading, dataList } }) => {
      return {
        loading,
        list: dataList,
      };
    }
  }
);
_

制御されたラジオボタングループにリストを表示していますが、デフォルトで項目の1つを選択する必要があります。選択したアイテムのidはReduxストアに保存されます。

だから、問題は、クエリが正常に戻った後にReduxストアを更新する(つまり、selectedItemを設定する)方法です。

私の頭に浮かんだいくつかのオプション:

オプション1

Reduxレデューサーで_APOLLO_QUERY_RESULT_アクションをリッスンする必要がありますか?しかし、クエリが以前に実行されている場合は、_APOLLO_QUERY_RESULT_と_APOLLO_QUERY_RESULT_CLIENT_の両方をリッスンする必要があるため、これはちょっと厄介です。また、operationName小道具は_APOLLO_QUERY_RESULT_アクションにのみ存在し、_APOLLO_QUERY_RESULT_CLIENT_アクションには存在しません。したがって、すべての_APOLLO_QUERY_RESULT_CLIENT_アクションを分析して、それがどこから来たのかを知る必要があります。クエリ結果のアクションを識別する簡単で直接的な方法はありませんか?

オプション2

componentWillReceivePropsで_SELECT_LIST_ITEM_のような別のアクションをディスパッチする必要があります(例: recompose を使用):

_const enhance = compose(
  connect(
    function mapStateToProps(state) {
      return {
        selectedItem: getSelectedItem(state),
      };
    }, {
      selectItem, // action creator
    }
  ),
  graphql(
    dataListQuery, {
      options: ({ listId }) => ({
        variables: {
          listId,
        },
      }),
      props: ({ data: { loading, dataList } }) => ({
        loading,
        items: dataList,
      }),
    }
  ),
  lifecycle({
    componentWillReceiveProps(nextProps) {
      const {
        loading,
        items,
        selectedItem,
        selectItem,
      } = nextProps;
      if (!selectedItem && !loading && items && items.length) {
        selectItem(items[items.length - 1].id);
      }
    }
  })
);
_

オプション3

ApolloクライアントをwithApolloで挿入して直接使用し、次にclient.query(...).then(result => { /* some logic */ selectItem(...)})でアクションをディスパッチする必要があります。しかし、そうすると、react-apollo統合のすべての利点が失われるため、実際にはオプションではありません。

オプション4

クエリが戻った後、Reduxストアをまったく更新しないでください。設定されている場合はselectedItemを返すセレクターを実装することもでき、設定されていない場合は、ストアのapollo部分を参照して派生しようとするためです。

私の選択肢はどれも私を満足させません。だから、私はそれをどのように正しくしますか?

23
TheWebweiser

ComponentDidUpdateの変更をリッスンし、それらが発生したときに、reduxストアにselectedItemを設定するアクションをディスパッチします

componentDidUpdate(prevProps, prevState) {

    if (this.props.data !== prevProps.data) {
        dispatch some action that set whatever you need to set
    }
}
1

オプション2と同様のことを行いますが、ライフサイクルメソッドを実際のコンポーネントに配置します。このようにして、ライフサイクルのビジネスロジックは、コンテナから継承された小道具から分離されます。

だからこのようなもの:

class yourComponent extends Component{
    componentWillReceiveProps(nextProps) {
      const {
        loading,
        items,
        selectedItem,
        selectItem,
      } = nextProps;
      if (!selectedItem && !loading && items && items.length) {
        selectItem(items[items.length - 1].id);
      }
    }
  render(){...}
}

// Connect redux and graphQL to the Component
const yourComponentWithGraphQL = graphql(...)(yourComponent);
export default connect(mapStateToProps, mapDispatchToProps)(yourComponentWithGraphQL)
1
C.Lee

オプション2の少し良いバージョンであるhocを使用します。composeの最後にwithLoaderHocを使用します。

const enhance = compose(
    connect(),
    graphql(dataListQuery, {
      options: ({ listId }) => ({
        variables: {
          listId,
        },
      }),
      props: ({ data: { loading, dataList } }) => ({
        isLoading:loading,
        isData:!!dataList,
        dataList
       }),
    }
  ),
withLoader
)(Component)

2つのプロップisDataとisLoadingに基づくWithLoaderホックレンダリングコンポーネント。 isDataがtrueの場合、ラップされたコンポーネントをレンダリングします。それ以外の場合はローダーをレンダリングします。

    function withLoader(WrappedComponent) {
        class comp extends React.PureComponent {
           render(){
              return this.props.isData?<WrappedComponent {...this.props}/>:<Loading/>
           }
        }
    }

ComponentのcomponentWillMountメソッドでdataListの最初の項目を設定しました。 withLoader hocによって保証されるdataListを取得するまで、コンポーネントはマウントされません。

0
shah chaitanya

私の意見では、取る最善のアプローチは、graphqlと同様に使用されるhoc of Option 2のわずかに変更された構成可能なバージョンを作成することです。その場限り。頭に浮かぶ使用例は次のとおりです。

export default compose(
  connect(
    state => ({ /* ... */ }),
    dispatch => ({ 
      someReduxAction: (payload) => dispatch({ /* ... */ }),
      anotherReduxAction: (payload) => dispatch({ /* ... */ }),
    }),
  ),
  graphqlWithDone(someQuery, {
    name: 'someQuery',
    options: props => ({ /* ... */ }),
    props: props => ({ /* ... */ }),
    makeDone: props => dataFromQuery => props.someReduxAction(dataFromQuery)
  }),
  graphqlWithDone(anotherQuery, {
    name: 'anotherQuery',
    options: props => ({ /* ... */ }),
    props: props => ({ /* ... */ }),
    makeDone: props => dataFromQuery => props.anotherReduxAction(dataFromQuery)
  })
)(SomeComponent)

そして、最も単純な実装は次のようになります。

const graphqlWithDone = (query, queryConfig) => (Wrapped) => {

  const enhance = graphql(query, {
    ...queryConfig,
    props: (props) => ({
      queryData: { ...( props[queryConfig.name] || props.data ) },
      queryProps: queryConfig.props(props),
    })
  })

  class GraphQLWithDone extends Component {
    state = {
      isDataHandled: false
    }

    get wrappedProps () {
      const resultProps = { ...this.props };
      delete resultProps.queryData;
      delete resultProps.queryProps;
      return {  ...resultProps, ...this.props.queryProps }
    }

    get shouldHandleLoadedData () {
      return (
        !this.props.queryData.error &&
        !this.props.queryData.loading &&
        !this.state.isDataHandled
      )
    }

    componentDidUpdate() {
      this.shouldHandleLoadedData &&
      this.handleLoadedData(this.props.queryData);
    }

    handleLoadedData = (data) => {
      if (!makeDone || !isFunction(makeDone)) return;
      const done = makeDone(this.wrappedProps)
      this.setState({ isDataHandled: true }, () => { done(data) })
    }

    render() {
      return <Wrapped {...this.wrappedProps}  />
    }
  }

  return enhance(GraphQLWithDone)
}

私がこの擬似コードを試したことがないとしても、テストも終了もしていません。その背後にある考え方は非常に単純で理解しやすいものです。それが誰かを助けることを願っています

0
streletss

'props'を使用するのに十分なはずです。

const enhance = compose(
  connect(
    function mapStateToProps(state) {
      return {
        selectedItem: getSelectedItem(state),
      };
    }, {
      selectItem, // action creator
    }
  ),
  graphql(
    dataListQuery, {
      options: ({ listId }) => ({
        variables: {
          listId,
        },
      }),
      props: ({ data: { loading, dataList } }) => {
        if (!loading && dataList && dataList.length) {
          selectItem(dataList[dataList.length - 1].id);
        }
        return {
          loading,
          items: dataList,
        }
      },
    }
  ),
);
0
xadm

私は過去に同様の問題に直面し、オプション2と同様のものを選択しました。独自のreduxストアとapollo独自の内部ストアの両方がある場合、それらの間の状態の同期が問題になります。

Apolloを使用している場合は、独自のreduxストアを削除することをお勧めします。 gqlサーバーといくつかのレストサーバーに同時に依存している場合は、データを論理的および物理的に分離します。

「データソース」としてapolloを使用することを決定すると、ディスパッチは単なる変更であり、状態の取得は単なるクエリです。クエリでフィルタリング、並べ替えなどを行うこともできます

0