Skip to content

Commit 4e7acda

Browse files
committed
fix: Update values from outer state in form component
1 parent 540e0e7 commit 4e7acda

File tree

3 files changed

+90
-68
lines changed

3 files changed

+90
-68
lines changed

packages/app/src/components/Form.tsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import { Button, Input } from "@nextui-org/react";
22
import cn from "clsx";
3-
import { forwardRef, useImperativeHandle } from "react";
3+
import { useEffect } from "react";
44
import { useForm } from "react-hook-form";
55
import { Controller } from "react-hook-form";
66
import type { InputProps } from "@nextui-org/input";
7-
import type { ForwardedRef } from "react";
87
import type { Control } from "react-hook-form";
98

10-
export interface FormRef {
11-
setValue(key: string, value: string | number): void;
12-
}
13-
149
interface FormProps<T extends FieldRecord> {
1510
className?: string;
1611
fields: T;
@@ -28,20 +23,29 @@ type FieldMap<T extends FieldRecord = FieldRecord> = {
2823
[K in keyof T]: T[K] extends { value: infer V } ? V : never;
2924
};
3025

31-
function FormComponent<T extends FieldRecord>(
32-
{ className, fields, onSubmit, submit }: FormProps<T>,
33-
ref: ForwardedRef<FormRef>,
34-
) {
26+
export function Form<T extends FieldRecord>({
27+
className,
28+
fields,
29+
onSubmit,
30+
submit,
31+
}: FormProps<T>) {
3532
const entries = Object.entries(fields);
3633

37-
const { handleSubmit, setValue, control } = useForm<FieldMap>({
34+
const { handleSubmit, setValue, control, getValues } = useForm<FieldMap>({
3835
defaultValues: entries.reduce<FieldMap>((acc, [key, field]) => {
3936
acc[key] = field.value;
4037
return acc;
4138
}, {}),
4239
});
4340

44-
useImperativeHandle(ref, () => ({ setValue }), [setValue]);
41+
useEffect(() => {
42+
const currentValues = getValues();
43+
Object.entries(fields).forEach(([name, field]) => {
44+
if (currentValues[name] !== field.value) {
45+
setValue(name, field.value);
46+
}
47+
});
48+
}, [fields]);
4549

4650
return (
4751
<form
@@ -78,10 +82,6 @@ function FormComponent<T extends FieldRecord>(
7882
);
7983
}
8084

81-
export const Form = forwardRef(FormComponent) as <T extends FieldRecord>(
82-
props: FormProps<T> & { ref?: ForwardedRef<FormRef> },
83-
) => ReturnType<typeof FormComponent>;
84-
8585
export function FormInput({
8686
name,
8787
control,

packages/app/src/components/PlayerControls.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ import { useSeekbar } from "../hooks/useSeekbar";
77
import type { ReactNode, RefObject } from "react";
88

99
export function PlayerControls() {
10-
const ready = usePlayerSelector((player) => player.ready);
11-
if (!ready) {
12-
return null;
13-
}
1410
return (
1511
<div className="flex flex-col gap-4 overflow-hidden">
1612
<div className="flex">
1713
<PlayButton />
1814
</div>
19-
<Seekbar />
20-
<Time />
15+
<div className="p-3 rounded-md bg-default-100">
16+
<Time />
17+
<Seekbar />
18+
</div>
2119
<Tracks />
2220
</div>
2321
);
@@ -59,11 +57,11 @@ function Seekbar() {
5957
>
6058
{hms(seekbar.value)}
6159
</Tooltip>
62-
<div className="flex items-center rounded-lg overflow-hidden mb-2">
63-
<div className="h-2 bg-gray-100 w-full" />
60+
<div className="relative flex items-center">
61+
<div className="h-2 bg-default-200 w-full" />
6462
<div
6563
className={cn(
66-
"h-2 absolute left-0 right-0 bg-gray-200 origin-left opacity-0 transition-opacity",
64+
"h-2 absolute left-0 right-0 bg-default-300 origin-left opacity-0 transition-opacity",
6765
seekbar.hover && "opacity-100",
6866
)}
6967
style={{
@@ -126,17 +124,18 @@ function CuePoints() {
126124
const seekableStart = usePlayerSelector((player) => player.seekableStart);
127125

128126
return (
129-
<div className="relative h-2 bg-gray-100 rounded-lg">
127+
<div className="relative h-4 bg-gray-100 rounded-lg">
130128
{cuePoints.map((cuePoint) => {
131129
return (
132130
<div
133131
key={cuePoint}
134132
style={{
135133
left: `${((cuePoint - seekableStart) / (duration - seekableStart)) * 100}%`,
136134
}}
137-
className="absolute -translate-x-1/2 top-0 w-2 h-2 rounded-full bg-yellow-500"
135+
className="absolute -translate-x-1/2 flex items-center flex-col"
138136
>
139-
<div className="absolute -translate-x-1/2 w-[2px] left-1/2 h-4 bottom-0 bg-yellow-500" />
137+
<div className="w-[2px] h-2 bg-yellow-500" />
138+
<div className="w-2 h-2 rounded-full bg-yellow-500" />
140139
</div>
141140
);
142141
})}
@@ -151,7 +150,7 @@ function Time() {
151150
const live = usePlayerSelector((player) => player.live);
152151

153152
return (
154-
<div className="flex text-sm">
153+
<div className="flex text-sm mb-2">
155154
{hms(time)}
156155
<div className="grow" />
157156
{live ? `${hms(seekableStart)} - ${hms(duration)}` : `${hms(duration)}`}

packages/app/src/routes/(dashboard)/_layout/player.tsx

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,25 @@ import {
77
Tabs,
88
} from "@nextui-org/react";
99
import { createFileRoute } from "@tanstack/react-router";
10-
import { useRef, useState } from "react";
10+
import { useEffect, useState } from "react";
1111
import { CodeEditor } from "../../../components/CodeEditor";
1212
import { Form } from "../../../components/Form";
1313
import { Player } from "../../../components/Player";
1414
import { PlayerControls } from "../../../components/PlayerControls";
1515
import { PlayerStats } from "../../../components/PlayerStats";
1616
import { ScrollCard } from "../../../components/ScrollCard";
17-
import { PlayerProvider, WithPlayer } from "../../../context/PlayerContext";
17+
import {
18+
PlayerProvider,
19+
usePlayerSelector,
20+
WithPlayer,
21+
} from "../../../context/PlayerContext";
1822
import { useSwaggerSchema } from "../../../hooks/useSwaggerSchema";
19-
import type { FormRef } from "../../../components/Form";
2023

2124
export const Route = createFileRoute("/(dashboard)/_layout/player")({
2225
component: RouteComponent,
2326
});
2427

2528
function RouteComponent() {
26-
const formRef = useRef<FormRef>(null);
2729
const [url, setUrl] = useState("");
2830
const [error, setError] = useState<string | null>(null);
2931

@@ -48,7 +50,6 @@ function RouteComponent() {
4850

4951
const data = await response.json();
5052
if (response.ok) {
51-
formRef.current?.setValue("url", data.url);
5253
setUrl(data.url);
5354
} else {
5455
setError(data);
@@ -65,40 +66,7 @@ function RouteComponent() {
6566
</div>
6667
</div>
6768
<WithPlayer>
68-
<Tabs
69-
classNames={{
70-
panel: "grow p-0",
71-
}}
72-
>
73-
<Tab title="Config">
74-
<ScrollCard>
75-
<Form
76-
ref={formRef}
77-
submit="Play"
78-
fields={{
79-
url: {
80-
label: "URL",
81-
type: "string",
82-
value: url,
83-
},
84-
}}
85-
onSubmit={async (values) => {
86-
setUrl(values.url);
87-
}}
88-
/>
89-
</ScrollCard>
90-
</Tab>
91-
<Tab title="Stats">
92-
<ScrollCard>
93-
<PlayerStats />
94-
</ScrollCard>
95-
</Tab>
96-
<Tab title="Controls">
97-
<ScrollCard>
98-
<PlayerControls />
99-
</ScrollCard>
100-
</Tab>
101-
</Tabs>
69+
<PlayerTabs url={url} setUrl={setUrl} />
10270
</WithPlayer>
10371
</div>
10472
</PlayerProvider>
@@ -126,3 +94,58 @@ function RouteComponent() {
12694
</div>
12795
);
12896
}
97+
98+
function PlayerTabs({
99+
url,
100+
setUrl,
101+
}: {
102+
url: string;
103+
setUrl: (value: string) => void;
104+
}) {
105+
const [selected, setSelected] = useState<string | number>("config");
106+
const ready = usePlayerSelector((player) => player.ready);
107+
108+
useEffect(() => {
109+
if (ready) {
110+
setSelected("controls");
111+
}
112+
}, [ready]);
113+
114+
return (
115+
<Tabs
116+
classNames={{
117+
panel: "grow p-0",
118+
}}
119+
selectedKey={selected}
120+
onSelectionChange={setSelected}
121+
>
122+
<Tab key="config" title="Config">
123+
<ScrollCard>
124+
<Form
125+
submit="Play"
126+
fields={{
127+
url: {
128+
label: "URL",
129+
type: "string",
130+
value: url,
131+
},
132+
}}
133+
onSubmit={async (values) => {
134+
setUrl(values.url);
135+
}}
136+
/>
137+
</ScrollCard>
138+
</Tab>
139+
<Tab key="stats" title="Stats">
140+
<ScrollCard>
141+
<PlayerStats />
142+
</ScrollCard>
143+
</Tab>
144+
<Tab key="controls" title="Controls" isDisabled={!ready}>
145+
<ScrollCard>
146+
<PlayerControls />
147+
</ScrollCard>
148+
</Tab>
149+
</Tabs>
150+
);
151+
}

0 commit comments

Comments
 (0)