Skip to content

Commit 48a1046

Browse files
committed
Fix re-rendering mess on allocation page
1 parent cbedd45 commit 48a1046

File tree

2 files changed

+113
-83
lines changed

2 files changed

+113
-83
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { memo, useState } from 'react';
2+
import isEqual from 'react-fast-compare';
3+
import tw from 'twin.macro';
4+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5+
import { faNetworkWired } from '@fortawesome/free-solid-svg-icons';
6+
import InputSpinner from '@/components/elements/InputSpinner';
7+
import { Textarea } from '@/components/elements/Input';
8+
import Can from '@/components/elements/Can';
9+
import Button from '@/components/elements/Button';
10+
import GreyRowBox from '@/components/elements/GreyRowBox';
11+
import { Allocation } from '@/api/server/getServer';
12+
import styled from 'styled-components/macro';
13+
import { debounce } from 'debounce';
14+
import setServerAllocationNotes from '@/api/server/network/setServerAllocationNotes';
15+
import useFlash from '@/plugins/useFlash';
16+
import { ServerContext } from '@/state/server';
17+
18+
const Code = styled.code`${tw`font-mono py-1 px-2 bg-neutral-900 rounded text-sm block`}`;
19+
const Label = styled.label`${tw`uppercase text-xs mt-1 text-neutral-400 block px-1 select-none transition-colors duration-150`}`;
20+
21+
interface Props {
22+
allocation: Allocation;
23+
onSetPrimary: (id: number) => void;
24+
onNotesChanged: (id: number, notes: string) => void;
25+
}
26+
27+
const AllocationRow = ({ allocation, onSetPrimary, onNotesChanged }: Props) => {
28+
const [ loading, setLoading ] = useState(false);
29+
const { clearFlashes, clearAndAddHttpError } = useFlash();
30+
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
31+
32+
const setAllocationNotes = debounce((notes: string) => {
33+
setLoading(true);
34+
clearFlashes('server:network');
35+
36+
setServerAllocationNotes(uuid, allocation.id, notes)
37+
.then(() => onNotesChanged(allocation.id, notes))
38+
.catch(error => clearAndAddHttpError({ key: 'server:network', error }))
39+
.then(() => setLoading(false));
40+
}, 750);
41+
42+
return (
43+
<GreyRowBox
44+
$hoverable={false}
45+
// css={index > 0 ? tw`mt-2 overflow-x-auto` : tw`overflow-x-auto`}
46+
>
47+
<div css={tw`hidden md:block pl-4 pr-6 text-neutral-400`}>
48+
<FontAwesomeIcon icon={faNetworkWired}/>
49+
</div>
50+
<div css={tw`mr-4`}>
51+
<Code>{allocation.alias || allocation.ip}</Code>
52+
<Label>IP Address</Label>
53+
</div>
54+
<div>
55+
<Code>{allocation.port}</Code>
56+
<Label>Port</Label>
57+
</div>
58+
<div css={tw`px-8 flex-none sm:flex-1 self-start`}>
59+
<InputSpinner visible={loading}>
60+
<Textarea
61+
css={tw`bg-neutral-800 hover:border-neutral-600 border-transparent`}
62+
placeholder={'Notes'}
63+
defaultValue={allocation.notes || undefined}
64+
onChange={e => setAllocationNotes(e.currentTarget.value)}
65+
/>
66+
</InputSpinner>
67+
</div>
68+
<div css={tw`w-32 text-right pr-4 sm:pr-0`}>
69+
{allocation.isDefault ?
70+
<span css={tw`bg-green-500 py-1 px-2 rounded text-green-50 text-xs`}>Primary</span>
71+
:
72+
<Can action={'allocations.update'}>
73+
<Button
74+
isSecondary
75+
size={'xsmall'}
76+
color={'primary'}
77+
onClick={() => onSetPrimary(allocation.id)}
78+
>
79+
Make Primary
80+
</Button>
81+
</Can>
82+
}
83+
</div>
84+
</GreyRowBox>
85+
);
86+
};
87+
88+
export default memo(AllocationRow, isEqual);

resources/scripts/components/server/network/NetworkContainer.tsx

Lines changed: 25 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,32 @@
1-
import React, { useEffect, useState } from 'react';
2-
import tw from 'twin.macro';
3-
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4-
import { faNetworkWired } from '@fortawesome/free-solid-svg-icons';
5-
import styled from 'styled-components/macro';
6-
import GreyRowBox from '@/components/elements/GreyRowBox';
7-
import Button from '@/components/elements/Button';
8-
import Can from '@/components/elements/Can';
1+
import React, { useCallback, useEffect } from 'react';
92
import useSWR from 'swr';
103
import getServerAllocations from '@/api/server/network/getServerAllocations';
114
import { Allocation } from '@/api/server/getServer';
125
import Spinner from '@/components/elements/Spinner';
13-
import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation';
146
import useFlash from '@/plugins/useFlash';
15-
import { Textarea } from '@/components/elements/Input';
16-
import setServerAllocationNotes from '@/api/server/network/setServerAllocationNotes';
17-
import { debounce } from 'debounce';
18-
import InputSpinner from '@/components/elements/InputSpinner';
197
import ServerContentBlock from '@/components/elements/ServerContentBlock';
208
import { ServerContext } from '@/state/server';
219
import { useDeepMemoize } from '@/plugins/useDeepMemoize';
22-
23-
const Code = styled.code`${tw`font-mono py-1 px-2 bg-neutral-900 rounded text-sm block`}`;
24-
const Label = styled.label`${tw`uppercase text-xs mt-1 text-neutral-400 block px-1 select-none transition-colors duration-150`}`;
10+
import AllocationRow from '@/components/server/network/AllocationRow';
11+
import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation';
2512

