React Apollo and Redux: Combine custom reducers with Apollo state

纵然是瞬间 提交于 2019-12-08 04:43:08

问题


When using react-apollo with redux, data from the server is cached in the redux store under an apollo key:

{
    apollo: // results from Apollo queries
}

After creating your own reducers and corresponding state tree, your store might look like this:

{
    apollo: { ... },
    ui: {
        selectedItem: 2;
        showItems: 6
    }
}

Imagine that there is an action which changes the value of ui.selectedItem, and that this value is the index of an item buried somewhere in the apollo object.

With reducer composition, reducers for our ui state will rightly have no access to anything under the Apollo state.

In our component we can subscribe to changes on our ui state via mapStateToProps, and we can map Apollo results to props using the react-apollo GraphQL container (or @graphql decorator).

But how can we update our store with values that are computed from Apollo results and other state in our application?

For example, if apollo.item.list is an array of items, and ui.selectedItem is the index you want to target in that list - how could the store contain a value for:

apollo.item.list[ui.selectedItem]

回答1:


You're right -- with reducer composition, each reducer should not have access to the rest of the state. The question is why would you even need your store to contain a separate value for apollo.item.list[ui.selectedItem].

Presumably you need this value to render the correct item inside your container. To calculate it, you just need an appropriate selector to call within mapStateToProps. Pass in the whole state, get the value you need. A simplified example:

const mapStateToProps = state => ({
  itemToShow: itemSelector(state)
})

const itemSelector = ({ apollo, ui }) => apollo.item.list[ui.selectedItem]

You'll need to wrap your container in both the connect and graphql HOCs to make that work, although as far as I can tell, whether you wrap it in one or the other first doesn't seem to matter. You can also look into using reselect which is ideal for derived data like this.

Edit: Since Apollo's chunk of the store wasn't really designed to be used as described above, you may find it easier to break this up into two steps instead: First, assign ui.selectedItem to a prop in mapStateToProps. Then, if you wrap your container with the graphql HOC, you can reference that prop like so:

const mapStateToProps = state => ({ selectedItem });
const mapDataToProps = ({ ownProps: { selectedItem }, data }) => ({
  itemToShow: data.item.list[selectedItem]
});
const ContainerWithData = graphql(myQuery, { props: mapDataToProps })(MyContainer);

export default connect(mapStateToProps)(ContainerWithData);

Or you could use apollo-react's compose for a cleaner approach.




回答2:


My solution was to use recompose to compose the store and Apollo states together via mapProps:

import { mapProps, compose } from ‘recompose’;

const mapStateToProps = state => ({
  selectedItem: state.ui.selectedItem
})

const mapDataToProps = {
  props: ({ data }) => ({ list: data.item.list })
}

const selectProps = mapProps(({ selectedItem, list}) => ({
  item: list[selectedItem] 
}))

export default compose(
  connect(mapStateToProps, {}),
  graphql(MY_QUERY, mapDataToProps),
  selectProps
)(MyComponent);

This also allows us to check the graphql loading state using branch, so we don't run selectProps until the data is available.

Thanks to Daniel Rearden's answer for pointing me in the right direction.




回答3:


Not a real answer to your question, but I found that ReactQL is a great starter kit that takes care of all those things for you. It has all the boilerplate for Apollo+React+Redux including custom Redux reducers. See how they did it.



来源:https://stackoverflow.com/questions/44975471/react-apollo-and-redux-combine-custom-reducers-with-apollo-state

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!