New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recommended way to use Apollo client 2.0 after redux removal #2593

Closed
kavink opened this Issue Nov 17, 2017 · 30 comments

Comments

Projects
None yet
@kavink

kavink commented Nov 17, 2017

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.

@tim-soft

This comment has been minimized.

Contributor

tim-soft commented Nov 20, 2017

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

@richburdon

This comment has been minimized.

richburdon commented Nov 22, 2017

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
      )
    );
@natterstefan

This comment has been minimized.

natterstefan commented Dec 2, 2017

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...

@aarondancer

This comment has been minimized.

aarondancer commented Dec 14, 2017

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.

@maplion

This comment has been minimized.

maplion commented Jan 13, 2018

@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.

@anselmdk

This comment has been minimized.

anselmdk commented Jan 13, 2018

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.

@natterstefan

This comment has been minimized.

natterstefan commented Jan 17, 2018

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: #2273 (comment). 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 :)

@aarondancer

This comment has been minimized.

aarondancer commented Jan 17, 2018

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.

@natterstefan

This comment has been minimized.

natterstefan commented Jan 17, 2018

@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.

@aarondancer

This comment has been minimized.

aarondancer commented Jan 17, 2018

@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.

@natterstefan

This comment has been minimized.

natterstefan commented Jan 17, 2018

@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.

@maplion

This comment has been minimized.

maplion commented Jan 17, 2018

@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)?

@aarondancer

This comment has been minimized.

aarondancer commented Jan 17, 2018

@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.

@aarondancer

This comment has been minimized.

aarondancer commented Jan 17, 2018

@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.

@maplion

This comment has been minimized.

maplion commented Jan 17, 2018

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

@rajiff

This comment has been minimized.

rajiff commented Feb 12, 2018

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?

@loganpowell

This comment has been minimized.

loganpowell commented Feb 14, 2018

(crickets)

@kavink

This comment has been minimized.

kavink commented Feb 14, 2018

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.

@kavink kavink closed this Feb 14, 2018

@loganpowell

This comment has been minimized.

loganpowell commented Feb 14, 2018

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

@smysnk

This comment has been minimized.

smysnk commented Feb 22, 2018

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.

@loganpowell

This comment has been minimized.

loganpowell commented Feb 23, 2018

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

@smysnk

This comment has been minimized.

smysnk commented Feb 25, 2018

@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.

@therealkevinard

This comment has been minimized.

therealkevinard commented Feb 26, 2018

@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.

@loganpowell

This comment has been minimized.

loganpowell commented Feb 27, 2018

@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
        }
    }
`;
  1. 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)?

@ryannealmes

This comment has been minimized.

Contributor

ryannealmes commented Jul 24, 2018

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

@reanimatter

This comment has been minimized.

reanimatter commented Aug 11, 2018

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;
}
@loganpowell

This comment has been minimized.

loganpowell commented Oct 14, 2018

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.

@stock1232

This comment has been minimized.

stock1232 commented Oct 16, 2018

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

@reanimatter

This comment has been minimized.

reanimatter commented Oct 17, 2018

@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.

@hinsxd

This comment has been minimized.

hinsxd commented Nov 4, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment