Skip to content
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

WIP: feat: Add migration context to enable GraphiQL refactoring #1380

Merged
merged 3 commits into from
Feb 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 69 additions & 37 deletions packages/graphiql/src/components/GraphiQL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ import {
introspectionQueryName,
introspectionQuerySansSubscriptions,
} from '../utility/introspectionQueries';
import {
MigrationContextProvider,
MigrationContext,
} from 'src/state/MigrationContext';

const DEFAULT_DOC_EXPLORER_WIDTH = 350;

Expand All @@ -63,7 +67,7 @@ if (majorVersion < 16) {
}

declare namespace global {
export let g: GraphiQL;
export let g: GraphiQLInternals;
}

export type Maybe<T> = T | null | undefined;
Expand Down Expand Up @@ -134,21 +138,70 @@ type GraphiQLState = {
*
* @see https://github.com/graphql/graphiql#usage
*/
export class GraphiQL extends React.Component<GraphiQLProps, GraphiQLState> {
/**
* Static Methods
*/
static formatResult(result: any) {
return JSON.stringify(result, null, 2);
}
export const GraphiQL: React.FC<GraphiQLProps> &
GraphiQLStaticProperties = props => {
return (
<MigrationContextProvider>
<GraphiQLInternals {...props} />
</MigrationContextProvider>
);
};
Comment on lines +141 to +148
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this and the changes that follow are a bit ugly. I'm trying to preserve the api without requiring an extra wrapper which required some... creativity.


static formatError(rawError: Error) {
const result = Array.isArray(rawError)
? rawError.map(formatSingleError)
: formatSingleError(rawError);
return JSON.stringify(result, null, 2);
}
interface GraphiQLStaticProperties {
formatResult: (result: any) => string;
formatError: (rawError: Error) => string;
Logo: typeof GraphiQLLogo;
Toolbar: typeof GraphiQLToolbar;
Footer: typeof GraphiQLFooter;
QueryEditor: typeof QueryEditor;
VariableEditor: typeof VariableEditor;
ResultViewer: typeof ResultViewer;
Button: typeof ToolbarButton;
ToolbarButton: typeof ToolbarButton;
Group: typeof ToolbarGroup;
Menu: typeof ToolbarMenu;
MenuItem: typeof ToolbarMenuItem;
}
Comment on lines +150 to +164
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotta be a better way to do this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're about to drop this interface for 1.0.0 so don't worry about it too much:
#1165


GraphiQL.formatResult = (result: any) => {
return JSON.stringify(result, null, 2);
};

GraphiQL.formatError = (rawError: Error) => {
const result = Array.isArray(rawError)
? rawError.map(formatSingleError)
: formatSingleError(rawError);
return JSON.stringify(result, null, 2);
};

// Export main windows/panes to be used separately if desired.
GraphiQL.Logo = GraphiQLLogo;
GraphiQL.Toolbar = GraphiQLToolbar;
GraphiQL.Footer = GraphiQLFooter;
GraphiQL.QueryEditor = QueryEditor;
GraphiQL.VariableEditor = VariableEditor;
GraphiQL.ResultViewer = ResultViewer;

// Add a button to the Toolbar.
GraphiQL.Button = ToolbarButton;
GraphiQL.ToolbarButton = ToolbarButton; // Don't break existing API.

// Add a group of buttons to the Toolbar
GraphiQL.Group = ToolbarGroup;

// Add a menu of items to the Toolbar.
GraphiQL.Menu = ToolbarMenu;
GraphiQL.MenuItem = ToolbarMenuItem;

// Add a select-option input to the Toolbar.
// GraphiQL.Select = ToolbarSelect;
// GraphiQL.SelectOption = ToolbarSelectOption;

/**
* GraphiQL implementation details, intended to only be used via
* the GraphiQL component
*/
class GraphiQLInternals extends React.Component<GraphiQLProps, GraphiQLState> {
// Ensure only the last executed editor query is rendered.
_editorQueryID = 0;
_storage: StorageAPI;
Expand Down Expand Up @@ -583,29 +636,6 @@ export class GraphiQL extends React.Component<GraphiQLProps, GraphiQLState> {
);
}

// Export main windows/panes to be used separately if desired.
static Logo = GraphiQLLogo;
static Toolbar = GraphiQLToolbar;
static Footer = GraphiQLFooter;
static QueryEditor = QueryEditor;
static VariableEditor = VariableEditor;
static ResultViewer = ResultViewer;

// Add a button to the Toolbar.
static Button = ToolbarButton;
static ToolbarButton = ToolbarButton; // Don't break existing API.

// Add a group of buttons to the Toolbar
static Group = ToolbarGroup;

// Add a menu of items to the Toolbar.
static Menu = ToolbarMenu;
static MenuItem = ToolbarMenuItem;

// Add a select-option input to the Toolbar.
// static Select = ToolbarSelect;
// static SelectOption = ToolbarSelectOption;

/**
* Get the query editor CodeMirror instance.
*
Expand Down Expand Up @@ -1266,6 +1296,8 @@ export class GraphiQL extends React.Component<GraphiQLProps, GraphiQLState> {
};
}

GraphiQLInternals.contextType = MigrationContext;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the magic that enables this.context.schema inside the class component.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yes, i remember it used to work this way with the old context API too, amazing how little the class component interface changed


// // Configure the UI by providing this Component as a child of GraphiQL.
function GraphiQLLogo<TProps>(props: PropsWithChildren<TProps>) {
return (
Expand Down
2 changes: 1 addition & 1 deletion packages/graphiql/src/state/GraphiQLSchemaProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const schemaReducer: SchemaReducer = (
*/

export type SchemaProviderProps = {
config: SchemaConfig;
config?: SchemaConfig;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This already provided a default so it seems it'd be fine to have as optional.

schemaLoader?: typeof defaultSchemaLoader;
children?: any;
};
Expand Down
34 changes: 34 additions & 0 deletions packages/graphiql/src/state/MigrationContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
SchemaContext,
SchemaProvider,
SchemaProviderProps,
} from './GraphiQLSchemaProvider';
import React, { useContext, useRef } from 'react';

export const MigrationContext = React.createContext({});

// To add a new context...

const AggregateContext: React.FC = ({ children }) => {
const schemaContext = useContext(SchemaContext); // 1. consume the context via useContext
const contexts = useRef({ schema: schemaContext }); // 2. add it to the migration's aggregate context

return (
<MigrationContext.Provider value={contexts.current}>
{children}
</MigrationContext.Provider>
);
};

export const MigrationContextProvider: React.FC<SchemaProviderProps> = ({
// 3. Union its props here
children,
...props
}) => {
return (
// 4. Wrap it around this section
<SchemaProvider {...props}>
<AggregateContext>{children}</AggregateContext>
</SchemaProvider>
);
};