Skip to content

Instantly share code, notes, and snippets.

@AlecAivazis
Last active June 10, 2019 16:38
Show Gist options
  • Save AlecAivazis/9c8a05302ee39a493c9aa5a813c1fc8e to your computer and use it in GitHub Desktop.
Save AlecAivazis/9c8a05302ee39a493c9aa5a813c1fc8e to your computer and use it in GitHub Desktop.

Proposal

I think we should approach this from the perspective of two discrete packages: graphiql and graphql-ide. Doing this allows us to design different APIs that cater to the distinct sets of users would be interested in using graphiql directly. The break down of the two libraries is outlined below with motivations further down.

The actual names and API here couldn't be further from final. They were just what came to mind first.

GraphiQL

This library is meant for simple users who have already bought into the GraphiQL experience and are looking to customize it. These customizations would include things like a custom fetcher, themes, etc. They explicitly do not include any modification of the internal UI. If the user cares about modifying the internals of the UI they should go to graphql-ide. If/when plugins and presets exist, they are implemented here - not in graphql-ide.

import GraphiQL from "graphiql";

function fetcher(...args) {
  // do something
}

const App = () => (
  <GraphiQL fetcher={fetcher} prettyPrintResult={false} theme={myTheme} />
);
import GraphiQL from "graphiql";
import { css } from "glamor";

const App = () => {
  function fetcher(...args) {
    // do something
  }

  return (
    <FlexColumn>
      {/* every element corresponds to an HTML element and can be styled with your favorite library */}
      <GraphiQL
        fetcher={fetcher}
        {...css({
          marginBottom: 20
        })}
      />
      <toolbar>
        <div>awesome toolbar content that modifies the fetcher</div>
      </toolbar>
    </FlexColumn>
  );
};

GraphQL-IDE

This library is designed for someone building a custom GraphQL IDE.

This first example is a bare bones graphql IDE with an editor, a place for the response, and a button to send the query. We'd also provide default implementations of things like the schema explorer but since this UI does not use them, they do not show up. In this example (assuming no default behaviors), the submit button is always enabled - even when the query has a validation error.

import { QueryEditor, Response, Container, SubmitButton } from "graphql-ide";

const App = () => (
  <Container>
    <FlexRow>
      <SubmitButton />
    </FlexRow>
    <FlexRow>
      <QueryEditor />
      <Response />
    </FlexRow>
  </Container>
);

We would also provide some hooks for fancy users. Here is what it would look like to explicitly disable that submit button when the query isn't valid:

import {
  QueryEditor,
  Response,
  Container,
  SubmitButton,
  useQueryState,
  useSchema
} from "graphql-ide";

const App = () => {
  // grab the current query from the context
  const { valid: queryIsValid } = useQueryState();

  return (
    <>
      <FlexRow>
        <SubmitButton disabled={!queryIsValid} />
      </FlexRow>
      <FlexRow>
        <QueryEditor />
        <Response />
      </FlexRow>
    </>
  );
};

export default props => (
  <Container>
    <App {...props} />
  </Container>
);

You can see how someone would slowly build up a custom environment including as much as they wanted. Here's an example with the default schema explorer. Note, graphql-ide would not provide a Drawer component, that's up to the user.

import {
  QueryEditor,
  Response,
  Container,
  SubmitButton,
  SchemaExplorer,
  useQueryState,
  useSchema
} from "graphql-ide";
import { Drawer } from "my-favorite-component-library";

function App() {
  // grab the current query from the context
  const { valid: queryIsValid } = useQueryState();
  // keep track of the drawer state
  const [drawerOpen, setDrawerOpen] = useState(false);

  return (
    <>
      <FlexRow>
        <SubmitButton disabled={!queryIsValid} />
      </FlexRow>
      <FlexRow>
        <QueryEditor />
        <Response />
      </FlexRow>
      <Drawer open={drawerOpen} setOpen={setDrawerOpen}>
        <SchemaExplorer />
      </Drawer>
    </>
  );
}

export default () => (
  <Container>
    <App />
  </Container>
);

Users could build a custom SchemaExplorer with hooks that graphql-ide would provide:

import { useSchemaExplorer } from 'graphql-ide'

function CustomSchemaExplorer() {
    // load the schema explorer utility
    const schema = useSchemaExplorer()

    // track the name of the type that we are looking at
    const [name, setName] = useState(null)


    // the type we are highlighting in the explorer
    const type = schema.getType(name)


    return (
        <>
            <h2>{type.name}</h1>
            {type.fields.map(field => (
                <div>
                    <h3>{field.name}</h3>
                    <button onClick={() => setName(field.type.name)}>{field.Type}</button>
                </div>
            ))}
        </>
    )
}

Notes:

  • fetcher is probably a prop to SubmitButton which handles updating the state internally

Motivations

  • Everyone component should correspond with an HTML element with props passed through so that users can style it however they want
  • The current API provides ways to add components that are outside of graphiql's domain.
    • look at how many components in the documentation refer to something "above graphiql" or "below graphiql"
    • If a user wants to put a toolbar above graphiql, they should do it like
      • <FlexColumn><toolbar /><GraphiQL/></FlexColumn>
  • Could differentiate the two users (see below) with <GraphiQL> /> vs <GraphiQL.Result />
    • You'd never see <GraphiQL/> in a codebase with a custom IDE
  • How would designers customize a GraphQL environment? What would they change about GraphiQL?
    • rearrange UI
    • hide pieces of UI
    • replace pieces of UI with their own
    • build UI that changes how different things work
      • the Headers section in Playground
  • What state does GraphiQL maintain?
    • What is core to an IDE?
      • the query
        • just a string?
        • Parsed string would be nice too but we can make the user parse it if they want to do something fancy
          • don't want to be responsible for the performance of the parser
          • "fancy" here is things like "know if the query has a syntax or validation error"
      • the result
        • loading state
        • the JSON response
      • the schema
        • utilities for traversing this should be made available
    • What is not core to GraphQL but part of the experience?
      • The query that is stored in local storage
      • The history
      • Wether the query is valid or not (if the submit button is enabled)
      • Wether the schema explorer is open or not

Train Station

GraphiQL Users

  • There are two kinds of users
    • People who want to drop in a GraphiQL component so they can provide a custom fetcher (aka "simple")
    • People who want to build custom IDEs for GraphiQL and can be expected to take more ownership over bits of state and what not (aka "advanced")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment