Skip to content

Commit

Permalink
add useQueryEditor hook to @graphiql/react (#2408)
Browse files Browse the repository at this point in the history
* move query editor logic to @graphiql/react

* avoid type assertion

* avoid using class methods in e2e tests

* fix test block description

* mock codemirror hooks from @graphiql/react for tests

* add changeset

* fix build order

* avoid recreating mocked codemirror textarea

* avoid race conditons in async effects
  • Loading branch information
thomasheyenbrock authored May 18, 2022
1 parent bc3dc64 commit d825bb7
Show file tree
Hide file tree
Showing 26 changed files with 595 additions and 404 deletions.
6 changes: 6 additions & 0 deletions .changeset/moody-tigers-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'graphiql': minor
'@graphiql/react': minor
---

Move the logic of the query editor from the `graphiql` package into a hook `useQueryEditor` provided by `@graphiql/react`
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
"npm": "please_use_yarn_instead"
},
"scripts": {
"build": "yarn workspace @graphiql/react run build && yarn tsc --clean && yarn tsc",
"build": "yarn build:clean && yarn build:packages && yarn build:graphiql-react && yarn build:graphiql",
"build-bundles": "yarn prebuild-bundles && yarn workspace graphiql run build-bundles",
"build-bundles-clean": "rimraf '{packages,examples,plugins}/**/{bundle,cdn,webpack}' && yarn workspace graphiql run build-bundles-clean",
"build-clean": "wsrun build-clean",
"build-demo": "wsrun build-demo",
"build-docs": "rimraf 'packages/graphiql/typedoc' && typedoc 'packages'",
"build:clean": "yarn tsc --clean",
"build:clean": "yarn tsc --clean && yarn tsc --clean resources/tsconfig.graphiql.json",
"build:graphiql": "yarn tsc resources/tsconfig.graphiql.json",
"build:graphiql-react": "yarn workspace @graphiql/react run build",
"build:packages": "yarn tsc",
"build:watch": "yarn tsc --watch",
"watch": "yarn build:watch",
"watch-vscode": "concurrently --raw 'yarn tsc --watch' 'yarn workspace vscode-graphql run compile --watch'",
Expand Down
5 changes: 5 additions & 0 deletions packages/graphiql-react/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const base = require('../../jest.config.base')(__dirname);

module.exports = {
...base,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { invalidCharacters, normalizeWhitespace } from '../whitespace';

describe('normalizeWhitespace', () => {
it('removes unicode characters', () => {
const result = normalizeWhitespace(invalidCharacters.join(''));
expect(result).toEqual(' '.repeat(invalidCharacters.length));
});
});
8 changes: 3 additions & 5 deletions packages/graphiql-react/src/editor/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import {
GraphQLField,
} from 'graphql';
import escapeHTML from 'escape-html';
import MD from 'markdown-it';
import { importCodeMirror } from './common';

const md = new MD();
import { markdown } from '../markdown';

/**
* Render a custom UI for CodeMirror's hint which includes additional info
Expand Down Expand Up @@ -63,7 +61,7 @@ export default function onHasCompletion(

// Now that the UI has been set up, add info to information.
const description = ctx.description
? md.render(ctx.description)
? markdown.render(ctx.description)
: 'Self descriptive.';
const type = ctx.type
? '<span class="infoType">' + renderType(ctx.type) + '</span>'
Expand All @@ -78,7 +76,7 @@ export default function onHasCompletion(

if (ctx && deprecation && ctx.deprecationReason) {
const reason = ctx.deprecationReason
? md.render(ctx.deprecationReason)
? markdown.render(ctx.deprecationReason)
: '';
deprecation.innerHTML =
'<span class="deprecation-label">Deprecated</span>' + reason;
Expand Down
18 changes: 13 additions & 5 deletions packages/graphiql-react/src/editor/context.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import type { Editor } from 'codemirror';
import { createContext, ReactNode, useState } from 'react';

import { CodeMirrorEditor } from './types';

export type EditorContextType = {
headerEditor: Editor | null;
setHeaderEditor(newEditor: Editor): void;
headerEditor: CodeMirrorEditor | null;
queryEditor: CodeMirrorEditor | null;
setHeaderEditor(newEditor: CodeMirrorEditor): void;
setQueryEditor(newEditor: CodeMirrorEditor): void;
};

export const EditorContext = createContext<EditorContextType>({
headerEditor: null,
queryEditor: null,
setHeaderEditor() {},
setQueryEditor() {},
});

export function EditorContextProvider(props: {
children: ReactNode;
initialValue?: string;
}) {
const [editor, setEditor] = useState<Editor | null>(null);
const [headerEditor, setHeaderEditor] = useState<CodeMirrorEditor | null>(
null,
);
const [queryEditor, setQueryEditor] = useState<CodeMirrorEditor | null>(null);
return (
<EditorContext.Provider
value={{ headerEditor: editor, setHeaderEditor: setEditor }}>
value={{ headerEditor, queryEditor, setHeaderEditor, setQueryEditor }}>
{props.children}
</EditorContext.Provider>
);
Expand Down
11 changes: 11 additions & 0 deletions packages/graphiql-react/src/editor/header-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,17 @@ export function useHeaderEditor({
const { headerEditor, setHeaderEditor } = context;

useEffect(() => {
let isActive = true;

importCodeMirror([
// @ts-expect-error
import('codemirror/mode/javascript/javascript'),
]).then(CodeMirror => {
// Don't continue if the effect has already been cleaned up
if (!isActive) {
return;
}

const container = ref.current;
if (!container) {
return;
Expand Down Expand Up @@ -99,6 +106,10 @@ export function useHeaderEditor({

setHeaderEditor(newEditor);
});

return () => {
isActive = false;
};
}, [editorTheme, readOnly, setHeaderEditor]);

useSynchronizeValue(headerEditor, value);
Expand Down
20 changes: 12 additions & 8 deletions packages/graphiql-react/src/editor/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Editor, EditorChange } from 'codemirror';
import { EditorChange } from 'codemirror';
import { RefObject, useEffect, useRef } from 'react';

import onHasCompletion from './completion';
import { CodeMirrorEditor } from './types';

export function useSynchronizeValue(
editor: Editor | null,
editor: CodeMirrorEditor | null,
value: string | undefined,
) {
useEffect(() => {
Expand All @@ -19,15 +20,15 @@ export function useSynchronizeValue(
export type EditCallback = (value: string) => void;

export function useChangeHandler(
editor: Editor | null,
editor: CodeMirrorEditor | null,
callback: EditCallback | undefined,
) {
useEffect(() => {
if (!editor) {
return;
}

const handleChange = (editorInstance: Editor) => {
const handleChange = (editorInstance: CodeMirrorEditor) => {
const newValue = editorInstance.getValue();
callback?.(newValue);
};
Expand All @@ -39,12 +40,15 @@ export function useChangeHandler(
export type CompletionCallback = (value: HTMLDivElement) => void;

export function useCompletion(
editor: Editor | null,
editor: CodeMirrorEditor | null,
callback: CompletionCallback | undefined,
) {
useEffect(() => {
if (editor && callback) {
const handleCompletion = (instance: Editor, changeObj?: EditorChange) => {
const handleCompletion = (
instance: CodeMirrorEditor,
changeObj?: EditorChange,
) => {
onHasCompletion(instance, changeObj, callback);
};
editor.on(
Expand All @@ -65,7 +69,7 @@ export function useCompletion(
export type EmptyCallback = () => void;

export function useKeyMap(
editor: Editor | null,
editor: CodeMirrorEditor | null,
keys: string[],
callback: EmptyCallback | undefined,
) {
Expand All @@ -88,7 +92,7 @@ export function useKeyMap(
}

export function useResizeEditor(
editor: Editor | null,
editor: CodeMirrorEditor | null,
ref: RefObject<HTMLDivElement>,
) {
const sizeRef = useRef<number>();
Expand Down
11 changes: 9 additions & 2 deletions packages/graphiql-react/src/editor/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { EditorContext, EditorContextProvider } from './context';
import { useHeaderEditor } from './header-editor';
import { useQueryEditor } from './query-editor';

import type { EditorContextType } from './context';
import type { UseHeaderEditorArgs } from './header-editor';
import type { UseQueryEditorArgs } from './query-editor';

export { EditorContext, EditorContextProvider, useHeaderEditor };
export {
EditorContext,
EditorContextProvider,
useHeaderEditor,
useQueryEditor,
};

export type { EditorContextType, UseHeaderEditorArgs };
export type { EditorContextType, UseHeaderEditorArgs, UseQueryEditorArgs };
Loading

0 comments on commit d825bb7

Please sign in to comment.