Skip to content

Commit 9f82691

Browse files
refactor(web): add HTTP Editor Context and eliminate prop drilling (#115)
* refactor(web): add HTTP Editor Context and eliminate prop drilling * fix(web): address code review feedback on context implementation and component structure - Fix createSimpleContext to use JSX syntax for proper SolidJS reactivity - Remove dead code guard clause from ScriptEditorWithExecution - Move HttpRequestEditorContext to shared context/ directory to eliminate cross-module coupling - Use createMemo for selectedRequest accessor consistency - Fix relative import paths after file relocation
1 parent f265652 commit 9f82691

File tree

5 files changed

+80
-125
lines changed

5 files changed

+80
-125
lines changed

packages/web/src/components/editor/HttpEditorWithExecution.tsx

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type Component, createEffect, createSignal, on, onCleanup, Show } from 'solid-js';
2-
import { useConnection, useObserver, useWorkspace } from '../../context';
2+
import { HttpRequestEditorProvider, useConnection, useObserver, useWorkspace } from '../../context';
33
import { useEditorPanelState } from '../../hooks/useEditorPanelState';
44
import { useHttpRequestWorkspace } from '../../hooks/useHttpRequestWorkspace';
55
import { ExecutionDetail } from '../execution/ExecutionDetail';
@@ -116,7 +116,7 @@ export const HttpEditorWithExecution: Component<HttpEditorWithExecutionProps> =
116116
});
117117

118118
return (
119-
<>
119+
<HttpRequestEditorProvider store={httpWorkspace}>
120120
<RequestSelectorBar
121121
requests={httpWorkspace.requests.all()}
122122
selectedIndex={httpWorkspace.selection.index()}
@@ -135,42 +135,6 @@ export const HttpEditorWithExecution: Component<HttpEditorWithExecutionProps> =
135135
<RequestWorkspaceTabs
136136
activeTab={activeRequestTab()}
137137
onTabChange={setActiveRequestTab}
138-
selectedRequest={httpWorkspace.selection.selected()}
139-
requestCount={httpWorkspace.requests.count()}
140-
requestHeaders={httpWorkspace.drafts.header.draftHeaders()}
141-
requestBodySummary={httpWorkspace.drafts.parse.bodySummary()}
142-
requestBodyDraft={httpWorkspace.drafts.body.draftBody()}
143-
requestBodyFormDataDraft={httpWorkspace.drafts.body.draftFormData()}
144-
requestBodyFilePathDraft={httpWorkspace.drafts.body.draftFilePath()}
145-
requestDetailsLoading={httpWorkspace.drafts.parse.loading()}
146-
requestDetailsError={httpWorkspace.drafts.parse.error()}
147-
headerDraftDirty={httpWorkspace.drafts.header.isDirty()}
148-
headerDraftSaving={httpWorkspace.drafts.header.isSaving()}
149-
headerDraftSaveError={httpWorkspace.drafts.header.saveError()}
150-
onHeaderChange={httpWorkspace.drafts.header.onHeaderChange}
151-
onAddHeader={httpWorkspace.drafts.header.onAddHeader}
152-
onRemoveHeader={httpWorkspace.drafts.header.onRemoveHeader}
153-
onSaveHeaders={httpWorkspace.drafts.header.onSave}
154-
onDiscardHeaders={httpWorkspace.drafts.header.onDiscard}
155-
bodyDraftDirty={httpWorkspace.drafts.body.isDirty()}
156-
bodyDraftSaving={httpWorkspace.drafts.body.isSaving()}
157-
bodyDraftSaveError={httpWorkspace.drafts.body.saveError()}
158-
bodyDraftValidationError={httpWorkspace.drafts.body.validationError()}
159-
bodyDraftIsJsonEditable={httpWorkspace.drafts.body.isJsonBody()}
160-
bodyDraftTemplateWarnings={httpWorkspace.drafts.body.templateWarnings()}
161-
onBodyChange={httpWorkspace.drafts.body.onBodyChange}
162-
onBodyFilePathChange={httpWorkspace.drafts.body.onFilePathChange}
163-
onBodyFormDataNameChange={httpWorkspace.drafts.body.onFormDataNameChange}
164-
onBodyFormDataTypeChange={httpWorkspace.drafts.body.onFormDataTypeChange}
165-
onBodyFormDataValueChange={httpWorkspace.drafts.body.onFormDataValueChange}
166-
onBodyFormDataFilenameChange={httpWorkspace.drafts.body.onFormDataFilenameChange}
167-
onBodyFormDataAddField={httpWorkspace.drafts.body.onAddFormDataField}
168-
onBodyFormDataRemoveField={httpWorkspace.drafts.body.onRemoveFormDataField}
169-
onBodyPrettify={httpWorkspace.drafts.body.onBodyPrettify}
170-
onBodyMinify={httpWorkspace.drafts.body.onBodyMinify}
171-
onBodyCopy={() => void httpWorkspace.drafts.body.onBodyCopy()}
172-
onSaveBody={httpWorkspace.drafts.body.onSave}
173-
onDiscardBody={httpWorkspace.drafts.body.onDiscard}
174138
/>
175139
</div>
176140
}
@@ -193,7 +157,7 @@ export const HttpEditorWithExecution: Component<HttpEditorWithExecutionProps> =
193157
onCollapseChange={panelState.setCollapsed}
194158
/>
195159
</div>
196-
</>
160+
</HttpRequestEditorProvider>
197161
);
198162
};
199163

