Skip to content

Commit d359e82

Browse files
authored
chore: move state management to context (#73)
1 parent 689a66c commit d359e82

16 files changed

+217
-179
lines changed

src/components/App.js

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
import React from 'react';
22
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
3-
import { AppContextProvider } from './Context';
43
import Playground from './Playground';
54
import Layout from './Layout';
65
import Embedded from './Embedded';
76

87
function App() {
98
return (
10-
<AppContextProvider>
11-
<Router>
12-
<Switch>
13-
<Route path="/embed/">
14-
<Embedded />
15-
</Route>
16-
<Route path="/">
17-
<Layout>
18-
<Playground />
19-
</Layout>
20-
</Route>
21-
</Switch>
22-
</Router>
23-
</AppContextProvider>
9+
<Router>
10+
<Switch>
11+
<Route path="/embed/">
12+
<Embedded />
13+
</Route>
14+
<Route path="/">
15+
<Layout>
16+
<Playground />
17+
</Layout>
18+
</Route>
19+
</Switch>
20+
</Router>
2421
);
2522
}
2623

src/components/Context.js

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,68 @@
1-
import React, { useContext, useRef, useState } from 'react';
2-
1+
import React, { useContext, useReducer, useEffect } from 'react';
2+
import parser from '../parser';
3+
import { initialValues as defaultValues } from '../constants';
34
export const AppContext = React.createContext();
45

5-
function AppContextProvider(props) {
6-
const jsEditorRef = useRef();
7-
const htmlEditorRef = useRef();
8-
const [parsed, setParsed] = useState({});
9-
10-
return (
11-
<AppContext.Provider
12-
value={{
13-
jsEditorRef,
14-
htmlEditorRef,
15-
parsed,
16-
setParsed,
17-
}}
18-
{...props}
19-
/>
20-
);
6+
function reducer(state, action) {
7+
switch (action.type) {
8+
case 'SET_MARKUP_EDITOR': {
9+
return { ...state, markupEditor: action.editor };
10+
}
11+
12+
case 'SET_MARKUP': {
13+
if (action.updateEditor !== false) {
14+
state.markupEditor.setValue(action.markup);
15+
}
16+
17+
return {
18+
...state,
19+
markup: action.markup,
20+
result: parser.parse({ markup: action.markup, query: state.query }),
21+
};
22+
}
23+
24+
case 'SET_QUERY_EDITOR': {
25+
return { ...state, queryEditor: action.editor };
26+
}
27+
28+
case 'SET_QUERY': {
29+
if (action.updateEditor !== false) {
30+
state.queryEditor.setValue(action.query);
31+
}
32+
33+
return {
34+
...state,
35+
setQuery: action.query,
36+
result: parser.parse({ markup: state.markup, query: action.query }),
37+
};
38+
}
39+
40+
default: {
41+
throw new Error('Unknown action type: ' + action.type);
42+
}
43+
}
2144
}
2245

23-
function useAppContext() {
24-
return useContext(AppContext);
46+
export function PlaygroundProvider(props) {
47+
let { initialValues: { markup, query } = {} } = props;
48+
const result = parser.parse({ markup, query, cacheId: props.instanceId });
49+
50+
if (!markup && !query) {
51+
markup = defaultValues.markup;
52+
query = defaultValues.query;
53+
}
54+
55+
const [state, dispatch] = useReducer(reducer, { result, markup, query });
56+
57+
useEffect(() => {
58+
if (typeof props.onChange === 'function') {
59+
props.onChange(state);
60+
}
61+
}, [state.result]);
62+
63+
return <AppContext.Provider value={{ state, dispatch }} {...props} />;
2564
}
2665

27-
export { AppContextProvider, useAppContext };
66+
export function usePlayground() {
67+
return useContext(AppContext);
68+
}

src/components/Editor.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function getQueryHints(cm) {
7171
} else if (word.includes('.')) {
7272
// user is already one level deeper, entered `screen.get...`
7373
const [obj, method] = word.split('.');
74-
const values = suggestions[obj]?.filter((x) =>
74+
const values = (suggestions[obj] || []).filter((x) =>
7575
x.toLowerCase().includes(method),
7676
);
7777
list.push(...values);
@@ -142,10 +142,9 @@ const NON_TRIGGER_KEYS = {
142142
'222': 'quote',
143143
};
144144

145-
function Editor({ onChange, mode, initialValue }, forwardRef) {
145+
function Editor({ onLoad, onChange, mode, initialValue }) {
146146
const elem = useRef();
147-
const localRef = useRef();
148-
const editor = forwardRef || localRef;
147+
const editor = useRef();
149148

150149
useEffect(() => {
151150
editor.current = CodeMirror.fromTextArea(
@@ -187,6 +186,8 @@ function Editor({ onChange, mode, initialValue }, forwardRef) {
187186
});
188187
}
189188
});
189+
190+
onLoad(editor.current);
190191
}, [editor.current, onChange]);
191192

192193
return (
@@ -196,4 +197,4 @@ function Editor({ onChange, mode, initialValue }, forwardRef) {
196197
);
197198
}
198199

199-
export default React.forwardRef(Editor);
200+
export default Editor;

src/components/Embedded.js

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import React, { useEffect } from 'react';
22
import { useLocation } from 'react-router-dom';
33
import queryString from 'query-string';
4-
5-
import usePlayground from '../hooks/usePlayground';
6-
import { initialValues } from '../constants';
74
import state from '../lib/state';
85

96
import Preview from './Preview';
107
import Query from './Query';
118
import Result from './Result';
129
import MarkupEditor from './MarkupEditor';
10+
import { PlaygroundProvider } from './Context';
11+
12+
function onStateChange({ markup, query, result }) {
13+
state.save({ markup, query });
14+
state.updateTitle(result?.expression?.expression);
15+
}
1316

14-
const savedState = state.load();
17+
const initialValues = state.load();
1518

1619
const SUPPORTED_PANES = {
1720
markup: true,
@@ -22,16 +25,6 @@ const SUPPORTED_PANES = {
2225

2326
// TODO: we should support readonly mode
2427
function Embedded() {
25-
const [query, setQuery, markup, setMarkup, parsed] = usePlayground({
26-
initialMarkup: initialValues.html || savedState.markup,
27-
initialQuery: initialValues.js || savedState.js,
28-
});
29-
30-
useEffect(() => {
31-
state.save({ markup, query });
32-
state.updateTitle(parsed?.expression?.expression);
33-
}, [markup, query]);
34-
3528
const location = useLocation();
3629
const params = queryString.parse(location.search);
3730

@@ -60,32 +53,26 @@ function Embedded() {
6053
}, []);
6154

6255
return (
63-
<div
64-
className={`h-full overflow-hidden grid grid-flow-col gap-4 p-4 bg-white shadow rounded ${columnClass}`}
65-
>
66-
{panes.map((area) => {
67-
switch (area) {
68-
case 'preview':
69-
return <Preview key={area} html={markup} />;
70-
case 'markup':
71-
return (
72-
<MarkupEditor
73-
key={area}
74-
onChange={setMarkup}
75-
initialValue={markup}
76-
/>
77-
);
78-
case 'query':
79-
return (
80-
<Query key={area} initialValue={query} onChange={setQuery} />
81-
);
82-
case 'result':
83-
return <Result key={area} />;
84-
default:
85-
return null;
86-
}
87-
})}
88-
</div>
56+
<PlaygroundProvider onChange={onStateChange} initialValues={initialValues}>
57+
<div
58+
className={`h-full overflow-hidden grid grid-flow-col gap-4 p-4 bg-white shadow rounded ${columnClass}`}
59+
>
60+
{panes.map((area) => {
61+
switch (area) {
62+
case 'preview':
63+
return <Preview key={area} />;
64+
case 'markup':
65+
return <MarkupEditor key={area} />;
66+
case 'query':
67+
return <Query key={area} />;
68+
case 'result':
69+
return <Result key={area} />;
70+
default:
71+
return null;
72+
}
73+
})}
74+
</div>
75+
</PlaygroundProvider>
8976
);
9077
}
9178

src/components/MarkupEditor.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
1-
import React from 'react';
1+
import React, { useCallback } from 'react';
22
import Editor from './Editor';
3+
import { usePlayground } from './Context';
4+
5+
function MarkupEditor() {
6+
const { dispatch, state } = usePlayground();
7+
8+
const onLoad = useCallback(
9+
(editor) => dispatch({ type: 'SET_MARKUP_EDITOR', editor }),
10+
[dispatch],
11+
);
12+
13+
const onChange = useCallback(
14+
(markup) => dispatch({ type: 'SET_MARKUP', markup, updateEditor: false }),
15+
[dispatch],
16+
);
317

4-
function MarkupEditor({ initialValue, onChange }) {
518
return (
619
<div className="h-full w-full flex flex-col">
720
<div className="markup-editor flex-auto relative overflow-hidden">
8-
<Editor mode="html" initialValue={initialValue} onChange={onChange} />
21+
<Editor
22+
mode="html"
23+
initialValue={state.markup}
24+
onLoad={onLoad}
25+
onChange={onChange}
26+
/>
927
</div>
1028
</div>
1129
);

src/components/Playground.js

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,46 @@
1-
import React, { useEffect } from 'react';
2-
3-
import usePlayground from '../hooks/usePlayground';
4-
import state from '../lib/state';
5-
import { initialValues } from '../constants';
1+
import React from 'react';
62

73
import Preview from './Preview';
84
import MarkupEditor from './MarkupEditor';
95
import Result from './Result';
106
import Query from './Query';
7+
import { PlaygroundProvider } from './Context';
8+
import state from '../lib/state';
119

12-
const savedState = state.load();
13-
14-
function Playground() {
15-
const [query, setQuery, markup, setMarkup, parsed] = usePlayground({
16-
initialMarkup: initialValues.html || savedState.markup,
17-
initialQuery: initialValues.js || savedState.js,
18-
});
10+
function onStateChange({ markup, query, result }) {
11+
state.save({ markup, query });
12+
state.updateTitle(result?.expression?.expression);
13+
}
1914

20-
useEffect(() => {
21-
state.save({ markup, query });
22-
state.updateTitle(parsed?.expression?.expression);
23-
}, [markup, query]);
15+
const initialValues = state.load();
2416

17+
function Playground() {
2518
return (
26-
<div className="flex flex-col h-auto md:h-full w-full">
27-
<div className="editor markup-editor gap-4 md:gap-8 md:h-56 flex-auto grid-cols-1 md:grid-cols-2">
28-
<div className="flex-auto relative h-56 md:h-full">
29-
<MarkupEditor onChange={setMarkup} initialValue={markup} />
19+
<PlaygroundProvider onChange={onStateChange} initialValues={initialValues}>
20+
<div className="flex flex-col h-auto md:h-full w-full">
21+
<div className="editor markup-editor gap-4 md:gap-8 md:h-56 flex-auto grid-cols-1 md:grid-cols-2">
22+
<div className="flex-auto relative h-56 md:h-full">
23+
<MarkupEditor />
24+
</div>
25+
26+
<div className="flex-auto h-56 md:h-full">
27+
<Preview />
28+
</div>
3029
</div>
3130

32-
<div className="flex-auto h-56 md:h-full">
33-
<Preview html={markup} />
34-
</div>
35-
</div>
31+
<div className="flex-none h-8" />
3632

37-
<div className="flex-none h-8" />
38-
39-
<div className="editor gap-4 md:gap-8 md:h-56 flex-auto grid-cols-1 md:grid-cols-2 overflow-hidden">
40-
<div className="flex-auto relative h-56 md:h-full">
41-
<Query onChange={setQuery} initialValue={query} />
42-
</div>
33+
<div className="editor gap-4 md:gap-8 md:h-56 flex-auto grid-cols-1 md:grid-cols-2 overflow-hidden">
34+
<div className="flex-auto relative h-56 md:h-full">
35+
<Query />
36+
</div>
4337

44-
<div className="flex-auto h-56 md:h-full overflow-hidden">
45-
<Result />
38+
<div className="flex-auto h-56 md:h-full overflow-hidden">
39+
<Result />
40+
</div>
4641
</div>
4742
</div>
48-
</div>
43+
</PlaygroundProvider>
4944
);
5045
}
5146

0 commit comments

Comments
 (0)