问题
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