1+ import { useEffect , useRef , useState } from "react" ;
12import { Button } from "@cloudflare/kumo/components/button" ;
23import { Input } from "@cloudflare/kumo/components/input" ;
34import { MinusIcon , PlusIcon } from "@phosphor-icons/react" ;
@@ -6,6 +7,19 @@ export function ConfigServicesEditor(props: Readonly<{
67 services : Array < [ string , number ] > ;
78 onChange : ( services : Array < [ string , number ] > ) => void ;
89} > ) {
10+ const [ localEntries , setLocalEntries ] = useState < Array < [ string , number ] > > (
11+ ( ) => props . services ,
12+ ) ;
13+
14+ const ownLengthRef = useRef ( props . services . length ) ;
15+
16+ useEffect ( ( ) => {
17+ if ( props . services . length !== ownLengthRef . current ) {
18+ ownLengthRef . current = props . services . length ;
19+ setLocalEntries ( props . services ) ;
20+ }
21+ } , [ props . services ] ) ;
22+
923 return (
1024 < div className = "space-y-3" >
1125 < div className = "flex items-center justify-between gap-3" >
@@ -20,10 +34,13 @@ export function ConfigServicesEditor(props: Readonly<{
2034 size = "sm"
2135 variant = "secondary"
2236 onClick = { ( ) => {
23- props . onChange ( [
24- ...props . services ,
25- [ "" , 8080 ] ,
26- ] ) ;
37+ const newEntries : Array < [ string , number ] > = [
38+ ...localEntries ,
39+ [ "service_name" , 8080 ] ,
40+ ] ;
41+ ownLengthRef . current = newEntries . length ;
42+ setLocalEntries ( newEntries ) ;
43+ props . onChange ( newEntries ) ;
2744 } }
2845 >
2946 < PlusIcon size = { 16 } />
@@ -32,22 +49,24 @@ export function ConfigServicesEditor(props: Readonly<{
3249 </ div >
3350
3451 < div className = "space-y-3" >
35- { props . services . map ( ( [ name , port ] , index ) => (
52+ { localEntries . map ( ( [ name , port ] , index ) => (
3653 < div
37- key = { ` ${ name } - ${ index } ` }
54+ key = { index }
3855 className = "grid gap-3 rounded-xl border border-kumo-line bg-kumo-overlay p-3 md:grid-cols-[minmax(0,1fr)_180px_auto]"
3956 >
4057 < Input
4158 label = "Name"
4259 value = { name }
60+ { ...( name === "" ? { error : "Name is required" } : { } ) }
4361 onChange = { ( event ) => {
44- props . onChange (
45- props . services . map ( ( currentService , currentIndex ) =>
46- currentIndex === index
47- ? [ event . target . value , currentService [ 1 ] ]
48- : currentService ,
49- ) ,
62+ const newName = event . target . value ;
63+ const newEntries = localEntries . map ( ( entry , i ) =>
64+ i === index ? [ newName , entry [ 1 ] ] as [ string , number ] : entry ,
5065 ) ;
66+ setLocalEntries ( newEntries ) ;
67+ if ( newName . trim ( ) !== "" ) {
68+ props . onChange ( newEntries ) ;
69+ }
5170 } }
5271 />
5372
@@ -58,13 +77,12 @@ export function ConfigServicesEditor(props: Readonly<{
5877 max = { 65535 }
5978 value = { port }
6079 onChange = { ( event ) => {
61- props . onChange (
62- props . services . map ( ( currentService , currentIndex ) =>
63- currentIndex === index
64- ? [ currentService [ 0 ] , Number ( event . target . value ) ]
65- : currentService ,
66- ) ,
80+ const newPort = Number ( event . target . value ) ;
81+ const newEntries = localEntries . map ( ( entry , i ) =>
82+ i === index ? [ entry [ 0 ] , newPort ] as [ string , number ] : entry ,
6783 ) ;
84+ setLocalEntries ( newEntries ) ;
85+ props . onChange ( newEntries ) ;
6886 } }
6987 />
7088
@@ -73,11 +91,12 @@ export function ConfigServicesEditor(props: Readonly<{
7391 type = "button"
7492 size = "sm"
7593 variant = "secondary"
76- disabled = { props . services . length === 1 }
94+ disabled = { localEntries . length === 1 }
7795 onClick = { ( ) => {
78- props . onChange (
79- props . services . filter ( ( _ , currentIndex ) => currentIndex !== index ) ,
80- ) ;
96+ const newEntries = localEntries . filter ( ( _ , i ) => i !== index ) ;
97+ ownLengthRef . current = newEntries . length ;
98+ setLocalEntries ( newEntries ) ;
99+ props . onChange ( newEntries ) ;
81100 } }
82101 >
83102 < MinusIcon size = { 16 } />
0 commit comments