pshrmn / curi

A JavaScript router for single-page applications
https://curi.js.org
MIT License
269 stars 10 forks source link

Use with Apollo Client #21

Closed loganpowell closed 6 years ago

loganpowell commented 6 years ago

@pshrmn Happy to see me again? I'm your number one fan!

So, I'm getting a little jealous from the examples provided for redux and mobx, because I'm using apollo and - as you can tell - I'm a complete newb to all of this (only 6 months in a coding bootcamp).

I've done some 'research' and I believe it shouldn't be a problem to use Curi with Apollo, but are there any pointers you could give me to set it up?

loganpowell commented 6 years ago

Perhaps something with this: https://www.apollographql.com/docs/react/basics/setup.html#compose

loganpowell commented 6 years ago

And in my code: https://github.com/loganpowell/RAATS/blob/master/src/App.js#L23

loganpowell commented 6 years ago

But to be completely fair, you've been incredible so far and I completely understand if you don't have time for this atm. It seems that there are dedicated packages for redux and mobx. If it requires you to build one of those. I can use Curi down the road when it makes sense for you ;)

pshrmn commented 6 years ago

First, have you tried the tutorial? I think it does a pretty good job familiarizing the basic concepts with using Curi.

As far as Curi + Apollo goes, to start I would recommend ignoring match.every for data loading and just load data in components. That will allow you to follow any Apollo + React tutorials.

When I think about the structure of a site that uses Curi (and I'll describe this for React since it is a bit different with Vue/Svelte), there are two portions.

The first is the overall layout, which is defined in the render function that you pass to the <CuriBase>. Content rendered in here is the same no matter which page a user visits. The only thing that does change is any content you render based on the response (or the second argument to render, which is a navigation object, but you probably rarely if ever need to care about that).

function render(response) {
  return (
    <div>
      <Menu />
      <response.body response={response} />
    </div>
  );
}

The second is the content for a route, which is we can access using response.body. In your routes, you should use match.response to attach a component to reach route.

// routes.js
import Home from './components/Home';
import About from './components/About';

export default [
  {
    name: 'Home',
    path: '',
    match: {
      response({ set }) {
        set.body(Home);
      }
    }
  },
  {
    name: 'About',
    path: 'about',
    match: {
      response({ set }) {
        set.body(About);
      }
    }
  }
];

These components are where you would setup Apollo data loading.

// components/Home.js
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

function Home({ data: { something } }) {
  return (
    <div>
      <h1>This is the home page</h1>
      <p>{something.nestedProperty}</p>
    </div>
  );
}

export default graphql(gql`
  query homePageQuery {
    something {
      nestedProperty
    }
  }
`)(Home);

I'd like to put together an Apollo guide at some point, but I'll have to figure out how to setup a mock server since I use CodeSandbox to host the demos.

loganpowell commented 6 years ago

You are incredible man! Thank you for the detailed follow up. I apologize for my naivety here, truly. I am a bit confused as to how to coordinate Apollo client's state management solution apollo-link-state with Curi. Basically, in Apollo 2.0 they've removed Redux in favor of managing all data (remote and local) so I assumed there would need to be some kind of consideration like you've outlined with the other state management libraries, but I assume you know this. As you can tell, I"m confused in general 😆

I.e., I'm not sure even how to ask my question and will just bite the bullet and - instead of trying to just use Curi in my existing setup - will do the Curi tutorial.

pshrmn commented 6 years ago

I think I see what you're talking about now. With the Redux and MobX packages, the router, response, and navigation are stored in the store, so you were asking about how to do that with Apollo, correct?

The basic answer is that it isn't actually necessary to store those in your store because you can always use the <Curious> component to inject them into components as props. That said (and without having actually tested this), I think you could write something like this:

const router = curi(history, routes);
const cache = new InMemoryCache(...);
const curiStateLink = withClientState({
  cache,
  resolvers: {
    Mutation: {
      updateResponse: (_, { response, navigation }, { cache }) => {
        const data = {
          responseState: {
            __typename: 'ResponseState',
            response,
            navigation
          },
        };
        cache.writeData({ data });
        return null
      },
    },
  }
});

Then, you would need to trigger an update in a response handler

const UPDATE_RESPONSE = gql`
  mutation updateResponse($response: Object, $navigation: Object) {
    updateResponse(response: $response, navigation: $navigation) @client
  }
`;

router.respond((response, navigation) => {
  client.mutate({
    mutation: UPDATE_RESPONSE,
    variables: { response, navigation }
  });
});

Now, that is half-baked because I didn't even include the router in the client state (and also because I don't know if it would even run). I'm also not sure how querying the state would work.

This is another thing that I wouldn't mind looking into eventually, but it will probably be a bit before I do. If you figure it out, please let me know.

loganpowell commented 6 years ago

Yep, you got it! This helps! I will give it a go with this guidance. I've got it on my todos to get the tutorial done tomorrow. Then I'll give this a go.

loganpowell commented 6 years ago

Have you ever heard of router5?

pshrmn commented 6 years ago

I have heard of and skimmed it, but I haven't tried it at all.

I'll probably take a look at Apollo integration soon. I have been making a number of updates that I think make things easier. The React API has slightly changed to what I think is a bit more "normal"; I still use a render function (you'll be seeing those a lot more in React in general), but I made it a children prop instead of render.

router.respond(() => {
  ReactDOM.render((
    <CuriProvider router={router}>
      {({ response }) => {
        const { body:Body } = response;
        return <Body response={response} />;
      }}
    </CuriProvider>
  ), holder);
});
pshrmn commented 6 years ago

I added a guide to using Curi + Apollo with what I think are the best practices.

Fixed with https://github.com/pshrmn/curi/commit/14d062a25d83b5770688b0afadd0dc9593da3f2c