on master help sign in
point-view: refactor component state and lifecycle
479879etomholford
src/components/L2/Dropdowns/AccountsDropdown.tsx+9−18
@@ -1,6 +1,5 @@
1−import React, { useCallback, useState } from 'react';
1+import { useCallback, useMemo, useState } from 'react';
2import * as ob from 'urbit-ob';
2import * as ob from 'urbit-ob';
3−import * as need from 'lib/need';
4import { Icon, Row, Box, Button } from '@tlon/indigo-react';
3import { Icon, Row, Box, Button } from '@tlon/indigo-react';
5import { Just } from 'folktale/maybe';
4import { Just } from 'folktale/maybe';
6
5
@@ -14,7 +13,6 @@ import { useHistory } from 'store/history';
14import { usePointCursor } from 'store/pointCursor';
13import { usePointCursor } from 'store/pointCursor';
15import { usePointCache } from 'store/pointCache';
14import { usePointCache } from 'store/pointCache';
16import { clearInvitesStorage } from 'store/storage/roller';
15import { clearInvitesStorage } from 'store/storage/roller';
17−import { useRollerStore } from 'store/roller';
18
16
19import { PointLayer } from 'lib/types/PointLayer';
17import { PointLayer } from 'lib/types/PointLayer';
20import { abbreviateAddress } from 'lib/utils/address';
18import { abbreviateAddress } from 'lib/utils/address';
@@ -22,8 +20,8 @@ import { abbreviateAddress } from 'lib/utils/address';
22import Dropdown from './Dropdown';
20import Dropdown from './Dropdown';
23import './AccountsDropdown.scss';
21import './AccountsDropdown.scss';
24import Modal from '../Modal';
22import Modal from '../Modal';
25−import { convertToInt } from 'lib/convertToInt';
23+import { useHasNetworkKeysSet } from 'lib/useHasNetworkKeysSet';
26−import { isPlanet } from 'lib/utils/point';
24+import { useRollerStore } from 'store/roller';
27
25
28interface AccountsDropdownProps {
26interface AccountsDropdownProps {
29 showMigrate: boolean;
27 showMigrate: boolean;
@@ -35,19 +33,12 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
35 const { push, names, reset }: any = useHistory();
33 const { push, names, reset }: any = useHistory();
36 const { setPointCursor }: any = usePointCursor();
34 const { setPointCursor }: any = usePointCursor();
37 const { controlledPoints }: any = usePointCache();
35 const { controlledPoints }: any = usePointCache();
38− const { currentL2 } = useRollerStore();
36+ const { currentL2 }: any = useRollerStore();
39− const { getDetails }: any = usePointCache();
40− const { pointCursor }: any = usePointCursor();
41− const point = pointCursor.getOrElse(null);
42−
43− let networkKeysNotSet = true;
44
37
45− // if (point) {
38+ const networkKeysSet = useHasNetworkKeysSet();
46− // const details = need.details(getDetails(point));
39+ const showMigrateModal = useMemo(() => {
47− // const isStarOrGalaxy = !isPlanet(point);
40+ return showMigrate || (!currentL2 && networkKeysSet);
48− // const networkRevision = convertToInt(details.keyRevisionNumber, 10);
41+ }, [currentL2, networkKeysSet, showMigrate]);
49− // networkKeysNotSet = !currentL2 && isStarOrGalaxy && networkRevision === 0;
50− // }
51
42
52 const [showModal, setShowModal] = useState(false);
43 const [showModal, setShowModal] = useState(false);
53
44
@@ -115,7 +106,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
115 })}
106 })}
116 </Box>
107 </Box>
117 <Box className="divider" />
108 <Box className="divider" />
118− {(showMigrate || (!currentL2 && !networkKeysNotSet)) && (
109+ {showMigrateModal && (
119 <Row className="entry" onClick={() => setShowModal(true)}>
110 <Row className="entry" onClick={() => setShowModal(true)}>
120 <Box>Migrate</Box>
111 <Box>Migrate</Box>
121 <Row className="layer-migration">
112 <Row className="layer-migration">
src/lib/useHasNetworkKeysSet.tsxnew file+30
@@ -0,0 +1,30 @@
1+import { useEffect, useMemo, useState } from 'react';
2+import * as need from 'lib/need';
3+import { usePointCache } from 'store/pointCache';
4+import { usePointCursor } from 'store/pointCursor';
5+import { useRollerStore } from 'store/roller';
6+import { convertToInt } from 'lib/convertToInt';
7+import { isPlanet } from 'lib/utils/point';
8+
9+export const useHasNetworkKeysSet = () => {
10+ const { pointCursor } = usePointCursor();
11+ const { getDetails } = usePointCache();
12+ const { currentL2 } = useRollerStore();
13+ const point = pointCursor.getOrElse(null);
14+ const [networkKeysSet, setNetworkKeysSet] = useState<boolean>(true);
15+
16+ useEffect(() => {
17+ if (point) {
18+ const details = need.details(getDetails(point));
19+ const isStarOrGalaxy = !isPlanet(point);
20+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
21+ const networkKeysNotSet =
22+ !currentL2 && isStarOrGalaxy && networkRevision === 0;
23+ setNetworkKeysSet(!networkKeysNotSet);
24+ }
25+ }, [currentL2, getDetails, point, pointCursor]);
26+
27+ return useMemo(() => {
28+ return networkKeysSet;
29+ }, [networkKeysSet]);
30+};
src/lib/useRoller.ts+8−8
@@ -401,14 +401,14 @@ export default function useRoller() {
401 }
401 }
402 },
402 },
403 [
403 [
404− // api,
404+ api,
405− // pointCursor,
406− // setInvites,
407− // setPendingTransactions,
408− // authToken,
405+ authToken,
409− // contracts,
406+ contracts,
410− // controlledPoints,
407+ controlledPoints,
411− // getDetails,
408+ getDetails,
409+ pointCursor,
410+ setInvites,
411+ setPendingTransactions,
412 ]
412 ]
413 );
413 );
414
414
src/views/Login/Ticket.js+2−6
@@ -1,6 +1,5 @@
1−import React, { useCallback, useMemo, useRef } from 'react';
1+import { useCallback, useMemo, useRef } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3−import * as azimuth from 'azimuth-js';
4import * as kg from 'urbit-key-generation';
3import * as kg from 'urbit-key-generation';
5import {
4import {
6 Grid,
5 Grid,
@@ -11,11 +10,9 @@ import {
11} from 'indigo-react';
10} from 'indigo-react';
12import { FORM_ERROR } from 'final-form';
11import { FORM_ERROR } from 'final-form';
13
12
14−import { useNetwork } from 'store/network';
15import { useWallet } from 'store/wallet';
13import { useWallet } from 'store/wallet';
16import { usePointCursor } from 'store/pointCursor';
14import { usePointCursor } from 'store/pointCursor';
17
15
18−import * as need from 'lib/need';
19import { urbitWalletFromTicket } from 'lib/wallet';
16import { urbitWalletFromTicket } from 'lib/wallet';
20import { WALLET_TYPES } from 'lib/constants';
17import { WALLET_TYPES } from 'lib/constants';
21import useImpliedPoint from 'lib/useImpliedPoint';
18import useImpliedPoint from 'lib/useImpliedPoint';
@@ -92,12 +89,11 @@ function TicketInputAccessory({ name }) {
92export default function Ticket({ className, goHome }) {
89export default function Ticket({ className, goHome }) {
93 useLoginView(WALLET_TYPES.TICKET);
90 useLoginView(WALLET_TYPES.TICKET);
94
91
95− const { contracts } = useNetwork();
96 const { setUrbitWallet } = useWallet();
92 const { setUrbitWallet } = useWallet();
97 const { setPointCursor } = usePointCursor();
93 const { setPointCursor } = usePointCursor();
98 const impliedPoint = useImpliedPoint();
94 const impliedPoint = useImpliedPoint();
99 const didWarn = useRef(false);
95 const didWarn = useRef(false);
100− const { api, getPoints } = useRoller();
96+ const { getPoints } = useRoller();
101
97
102 const validateForm = useCallback((values, errors) => {
98 const validateForm = useCallback((values, errors) => {
103 didWarn.current = false;
99 didWarn.current = false;
src/views/Point/InviteForm.tsxnew file+92
@@ -0,0 +1,92 @@
1+import cn from 'classnames';
2+import BarGraph from 'components/BarGraph';
3+import Chip from 'components/Chip';
4+import InviteSigilList from 'components/InviteSigilList';
5+import { Grid, Flex } from 'indigo-react';
6+import { Button } from '@tlon/indigo-react';
7+import Inviter from 'views/Invite/Inviter';
8+
9+export const InviteForm = ({
10+ showInviteForm,
11+ setShowInviteForm,
12+ acceptedInvites,
13+ acceptedPoints,
14+ availableInvites,
15+ goCohort,
16+ pendingPoints,
17+ sentInvites,
18+ showInvites,
19+}) => {
20+ const _totalInvites =
21+ sentInvites.getOrElse(0) + availableInvites.getOrElse(0);
22+ const _pendingInvites = pendingPoints.getOrElse([]).length;
23+
24+ return (
25+ <>
26+ <Grid.Item cols={[1, 11]}>
27+ Invite Group
28+ <br />
29+ </Grid.Item>
30+
31+ <Grid.Item
32+ className={cn('t-right underline pointer-hover', {
33+ gray4: sentInvites.getOrElse(0) === 0,
34+ })}
35+ onClick={goCohort}
36+ cols={[11, 13]}>
37+ View
38+ </Grid.Item>
39+ <Grid.Item full>
40+ <Flex align="center">
41+ <Flex.Item>
42+ {acceptedInvites.getOrElse(0)} / {_totalInvites}
43+ </Flex.Item>
44+ {_pendingInvites > 0 && (
45+ <Flex.Item as={Chip} className="bg-yellow1 yellow4">
46+ {_pendingInvites} pending
47+ </Flex.Item>
48+ )}
49+ </Flex>
50+ </Grid.Item>
51+
52+ {showInvites && (
53+ <>
54+ <Grid.Item
55+ full
56+ as={BarGraph}
57+ available={availableInvites}
58+ sent={sentInvites}
59+ accepted={acceptedInvites}
60+ />
61+ <Grid.Item
62+ full
63+ as={InviteSigilList}
64+ pendingPoints={pendingPoints}
65+ acceptedPoints={acceptedPoints}
66+ />
67+ </>
68+ )}
69+ {!showInvites && (
70+ <>
71+ <Grid.Item full className="b-gray4 b-dotted b1 self-center">
72+ <div className="p4 pv8 t-center gray4">
73+ Start your invite group by adding members
74+ </div>
75+ </Grid.Item>
76+ </>
77+ )}
78+ {!showInviteForm && availableInvites.getOrElse(0) > 0 && (
79+ <Grid.Item
80+ full
81+ solid
82+ as={Button}
83+ center
84+ onClick={() => setShowInviteForm(true)}>
85+ Add Members
86+ </Grid.Item>
87+ )}
88+ {showInviteForm && <Inviter />}
89+ <Grid.Item full className="mb2" />
90+ </>
91+ );
92+};
src/views/Point/Point.js → src/views/Point/Point.tsxrenamed+45−169
@@ -1,5 +1,4 @@
1−import React, { useCallback, useEffect, useState } from 'react';
1+import { useCallback, useEffect, useMemo, useState } from 'react';
2−import cn from 'classnames';
3import { azimuth } from 'azimuth-js';
2import { azimuth } from 'azimuth-js';
4import { Just } from 'folktale/maybe';
3import { Just } from 'folktale/maybe';
5import { Grid, Flex } from 'indigo-react';
4import { Grid, Flex } from 'indigo-react';
@@ -13,9 +12,6 @@ import View from 'components/View';
13import Greeting from 'components/Greeting';
12import Greeting from 'components/Greeting';
14import Passport from 'components/Passport';
13import Passport from 'components/Passport';
15import Blinky from 'components/Blinky';
14import Blinky from 'components/Blinky';
16−import BarGraph from 'components/BarGraph';
17−import Chip from 'components/Chip';
18−import InviteSigilList from 'components/InviteSigilList';
19import { ForwardButton } from 'components/Buttons';
15import { ForwardButton } from 'components/Buttons';
20import L2PointHeader from 'components/L2/Headers/L2PointHeader';
16import L2PointHeader from 'components/L2/Headers/L2PointHeader';
21import LayerIndicator from 'components/L2/LayerIndicator';
17import LayerIndicator from 'components/L2/LayerIndicator';
@@ -23,185 +19,67 @@ import Card from 'components/L2/Card';
23
19
24import * as need from 'lib/need';
20import * as need from 'lib/need';
25import useInvites from 'lib/useInvites';
21import useInvites from 'lib/useInvites';
26−import { useSyncExtras } from 'lib/useSyncPoints';
27import useCurrentPermissions from 'lib/useCurrentPermissions';
22import useCurrentPermissions from 'lib/useCurrentPermissions';
28import { useLocalRouter } from 'lib/LocalRouter';
23import { useLocalRouter } from 'lib/LocalRouter';
29−import Inviter from 'views/Invite/Inviter';
30import useRoller from 'lib/useRoller';
24import useRoller from 'lib/useRoller';
31
25
32import './Point.scss';
26import './Point.scss';
33import { isL2 } from 'lib/utils/roller';
27import { isL2 } from 'lib/utils/roller';
34import { isDevelopment } from 'lib/flags';
28import { isDevelopment } from 'lib/flags';
35−import { convertToInt } from 'lib/convertToInt';
36import { usePointCache } from 'store/pointCache';
29import { usePointCache } from 'store/pointCache';
37import Modal from 'components/L2/Modal';
30import Modal from 'components/L2/Modal';
38import { isPlanet } from 'lib/utils/point';
31import { isPlanet } from 'lib/utils/point';
39−
32+import { useHasNetworkKeysSet } from 'lib/useHasNetworkKeysSet';
40−function InviteForm({
33+import { InviteForm } from './InviteForm';
41− showInviteForm,
42− setShowInviteForm,
43− acceptedInvites,
44− acceptedPoints,
45− availableInvites,
46− goCohort,
47− pendingPoints,
48− sentInvites,
49− showInvites,
50−}) {
51− const _totalInvites =
52− sentInvites.getOrElse(0) + availableInvites.getOrElse(0);
53− const _pendingInvites = pendingPoints.getOrElse([]).length;
54−
55− return (
56− <>
57− <Grid.Item cols={[1, 11]}>
58− Invite Group
59− <br />
60− </Grid.Item>
61−
62− <Grid.Item
63− className={cn('t-right underline pointer-hover', {
64− gray4: sentInvites.getOrElse(0) === 0,
65− })}
66− onClick={goCohort}
67− cols={[11, 13]}>
68− View
69− </Grid.Item>
70− <Grid.Item full>
71− <Flex align="center">
72− <Flex.Item>
73− {acceptedInvites.getOrElse(0)} / {_totalInvites}
74− </Flex.Item>
75− {_pendingInvites > 0 && (
76− <Flex.Item as={Chip} className="bg-yellow1 yellow4">
77− {_pendingInvites} pending
78− </Flex.Item>
79− )}
80− </Flex>
81− </Grid.Item>
82−
83− {showInvites && (
84− <>
85− <Grid.Item
86− full
87− as={BarGraph}
88− available={availableInvites}
89− sent={sentInvites}
90− accepted={acceptedInvites}
91− />
92− <Grid.Item
93− full
94− as={InviteSigilList}
95− pendingPoints={pendingPoints}
96− acceptedPoints={acceptedPoints}
97− />
98− </>
99− )}
100− {!showInvites && (
101− <>
102− <Grid.Item full className="b-gray4 b-dotted b1 self-center">
103− <div className="p4 pv8 t-center gray4">
104− Start your invite group by adding members
105− </div>
106− </Grid.Item>
107− </>
108− )}
109− {!showInviteForm && availableInvites.getOrElse(0) > 0 && (
110− <Grid.Item
111− full
112− solid
113− as={Button}
114− center
115− onClick={() => setShowInviteForm(true)}>
116− Add Members
117− </Grid.Item>
118− )}
119− {showInviteForm && <Inviter />}
120− <Grid.Item full className="mb2" />
121− </>
122− );
123−}
124
34
125export default function Point() {
35export default function Point() {
126 const { pop, push, names } = useLocalRouter();
36 const { pop, push, names } = useLocalRouter();
127− const { wallet, authToken } = useWallet();
37+ const { wallet } = useWallet();
128 const { pointCursor } = usePointCursor();
38 const { pointCursor } = usePointCursor();
129 const point = need.point(pointCursor);
39 const point = need.point(pointCursor);
130− const { getDetails } = usePointCache();
40+ const { syncExtras } = usePointCache();
131− // sync the current cursor
132− useSyncExtras([point]);
133−
134 const { api, getInvites } = useRoller();
41 const { api, getInvites } = useRoller();
135 const {
42 const {
136 pendingTransactions,
43 pendingTransactions,
137 nextRoll,
44 nextRoll,
138 invites,
45 invites,
139 setCurrentPoint,
46 setCurrentPoint,
140− setPendingTransactions,
141− currentL2,
142 setNonces,
47 setNonces,
143 increaseNonce,
48 increaseNonce,
144− nonces,
145 } = useRollerStore();
49 } = useRollerStore();
146−
50+ const networkKeysSet = useHasNetworkKeysSet();
147− // const pointSize = azimuth.getPointSize(point);
148− // const details = need.details(getDetails(point));
149− // const details = getDetails(point);
150− // console.log(details);
151− // const isStarOrGalaxy = pointSize !== azimuth.PointSize.Planet;
152− // const networkRevision =
153− // Just.hasInstance(details) &&
154− // convertToInt(need.details(details).keyRevisionNumber, 10);
155− // console.log(networkRevision);
156− const networkKeysNotSet = false;
157− // networkRevision && !currentL2 && isStarOrGalaxy && networkRevision === 0;
158−
159− const [showModal, setShowModal] = useState(networkKeysNotSet);
51+ const [showModal, setShowModal] = useState(!networkKeysSet);
160−
52+
161− // useEffect(() => {
53+ const loadL1Info = useCallback(async () => {
162− // if (networkKeysNotSet) {
54+ await syncExtras(point);
163− // setShowModal(true);
55+ }, [point, syncExtras]);
164− // }
56+
165− // }, [networkKeysNotSet]);
166−
167− useEffect(
168− () => {
169− const loadL2Info = async () => {
57+ const loadL2Info = useCallback(async () => {
170− const getTransactions = async () => {
58+ const getTransactions = async () => {
171− const pointInfo = await api.getPoint(Number(point));
59+ const pointInfo = await api.getPoint(Number(point));
172− const pendingTxs = await api.getPendingByShip(Number(point));
60+ const pendingTxs = await api.getPendingByShip(Number(point));
173− if (isDevelopment) {
61+ if (isDevelopment) {
174− console.log('POINT INFO', pointInfo);
62+ console.log('POINT INFO', pointInfo);
175− }
63+ }
176− setNonces(point, pointInfo.ownership);
64+ setNonces(point, pointInfo.ownership);
177−
65+
178− for (let index = 0; index < pendingTxs.length; index++) {
66+ for (let index = 0; index < pendingTxs.length; index++) {
179− const proxy = pendingTxs[index].rawTx?.from?.proxy;
67+ const proxy = pendingTxs[index].rawTx?.from?.proxy;
180− console.log(pointInfo.ownership, proxy);
68+ console.log(pointInfo.ownership, proxy);
181− increaseNonce(point, proxy);
69+ increaseNonce(point, proxy);
182− }
70+ }
183− setCurrentPoint(pointInfo);
71+ setCurrentPoint(pointInfo);
184− getInvites(isL2(pointInfo.dominion));
72+ await getInvites(isL2(pointInfo.dominion));
185− };
73+ };
186−
74+
187− getTransactions();
75+ await getTransactions();
188− };
189−
190− loadL2Info();
191− },
192− [
193− // TODO: adding this causes the effect to be called constantly...
194− // api,
195− // point,
196− // authToken,
197− // setPendingTransactions,
198− // nonces,
199− // setCurrentPoint,
76+ }, [api, point, setCurrentPoint, increaseNonce, getInvites, setNonces]);
200− // increaseNonce,
77+
201− // getInvites,
78+ useEffect(() => {
202− // setNonces,
79+ loadL1Info();
203− ]
80+ loadL2Info();
204− );
81+ // eslint-disable-next-line react-hooks/exhaustive-deps
82+ }, [point]);
205
83
206 const { isParent, canManage, canSpawn, canVote } = useCurrentPermissions();
84 const { isParent, canManage, canSpawn, canVote } = useCurrentPermissions();
207
85
@@ -222,8 +100,6 @@ export default function Point() {
222 const hasInvites = showInvites || availableInvites.getOrElse(0) !== 0;
100 const hasInvites = showInvites || availableInvites.getOrElse(0) !== 0;
223
101
224 const loadedInvites = Just.hasInstance(availableInvites);
102 const loadedInvites = Just.hasInstance(availableInvites);
225− //
226− // availableInvites.getOrElse(0) === 0
227
103
228 const goSenate = useCallback(() => push(names.SENATE), [push, names]);
104 const goSenate = useCallback(() => push(names.SENATE), [push, names]);
229
105
@@ -262,13 +138,13 @@ export default function Point() {
262 );
138 );
263 })();
139 })();
264
140
265− // sync the current cursor
266− useSyncExtras([point]);
267−
268 const address = need.addressFromWallet(wallet);
141 const address = need.addressFromWallet(wallet);
142+
143+ const numPending = useMemo(() => {
269− const numPending = pendingTransactions.filter(
144+ return pendingTransactions.filter(
270− ({ rawTx }) => rawTx?.tx?.type === 'spawn'
145+ ({ rawTx }) => rawTx?.tx?.type === 'spawn'
271− ).length;
146+ ).length;
147+ }, [pendingTransactions]);
272
148
273 return (
149 return (
274 <View
150 <View
@@ -280,7 +156,7 @@ export default function Point() {
280 <L2PointHeader
156 <L2PointHeader
281 hideTimer={!!numPending}
157 hideTimer={!!numPending}
282 numInvites={invites.length}
158 numInvites={invites.length}
283− hideInvites={networkKeysNotSet}
159+ hideInvites={!networkKeysSet}
284 />
160 />
285 }>
161 }>
286 <Greeting point={point} />
162 <Greeting point={point} />