React Redux actions/reducers with apollo client


#1

Hi, I had just finished my first react redux application that get/post to a java ee api rest server with a mysql database.

Now I am trying apollo and I want to migrate my app to use apollo (server and client).

Currently I succesfully build my apollo server with a sequelize and mysql (to replace completelly the java ee tier by apollo server), and I am able to perform queries to apollo server using the graphiql console.

Now I want to refactor my redux react applications. Iv been reading the documentation of apollo client but it is not clear for me how to refactor my current actions creators, like for example:

action:

export function fetchPersons() {
  return dispatch => {
    dispatch(beginAjaxCall());
    return restApi.fetchData("persons")
      .then(response => response.json())
      .then(persons => dispatch(fetchPersonsSuccess(persons)));
  };
}


and a reducer:

export default function persons(state = initialState.persons, action) {
  switch (action.type) {
    case types.FETCH_PERSONS_SUCCESS:
      return action.persons;

    default:
      return state;
  }
}

How do I implement the same using apollo client queries? Can I keep using my actions creators and replace the api call for for example a call to a apollo query/mutation or must I use only the mapQueriesToProps and fire the query/mutation from my container component?


#2

Hey, did you get your question answered on the Apollo slack channel? I just want to make sure you’re not stuck without answers!


#3

@helfer could you explain, im stuck on the same situation as @windgaucho

i have a project running under Redux and Babel + Webpack and i want to use Meteor and i have my mind broken trying to find the best way to do it.

Thanks!


#4

Hello @helfer, yes more or less, but it is not fault of the answers of the slack channels members, it is just that I am a beginner with apollo and I am having difficulties in refactor my existing react redux app for include apollo client and perform fetch/post (query/mutations) to my apollo server from my components.

My current app have actions and reducers and I am trying to include apollo client in my components for perform a query/mutations to the apollo server.

For example, here is a little test component that I have in current react redux app:

import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import * as fueroActions from '../../actions/fueros';
import FueroList from './FueroList';

class Fueros extends Component {
  componentWillMount() {
    this.props.actions.fetchFueros();
  }
  render() {
    return (
      <FueroList fueros={this.props.fueros}/>
    );
  }
}
function mapStateToProps(state, ownProps) {
  return {fueros: state.fueros};
}
function mapDispatchToProps(dispatch) {
  return {actions: bindActionCreators(fueroActions, dispatch)};
}
export default connect(mapStateToProps, mapDispatchToProps)(Fueros);

Here, I am firing an action in componentWillMount, this action perform a get in my api call (in my actions creators), and update my store in my reducer. My component is listening to the store with mapStateToProps, and the render the result when ready.

Now, I am confused about how to do the same using apollo-client or react-apollo.
Reading the docs I think that there is two approach:

  1. I can use apollo client query or watchQuery in mi action creators for replace my current api call, and continue to use my react redux logic or
  2. I can use mapQueriesToProps to perform a query against my apollo server, but I don’t know how my actual reducer will listen.

I will appreciate if you can point me in the right direction and tell me what is the best way in using apollo with a react redux app.
Thanks


#5

Hi windgaucho and cristiandley,

With the disclaimer that I’m not a Redux expert, here’s how I think you can best hook up your existing redux app (I’m leaving out some of the boilerplate, like imports):

First, use the ApolloProvider, and give it your redux store, as well as the client:

// Globally register gql template literal tag
registerGqlTag()

const networkInterface =
  createNetworkInterface(YOUR_GRAPHQL_URL);

const client = new ApolloClient({ networkInterface });

// use combine reducers for your store, and hook up redux dev tools
const store = createStore(
  combineReducers({
    something: yourOtherReducer1,
    anotherThing: yourOtherReducer2,
    apollo: client.reducer(),
  }),
  applyMiddleware(client.middleware()),
  window.devToolsExtension ? window.devToolsExtension() : f => f
);

// then render your react root component

render(
  <ApolloProvider store={store} client={client}>
    <YourComponent categoryId={5} />
  </ApolloProvider>,
  document.getElementById('root')
)

Then use connect from react-apollo to make your smart component (just like redux connect, but with two more methods. Check the docs for more info).

// your other code here...

const mapQueryToProps = ({ ownProps, state }) => {
  return {
    category: {
      query: gql`
        query getCategory($categoryId: Int!) {
          category(id: $categoryId) {
            name
            color
          }
        }
      `,
      variables: {
        categoryId: ownProps.categoryId,
      },
      forceFetch: false, // optional 
      returnPartialData: true,  // optional
    },
  };
};

export default connect(mapStateToProps, mapDispatchToProps, mapQueriesToProps)(Fueros);

If the query takes care of your dispatch thing, then you don’t need mapDispatchToProps. Also, if you don’t have any local state in your app, you can skip the entire store declaration and you also don’t need mapStateToProps.

I hope this helps! If you have more questions, feel free to bug people on Slack (or ask here).


#6

@helfer thank you for all your help !!

I successfully conect apollo-client, react-apollo with my example component.

Now, my issue is that in my render() method is still loading and give an error about context not defined.
My solution was to declare a condition over loading property like follows:

      render() {
        if (!this.props.data.loading) {
           const {people} = this.props.data;
           return (
          <PeopleList people={people} />
        );
      } else {
        return (<div></div>)
      }

How can avoid this? In my previous react redux app, without apollo, I was setting my initial data of people in my reducer, so when the components first load I have no problem:

    intialStore = {
    people:[{}]
    }    
    export default function people(state = initialState.people, action) {
        ......
    }

#7

@windgaucho is the same that is happening to me !!! @helfer

maybe there is something that im not understanding.

Thanks


#8

Yeah, that’s perfectly normal. I’m not sure what Redux does to avoid this, but for react-apollo you have to do this, at least for the moment.


#9

@helfer ok understood.

In redux you set an initial store in your reducer and then you bind your component with the store using mapStateToProps. So the render method have the store properly initialized, maybe the connect of the react-apollo works a little different to the react-redux.

Another possibility will be dispatch the query call in my reducer action, wait for the query result, and dispatch another redux action for get my result data in my store (I think it is apollo already does), but with this, my component use mapStateToProps for get the data from my reducer.
I don’t know if this is recommendable approach for use apollo with react redux, what do you think about?


#10

I think I would stick to the approach outlined in the docs (using connect + mapQueriesToProps), but if that doesn’t do the job for some reason, the best thing to do would be to file an issue on the react-apollo repository. The API is still evolving, and I’m sure your input would be appreciated!


#11

Perfect !! I will migrate my app completely to use connect and mapQueriesToProps, thank your your help!!


#12

There’s a small issue with this, which caused me a big headache, and it’s hard to find resources about this on google.

before:

const store = createStore(
  combineReducers({
    something: yourOtherReducer1,
    anotherThing: yourOtherReducer2,
    apollo: client.reducer(),
  }),
  applyMiddleware(client.middleware()),
  window.devToolsExtension ? window.devToolsExtension() : f => f
);

should use compose, as createReducer takes a single function as the second parameter. As it is, window.devToolsExtension ? window.devToolsExtension() : f => f is being passed to createStore as the initial state. To fix just import compose from redux, and then use it to wrap the two functions:

after:

const store = createStore(
  combineReducers({
    something: yourOtherReducer1,
    anotherThing: yourOtherReducer2,
    apollo: client.reducer(),
  }),
  compose(
    applyMiddleware(client.middleware()),
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )
);