Apollo-client: Recommended way to use Apollo client 2.0 after redux removal

Created on 17 Nov 2017  路  33Comments  路  Source: apollographql/apollo-client

Just wanted to know what is the community recommended usage of Apollo Client after redux removal.

Mainly my question surrounds for new projects which are on simpler side, i.e. it just depends on multiple graphql queries from server and display those. Also triggering mutations and updating queries.

Do we recommend using using apollo-in-memory cache ? but im finding it hard to figure out how do I set other state or flags, e.g. querying all available "something" and then setting global state/filter to user chosen value and then query again with new values and update all dependent views. Trying to read apollo-in-memory cache docs, seems its possible. But im still struggling to map the redux world API's to in-memory cache and how to use them . (maybe because im new to concepts of Apollo)

Or do we recommend also using Redux in additional to apollo-in-memory cache and storing the deduced state or flags in redux and using both ?

would be great to have a real world example to see how best to use all powerful features of apollo-in-memory-cache.

Most helpful comment

Releasing 2.0 without a Redux-based cache option IMO was a bad decision. Because Apollo 1.x relied on Redux, many projects tightly integrated their logic using Redux and may rely on accessing Apollo's data through Redux. Having data fragmented between two stores is not ideal for many reasons, and is especially true for large/complex apps.

I hope this gets addressed with a stable solution soon. I was excited about all the changes in 2.0, until I found out that there wasn't a Redux cache ready.

If you were using the Redux integration for other uses, please reach out or open an issue so we can help find a solution with the 2.0!

I really feel like this should have been asked before 2.0 was made final. To be frank, I hadn't heard anything about the removal of Redux in 2.0 prior to the release. Right now I feel like people are confused and unsure of what to do other than rewrite portions of their apps or stick with 1.x.

All 33 comments

I'm personally waiting on https://github.com/rportugal/apollo-cache-redux to mature. I'm unwilling to upgrade until I can use redux.

Has redux integration support been remove entirely? E.g., below.

This isn't addressed at all here: https://github.com/apollographql/apollo-client/blob/master/Upgrade.md. That's quite harsh.

~~~~
this._store = createStore(

  // Reducers.
  combineReducers({
    apollo: client.reducer(),                    // GONE
    [AppReducer.NS]: appReducer.reducer()
  }),

  // Middleware.
  compose(
    applyMiddleware(client.middleware())         // GONE
  )
);

~~~~

Any news on that issue? I do not want to upgrade to the latest apollo either, when I do not know how to integrate redux...

Releasing 2.0 without a Redux-based cache option IMO was a bad decision. Because Apollo 1.x relied on Redux, many projects tightly integrated their logic using Redux and may rely on accessing Apollo's data through Redux. Having data fragmented between two stores is not ideal for many reasons, and is especially true for large/complex apps.

I hope this gets addressed with a stable solution soon. I was excited about all the changes in 2.0, until I found out that there wasn't a Redux cache ready.

If you were using the Redux integration for other uses, please reach out or open an issue so we can help find a solution with the 2.0!

I really feel like this should have been asked before 2.0 was made final. To be frank, I hadn't heard anything about the removal of Redux in 2.0 prior to the release. Right now I feel like people are confused and unsure of what to do other than rewrite portions of their apps or stick with 1.x.

@aarondancer I agree and I still feel like the Apollo crew is deliberately ignoring this issue. It doesn't exactly give me confidence in adopting Apollo for anything in future projects if they are willing to make big breaking changes and purposely leave large swaths of the community high-and-dry in the process and then continue to forge ahead as if nothing happened.

Just adding my voice here. I'm still left out in the dark re. what to do right now, and can't really find any real information.

Hey @aarondancer, @maplion and @anselmdk.

I am still not 100% convinced that removing Redux was a good choice, but at least I got 2.0 to work whilst still using/having Redux (kind of).

This was the answer, after which finally helped me to understand how to still use Redux and get both to work side by side: https://github.com/apollographql/apollo-client/issues/2273#issuecomment-349240207. Now you could still update the store

For people new to Apollo 2.0 (like me), this tutorial helped me a lot to understand the latest version: https://www.howtographql.com/react-apollo/1-getting-started/ and this answer from Peggy Rayzis.

Draft Example:

const mapState = (state) => {
  return {
    items: state.items || [],
  }
}