2613
const NetworkContainer = () => {
2714
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
2815
const allocations = useDeepMemoize(ServerContext.useStoreState(state => state.server.data!.allocations));
2916

3017
const { clearFlashes, clearAndAddHttpError } = useFlash();
31-
const [ loading, setLoading ] = useState<false | number>(false);
32-
const { data, error, mutate } = useSWR<Allocation[]>(uuid, key => getServerAllocations(key), { initialData: allocations });
18+
const { data, error, mutate } = useSWR<Allocation[]>(uuid, key => getServerAllocations(key), {
19+
initialData: allocations,
20+
revalidateOnFocus: false,
21+
});
22+
23+
useEffect(() => {
24+
if (error) {
25+
clearAndAddHttpError({ key: 'server:network', error });
26+
}
27+
}, [ error ]);
3328

34-
const setPrimaryAllocation = (id: number) => {
29+
const setPrimaryAllocation = useCallback((id: number) => {
3530
clearFlashes('server:network');
3631

3732
const initial = data;
@@ -42,77 +37,24 @@ const NetworkContainer = () => {
4237
clearAndAddHttpError({ key: 'server:network', error });
4338
mutate(initial, false);
4439
});
45-
};
40+
}, []);
4641

47-
const setAllocationNotes = debounce((id: number, notes: string) => {
48-
setLoading(id);
49-
clearFlashes('server:network');
50-
51-
setServerAllocationNotes(uuid, id, notes)
52-
.then(() => mutate(data?.map(a => a.id === id ? { ...a, notes } : a), false))
53-
.catch(error => {
54-
clearAndAddHttpError({ key: 'server:network', error });
55-
})
56-
.then(() => setLoading(false));
57-
}, 750);
58-
59-
useEffect(() => {
60-
if (error) {
61-
clearAndAddHttpError({ key: 'server:network', error });
62-
}
63-
}, [ error ]);
42+
const onNotesAdded = useCallback((id: number, notes: string) => {
43+
mutate(data?.map(a => a.id === id ? { ...a, notes } : a), false);
44+
}, []);
6445

6546
return (
6647
<ServerContentBlock showFlashKey={'server:network'} title={'Network'}>
6748
{!data ?
6849
<Spinner size={'large'} centered/>
6950
:
70-
data.map(({ id, ip, port, alias, notes, isDefault }, index) => (
71-
<GreyRowBox
72-
$hoverable={false}
73-
key={`${ip}:${port}`}
74-
css={index > 0 ? tw`mt-2 overflow-x-auto` : tw`overflow-x-auto`}
75-
>
76-
<div css={tw`hidden md:block pl-4 pr-6 text-neutral-400`}>
77-
<FontAwesomeIcon icon={faNetworkWired}/>
78-
</div>
79-
<div css={tw`mr-4`}>
80-
<Code>{alias || ip}</Code>
81-
<Label>IP Address</Label>
82-
</div>
83-
<div>
84-
<Code>{port}</Code>
85-
<Label>Port</Label>
86-
</div>
87-
<div css={tw`px-8 flex-none sm:flex-1 self-start`}>
88-
<InputSpinner visible={loading === id}>
89-
<Textarea
90-
css={tw`bg-neutral-800 hover:border-neutral-600 border-transparent`}
91-
placeholder={'Notes'}
92-
defaultValue={notes || undefined}
93-
onChange={e => setAllocationNotes(id, e.currentTarget.value)}
94-
/>
95-
</InputSpinner>
96-
</div>
97-
<div css={tw`w-32 text-right pr-4 sm:pr-0`}>
98-
{isDefault ?
99-
<span css={tw`bg-green-500 py-1 px-2 rounded text-green-50 text-xs`}>
100-
Primary
101-
</span>
102-
:
103-
<Can action={'allocations.update'}>
104-
<Button
105-
isSecondary
106-
size={'xsmall'}
107-
color={'primary'}
108-
onClick={() => setPrimaryAllocation(id)}
109-
>
110-
Make Primary
111-
</Button>
112-
</Can>
113-
}
114-
</div>
115-
</GreyRowBox>
51+
data.map(allocation => (
52+
<AllocationRow
53+
key={`${allocation.ip}:${allocation.port}`}
54+
allocation={allocation}
55+
onSetPrimary={setPrimaryAllocation}
56+
onNotesChanged={onNotesAdded}
57+
/>
11658
))
11759
}
11860
</ServerContentBlock>

0 commit comments

Comments
 (0)