packages/web/src/components/request-workspace/request-workspace-tabs.tsx

Lines changed: 56 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import { createMemo, For, Match, Show, Switch } from 'solid-js';
2+
import { useHttpRequestEditor } from '../../context';
23
import type { WorkspaceRequest } from '../../sdk';
3-
import type {
4-
RequestBodyField,
5-
RequestBodySummary,
6-
RequestDetailsRow
7-
} from '../../utils/request-details';
84
import { toRequestParams } from '../../utils/request-details';
95
import { REQUEST_WORKSPACE_TABS, type RequestWorkspaceTabId } from './model';
106
import {
@@ -16,42 +12,6 @@ import {
1612
interface RequestWorkspaceTabsProps {
1713
activeTab: RequestWorkspaceTabId;
1814
onTabChange: (tab: RequestWorkspaceTabId) => void;
19-
selectedRequest?: WorkspaceRequest;
20-
requestCount: number;
21-
requestHeaders: RequestDetailsRow[];
22-
requestBodySummary: RequestBodySummary;
23-
requestBodyDraft: string;
24-
requestBodyFormDataDraft: RequestBodyField[];
25-
requestBodyFilePathDraft: string;
26-
requestDetailsLoading: boolean;
27-
requestDetailsError?: string;
28-
headerDraftDirty: boolean;
29-
headerDraftSaving: boolean;
30-
headerDraftSaveError?: string;
31-
onHeaderChange: (index: number, field: 'key' | 'value', value: string) => void;
32-
onAddHeader: () => void;
33-
onRemoveHeader: (index: number) => void;
34-
onSaveHeaders: () => void;
35-
onDiscardHeaders: () => void;
36-
bodyDraftDirty: boolean;
37-
bodyDraftSaving: boolean;
38-
bodyDraftSaveError?: string;
39-
bodyDraftValidationError?: string;
40-
bodyDraftIsJsonEditable: boolean;
41-
bodyDraftTemplateWarnings: string[];
42-
onBodyChange: (value: string) => void;
43-
onBodyFilePathChange: (value: string) => void;
44-
onBodyFormDataNameChange: (index: number, value: string) => void;
45-
onBodyFormDataTypeChange: (index: number, isFile: boolean) => void;
46-
onBodyFormDataValueChange: (index: number, value: string) => void;
47-
onBodyFormDataFilenameChange: (index: number, value: string) => void;
48-
onBodyFormDataAddField: () => void;
49-
onBodyFormDataRemoveField: (index: number) => void;
50-
onBodyPrettify: () => void;
51-
onBodyMinify: () => void;
52-
onBodyCopy: () => void;
53-
onSaveBody: () => void;
54-
onDiscardBody: () => void;
5515
}
5616

5717
const TAB_LABELS: Record<RequestWorkspaceTabId, string> = {
@@ -61,14 +21,21 @@ const TAB_LABELS: Record<RequestWorkspaceTabId, string> = {
6121
};
6222

6323
export function RequestWorkspaceTabs(props: RequestWorkspaceTabsProps) {
24+
const httpWorkspace = useHttpRequestEditor();
25+
6426
const requestParams = createMemo(() => {
65-
const request = props.selectedRequest;
27+
const request = httpWorkspace.selection.selected();
6628
if (!request) {
6729
return [];
6830
}
6931
return toRequestParams(request.url);
7032
});
7133

34+
const selectedRequest = createMemo((): WorkspaceRequest | undefined =>
35+
httpWorkspace.selection.selected()
36+
);
37+
const requestCount = (): number => httpWorkspace.requests.count();
38+
7239
return (
7340
<section
7441
class="border-b border-treq-border-light dark:border-treq-dark-border-light bg-base-100/80"
@@ -79,7 +46,7 @@ export function RequestWorkspaceTabs(props: RequestWorkspaceTabsProps) {
7946
Request Workspace
8047
</p>
8148
<span class="badge badge-sm border-base-300 bg-base-200/70 font-mono text-[11px]">
82-
{props.requestCount} req
49+
{requestCount()} req
8350
</span>
8451
</div>
8552

@@ -103,7 +70,7 @@ export function RequestWorkspaceTabs(props: RequestWorkspaceTabsProps) {
10370
<div class="px-3 pb-3 pt-2">
10471
<div class="rounded-box border border-base-300 bg-base-100/70 px-3 py-2 text-sm text-base-content/75">
10572
<Show
106-
when={props.selectedRequest}
73+
when={selectedRequest()}
10774
fallback={<p>Select a request to view {TAB_LABELS[props.activeTab].toLowerCase()}.</p>}
10875
>
10976
{(request) => (
@@ -116,59 +83,64 @@ export function RequestWorkspaceTabs(props: RequestWorkspaceTabsProps) {
11683
</Match>
11784
<Match when={props.activeTab === 'headers'}>
11885
<Show
119-
when={!props.requestDetailsLoading}
86+
when={!httpWorkspace.drafts.parse.loading()}
12087
fallback={<p>Loading request headers…</p>}
12188
>
12289
<Show
123-
when={!props.requestDetailsError}
124-
fallback={<p>{props.requestDetailsError}</p>}
90+
when={!httpWorkspace.drafts.parse.error()}
91+
fallback={<p>{httpWorkspace.drafts.parse.error()}</p>}
12592
>
12693
<RequestWorkspaceHeadersPanel
127-
hasRequest={Boolean(props.selectedRequest)}
128-
requestHeaders={props.requestHeaders}
129-
headerDraftDirty={props.headerDraftDirty}
130-
headerDraftSaving={props.headerDraftSaving}
131-
headerDraftSaveError={props.headerDraftSaveError}
132-
onHeaderChange={props.onHeaderChange}
133-
onAddHeader={props.onAddHeader}
134-
onRemoveHeader={props.onRemoveHeader}
135-
onSaveHeaders={props.onSaveHeaders}
136-
onDiscardHeaders={props.onDiscardHeaders}
94+
hasRequest={Boolean(selectedRequest())}
95+
requestHeaders={httpWorkspace.drafts.header.draftHeaders()}
96+
headerDraftDirty={httpWorkspace.drafts.header.isDirty()}
97+
headerDraftSaving={httpWorkspace.drafts.header.isSaving()}
98+
headerDraftSaveError={httpWorkspace.drafts.header.saveError()}
99+
onHeaderChange={httpWorkspace.drafts.header.onHeaderChange}
100+
onAddHeader={httpWorkspace.drafts.header.onAddHeader}
101+
onRemoveHeader={httpWorkspace.drafts.header.onRemoveHeader}
102+
onSaveHeaders={httpWorkspace.drafts.header.onSave}
103+
onDiscardHeaders={httpWorkspace.drafts.header.onDiscard}
137104
/>
138105
</Show>
139106
</Show>
140107
</Match>
141108
<Match when={props.activeTab === 'body'}>
142-
<Show when={!props.requestDetailsLoading} fallback={<p>Loading request body…</p>}>
109+
<Show
110+
when={!httpWorkspace.drafts.parse.loading()}
111+
fallback={<p>Loading request body…</p>}
112+
>
143113
<Show
144-
when={!props.requestDetailsError}
145-
fallback={<p>{props.requestDetailsError}</p>}
114+
when={!httpWorkspace.drafts.parse.error()}
115+
fallback={<p>{httpWorkspace.drafts.parse.error()}</p>}
146116
>
147117
<RequestWorkspaceBodyPanel
148-
hasRequest={Boolean(props.selectedRequest)}
149-
requestBodySummary={props.requestBodySummary}
150-
requestBodyDraft={props.requestBodyDraft}
151-
requestBodyFormDataDraft={props.requestBodyFormDataDraft}
152-
requestBodyFilePathDraft={props.requestBodyFilePathDraft}
153-
bodyDraftDirty={props.bodyDraftDirty}
154-
bodyDraftSaving={props.bodyDraftSaving}
155-
bodyDraftSaveError={props.bodyDraftSaveError}
156-
bodyDraftValidationError={props.bodyDraftValidationError}
157-
bodyDraftIsJsonEditable={props.bodyDraftIsJsonEditable}
158-
bodyDraftTemplateWarnings={props.bodyDraftTemplateWarnings}
159-
onBodyChange={props.onBodyChange}
160-
onBodyFilePathChange={props.onBodyFilePathChange}
161-
onBodyFormDataNameChange={props.onBodyFormDataNameChange}
162-
onBodyFormDataTypeChange={props.onBodyFormDataTypeChange}
163-
onBodyFormDataValueChange={props.onBodyFormDataValueChange}
164-
onBodyFormDataFilenameChange={props.onBodyFormDataFilenameChange}
165-
onBodyFormDataAddField={props.onBodyFormDataAddField}
166-
onBodyFormDataRemoveField={props.onBodyFormDataRemoveField}
167-
onBodyPrettify={props.onBodyPrettify}
168-
onBodyMinify={props.onBodyMinify}
169-
onBodyCopy={props.onBodyCopy}
170-
onSaveBody={props.onSaveBody}
171-
onDiscardBody={props.onDiscardBody}
118+
hasRequest={Boolean(selectedRequest())}
119+
requestBodySummary={httpWorkspace.drafts.parse.bodySummary()}
120+
requestBodyDraft={httpWorkspace.drafts.body.draftBody()}
121+
requestBodyFormDataDraft={httpWorkspace.drafts.body.draftFormData()}
122+
requestBodyFilePathDraft={httpWorkspace.drafts.body.draftFilePath()}
123+
bodyDraftDirty={httpWorkspace.drafts.body.isDirty()}
124+
bodyDraftSaving={httpWorkspace.drafts.body.isSaving()}
125+
bodyDraftSaveError={httpWorkspace.drafts.body.saveError()}
126+
bodyDraftValidationError={httpWorkspace.drafts.body.validationError()}
127+
bodyDraftIsJsonEditable={httpWorkspace.drafts.body.isJsonBody()}
128+
bodyDraftTemplateWarnings={httpWorkspace.drafts.body.templateWarnings()}
129+
onBodyChange={httpWorkspace.drafts.body.onBodyChange}
130+
onBodyFilePathChange={httpWorkspace.drafts.body.onFilePathChange}
131+
onBodyFormDataNameChange={httpWorkspace.drafts.body.onFormDataNameChange}
132+
onBodyFormDataTypeChange={httpWorkspace.drafts.body.onFormDataTypeChange}
133+
onBodyFormDataValueChange={httpWorkspace.drafts.body.onFormDataValueChange}
134+
onBodyFormDataFilenameChange={
135+
httpWorkspace.drafts.body.onFormDataFilenameChange
136+
}
137+
onBodyFormDataAddField={httpWorkspace.drafts.body.onAddFormDataField}
138+
onBodyFormDataRemoveField={httpWorkspace.drafts.body.onRemoveFormDataField}
139+
onBodyPrettify={httpWorkspace.drafts.body.onBodyPrettify}
140+
onBodyMinify={httpWorkspace.drafts.body.onBodyMinify}
141+
onBodyCopy={() => void httpWorkspace.drafts.body.onBodyCopy()}
142+
onSaveBody={httpWorkspace.drafts.body.onSave}
143+
onDiscardBody={httpWorkspace.drafts.body.onDiscard}
172144
/>
173145
</Show>
174146
</Show>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { HttpRequestWorkspaceState } from '../hooks/useHttpRequestWorkspace';
2+
import { createSimpleContext } from '../utils/createSimpleContext';
3+
4+
// Context for HTTP request editor - provides workspace state to child components
5+
// This eliminates prop drilling from HttpEditorWithExecution to RequestWorkspaceTabs
6+
7+
interface HttpRequestEditorContextProps extends Record<string, unknown> {
8+
store: HttpRequestWorkspaceState;
9+
}
10+
11+
const context = createSimpleContext<HttpRequestWorkspaceState, HttpRequestEditorContextProps>({
12+
name: 'HttpRequestEditor',
13+
gate: false, // No async initialization needed
14+
init: (props) => props.store
15+
});
16+
17+
export const useHttpRequestEditor = context.use;
18+
export const HttpRequestEditorProvider = context.provider;

packages/web/src/context/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { HttpRequestEditorProvider, useHttpRequestEditor } from './httpRequestEditor';
12
export { ObserverProvider, useObserver } from './observer';
23
export { ScriptRunnerProvider, useScriptRunner } from './scriptRunner';
34
export type { ConnectionState } from './sdk';

packages/web/src/hooks/useHttpRequestWorkspace.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ interface UseHttpRequestWorkspaceInput {
1414
workspace: WorkspaceStore;
1515
}
1616

17-
// Structured API with domain grouping - prepares for Phase 4 Context
18-
interface HttpRequestWorkspaceState {
17+
// Structured API with domain grouping
18+
export interface HttpRequestWorkspaceState {
1919
// Core request selection state
2020
selection: {
2121
index: Accessor<number>;

0 commit comments

Comments
 (0)