const mapDispatch = (dispatch) => {
  return {
    onCreateItem: (item) => dispatch(createItem(item)),
  }
}

const mergeProps = (stateProps, dispatchProps, ownProps) => {

  const mutations = {
    addItem: (story) => {
      // dispatch redux action
      ...
      // and update apollo cache
      ownProps.mutations.addItem(item)
    }
  }

  const newOwnProps = _.omit(ownProps, 'mutations')
  return {
    mutations,
    ...stateProps,
    ...dispatchProps,
    ...newOwnProps
  }
}

// lodash's flow
export default flow(
  connect(mapState, mapDispatch, mergeProps),
  withApollo,
  // mutations
  graphql(addItem, {
    props: ({ mutate, ownProps }) => ({
      mutations: {
        ...ownProps.mutations,
        // will be available at ownProps.mutations.* above
        addItem: ({ id, title }) =>
          mutate({
            variables: { id, title },
            update: (proxy, { data: response }) => {
             // update apollo cache...       
            }
          })
      }
    })
  })
)(ItemComponent);

Hope this helps at least someone reading this :)

Something I'd like to note, we know that we can use Apollo 2.0 along side a separate Redux store. It's just a matter of using the HoCs for each library, and there's hacky ways to get Apollo's data read by our reducers. The issue is that people may have logic that's already tightly integrated with Apollo 1.x's use of a Redux store and that there's current no stable/recommended solution other than a rewrite, which may not be an option for many.

The Apollo team even mentioned in their 2.0 announcements that we would be able to use Redux, Mobx, etc for our caches. So logically you'd think they'd release or at least address the plans for an official Redux cache with backwards compatibility with 1.0.

@aarondancer Okay got it. Thanks for pointing this out :) Well, then let's see if I stumble upon something in the future or someone else.

@natterstefan IMO that solution is really convoluted and pushes too much logic outside of Redux reducers and actions. I would rather ditch Redux entirely and use apollo-link-state (which is awesome) or just stick to 1.x until a Redux cache is made available. It would actually be cheaper for me to rewrite my state to be entirely in Apollo in some large apps than rewrite the containers and reducers to accommodate 2.0 in that style.

@aarondancer true that. It is not a solution that can be applied to bigger applications, maybe just to small and prototype ones.

apollo-link-state (which is awesome)

I am still not getting all the features and awesomeness of it. Do you have a good tutorial, gist or example for me? :)

Btw, thanks for the feedback. Helps to learn.

@aarondancer Even if you use apollo-link-state and are able to store locally using that instead, would you really want to ditch Redux? Sure, you can get a faster cache and what not, but faster isn't always the goal; sometimes it is just management of the state and running lots of logic and validation through reducers. Am I missing something or is the apollo-link-state just a way to store locally using graphql? Is there a way to emulate all that you can do with actions and reducers (i.e. be able to reach in during a state change to run validation and the like)?

@natterstefan I honestly don't have any good resources for learning it. When I read the official announcement and repo's readme I felt the same way. Frankly, I didn't see how it was useful. It wasn't until some of the devs I follow on Twitter (including some of the Apollo team) showed some code snippets and had discussions that I realized how great it could be.

@maplion apollo-link-state is definitely not a perfect replacement to Redux. apollo-link-state isn't a cache implementation, it's just a way to store your local state inside of Apollo's cache (default or other afaik) using GraphQL. In practice, it _can_ work very similarly to Redux. Here's a bit of insight from my experience using it so far on a couple medium-ish sized apps.

  1. Instead of dispatching actions you use mutations, and instead of connecting data from stores using JS, you query what you want using GraphQL. Now all of your data-related fetching can be written entirely in GraphQL and without needing multiple HoCs for connecting data/store endpoints.

  2. Instead of reducers, you use resolvers. These are basically the same idea, and work very similarly.

  3. Lower mental overhead. This entirely depends on the codebase and individual, but having all my data-related logic written in a unified manner definitely helps.

I recommend Apollo's docs for some more detailed insight on how apollo-link-state works: https://www.apollographql.com/docs/link/links/state.html

That being said, it's still definitely a WIP and there's no way I'd convert any of my large apps to apollo-link-state _yet_, and I don't think it's a state solution suitable for everyone. I'm still firm on my opinion that we need a Redux cache or an official solution to this issue.

@aarondancer Thanks for the insight. I haven't wrapped my head around it yet, but it sounds like it has potential.

Hi not sure If I am missing something, but I don't see a way around for this issue

Is it in GraphQL 2.0, we can no longer use single source of truth for stage using Redux?

Is apollo-link-state is the only way forward?

I cannot figure out how I ensure Redux reducers are the only way to update the stat
Colocating components with graphql query works, but not sure if thats the correct way, as it does not pass through Redux Store neither Redux Reducers

Please can we have some direction from he team?

(crickets)

If its of any value to others, I have successfully avoided using redux and using apollo-link-state and react-final-forms.

The summary of the issue was from Apollo team for people to move towards apollo-link-state, if it fits your use case.

I guess redux is the new jquery or jquery is the new redux dont know whats more snarky 馃槃 but most libraries look like moving away from redux.

I will go ahead and close the issue, because I got a solution working for me and moved away from redux.

Sorry for pinging on the thread after you closed it, but thought others might have the feeling that Apollo is bigfooting. I love Apollo and all that they've done, but it does feel like "GraphQL all the things" may be overload for some

It is probably for the best Apollo is moving away from Redux because it was just using redux as place to store state but not actually embracing redux in any meaningful way. I believe true users of Redux understand that their view and state should not be combined. The whole pattern of using v2 ApolloProvider muddies the water and introduces a black box between data / local state and view.

One use redux because you want SSR, TimeTravel, sane debugging state changes. Allowing Apollo to drive your react components will only take you farther away from this. If you want to use Apollo client with Redux in any sane way you must use it decoupled from all the magic they're trying to introduce with graphql queries close to yourReact components. Use Apollo client directly with actions or redux middleware.

Use Apollo client directly with actions or redux middleware.

@smysnk I was thinking the exact same thing. Do you have a gist or link you can share that shows how to do that? I've come up almost bone dry with the exception of some one-off issues comments

@loganpowell I have a working example from a reactql.com starter kit I've been butchering..

I am going to pull the thunks way far away from the react components, but have it here temporarily. In this file I have a updateFeed() thunk that will call the apollo client directly to make a graphql query call. After the query is query is complete it will dispatch a UPDATE_FEED action with the payload. The reducer will then shape the data into the state tree. Passing the apollo client into the redux-thunk middleware.

If you want to run this example it currently uses the GitHunt-API graphql endpoint.

@loganpowell i pushed a (not so) minimal pair of repos where I'm doing this. The client is https://github.com/therealkevinard/react-redux-apollo-minimal-client (server is therealkevinard/react-redux-apollo-minimal-client).

Client's store is from from the react redux feathers hot reload boilerplate. It's very simple, once you're setup for async actions. I have client.js where I build and export apollo client. If you look at the action for loadTimers (or similar name) you'll see where I'm calling client nearly identical to the way you'd query inside a cmp that's wrapped with withApollo. The response is handled like any other action.

@therealkevinard Thank you for this! For posterity:

Links:

Redux/Apollo Integration:
1) Creating Apollo Client

2) Forming some gql queries

import gql from 'graphql-tag'

export const TIMER_FRAGMENT_BASE = gql`
    fragment timerBase on TimerType{
        id
        label
        description
        loop
        tags
    }
`;

export const TIMER_FRAGMENT_TIMERINFO = gql`
    fragment timeInfo on TimerType{
        active
        start
        end
    }
`;

export const TIMERS_QUERY = gql`
    ${TIMER_FRAGMENT_BASE}
    ${TIMER_FRAGMENT_TIMERINFO}
    query GetTimers{
        timers {
            ...timerBase
            ...timeInfo
        }
    }
`;

3) Making a GraphQL query from action-creator

import client from '../../apollo-client/client';
import {TIMERS_QUERY} from "../../scenes/timers/gql/timerQueries";
...
export function loadTimers() {
    return {
        types: [TIMER_FETCH, TIMER_FETCH_SUCCESS, TIMER_FETCH_FAIL],
        promise: async () => {
            const result = await client.query({
                query: TIMERS_QUERY,
            });
            return result.data.timers;
        }
    };
}

Just lovely!

So, in general, what I'm gathering is that we just call the client directly from wherever we want to invoke a query/mutation/subscription and get a result. Is that about right? If so, how/where might you handle a subscription (or in the future @live stream)?

I got emo when I saw there was no more redux, but after figuring out exactly how apollo-link-state works, it is actually quite a nice solution to managing global state. Thought I would provide another example here of how one can use apollo to manage client state :)

Setting up your state
https://github.com/ryannealmes/shakra-web/blob/68f3e2b7a89d7dda81713651061ad7e2c530fffb/lib/withClientState.js

Making it available in your cache
https://github.com/ryannealmes/shakra-web/blob/68f3e2b7a89d7dda81713651061ad7e2c530fffb/lib/initApollo.js#L40

Accessing the data from your cache
https://github.com/ryannealmes/shakra-web/blob/68f3e2b7a89d7dda81713651061ad7e2c530fffb/pages/withAuthentication.js#L8

Those who have redux/redux-saga setup may benefit from using saga's setContext/getContext

import { setContext } from 'redux-saga/effects';
import ApolloClient, { InMemoryCache } from 'apollo-boost';

const client = new ApolloClient({
  cache: new InMemoryCache({
    addTypename: false
  }),
  defaultOptions: {
    query: {
      fetchPolicy: 'no-cache' // ignored for some reason. i had to set the policy in the client.query call
    }
  }
})

export function* rootSaga() {
  yield setContext({ client });
  /* other sagas */
}
import { call, getContext } from 'redux-saga/effects';
import { gql } from 'apollo-boost';

const query = gql`
  query {
    animals {
      id
      name
    }
  }
`;

export function* animalsWorkerSaga() {
  const client = yield getContext('client');
  const { data: { animals } } = yield call(client.query, { query, fetchPolicy: 'no-cache' });
  return animals;
}

I'd like to take back some of my prior commentary in here, especially the "bigfooting" comment. After quite a bit of thought on this, I believe that making GraphQL a form through which we can manage state makes a lot of sense, especially given that links can be customized, created and composed together. There are a lot of benefits to having a single data shape being the API for both server data resolution as well as state resolution. The Apollo team is doing some amazing work and a new state management solution is probably just what the doctor ordered if you are ready to embrace GraphQL as the API protocol abstraction of the future, which I - for one - have.

@reanimatter Very cool implementation with sagas. How would you subscribe to a subscription mutation using a saga? Is it even possible?

@stock1232 , I think you can do that with eventChannel. Here is an example (I didn't test it, so there may be some issues)

import { eventChannel } from 'redux-saga';
import { call, put, fork, getContext, takeEvery } from 'redux-saga/effects';
import gql from 'graphql-tag';

// an action creator to update the state
const updateState = payload => ({ type: 'UPDATE_STATE', payload });

const query = gql`
  subscription...
`;

const createEventChannel = (client, params) => eventChannel(emitter => {
  const observable = client.subscribe({
    query,
    fetchPolicy: 'no-cache',
    variables: params
  });
  const subscription = observable.subscribe({
    next(value) {
      emitter(value);
    }
  });
  return () => {
    subscription.unsubscribe();
  };
});

function* handleEvent(payload) {
  yield put(updateState(payload));
}

function* subscribe(params) {
  const client = yield getContext('client');
  const channel = yield call(createEventChannel, client, params);
  yield takeEvery(channel, handleEvent);
}

export default function* saga() {
  // subscription params
  const params = { };
  yield fork(subscribe, params);
}

So, the main idea is to setup an event channel listening to Apollo subscription. Once you get data, you can dispatch a regular action to update the state.

@reanimatter How does the setContext way differ from directly importing the client? Is the context globally accessible across sagas in the same store?

I have fixed this bug in apollo-cache-redux. Since apollo-cache-redux is deprecated, you can now use @codejamninja/apollo-cache-redux.

npm install --save @codejamninja/apollo-cache-redux
import { ReduxCache, apolloReducer } from '@codejamninja/apollo-cache-redux';

https://github.com/codejamninja/apollo-cache-redux
https://www.npmjs.com/package/@codejamninja/apollo-cache-redux

This fixes the following issues
https://github.com/rportugal/apollo-cache-redux/issues/27
https://github.com/rportugal/apollo-cache-redux/issues/35

Guys I came from the future... remove Redux from Apollo Client was an excellent choice from the core team. You don't need Redux for anything, Apollo Cache is much better and simpler to use.

@JuanmaMenendez Now try go back further in time and submit some PRs to add Redux to Apollo :D

Was this page helpful?
0 / 5 - 0 ratings