useCallback in all staking callbacks (#2376)

* Add React.emomo to some components

* Linting

* useCallback for callbacks

* Additional useCallback impl.
This commit is contained in:
Jaco Greeff 2020-03-12 12:59:09 +01:00 committed by GitHub
parent 31211324e5
commit 83a2ddedd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 225 additions and 143 deletions

View File

@ -4,7 +4,7 @@
import { BareProps as Props } from '@polkadot/react-components/types';
import React, { useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import store from 'store';
import styled from 'styled-components';
import { defaultColor, chainColors, emptyColor, nodeColors } from '@polkadot/apps-config/ui/general';
@ -46,23 +46,35 @@ function Apps ({ className }: Props): React.ReactElement<Props> {
}, [systemChain, systemName]);
const { isCollapsed, isMenu, isMenuOpen } = sidebar;
const _setSidebar = (update: Partial<SidebarState>): void =>
setSidebar(store.set('sidebar', { ...sidebar, ...update }));
const _collapse = (): void =>
_setSidebar({ isCollapsed: !isCollapsed });
const _toggleMenu = (): void =>
_setSidebar({ isCollapsed: false, isMenuOpen: true });
const _handleResize = (): void => {
const transition = window.innerWidth < SIDEBAR_MENU_THRESHOLD
? SideBarTransition.MINIMISED_AND_EXPANDED
: SideBarTransition.EXPANDED_AND_MAXIMISED;
const _setSidebar = useCallback(
(update: Partial<SidebarState>): void =>
setSidebar((sidebar: SidebarState) =>
store.set('sidebar', { ...sidebar, ...update })
),
[]
);
const _collapse = useCallback(
(): void => _setSidebar({ isCollapsed: !isCollapsed }),
[isCollapsed]
);
const _toggleMenu = useCallback(
(): void => _setSidebar({ isCollapsed: false, isMenuOpen: true }),
[]
);
const _handleResize = useCallback(
(): void => {
const transition = window.innerWidth < SIDEBAR_MENU_THRESHOLD
? SideBarTransition.MINIMISED_AND_EXPANDED
: SideBarTransition.EXPANDED_AND_MAXIMISED;
_setSidebar({
isMenu: transition === SideBarTransition.MINIMISED_AND_EXPANDED,
isMenuOpen: false,
transition
});
};
_setSidebar({
isMenu: transition === SideBarTransition.MINIMISED_AND_EXPANDED,
isMenuOpen: false,
transition
});
},
[]
);
return (
<>

View File

@ -4,7 +4,7 @@
import { SIDEBAR_MENU_THRESHOLD } from '../constants';
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
import { Responsive } from 'semantic-ui-react';
import routing from '@polkadot/apps-routing';
@ -38,8 +38,14 @@ function SideBar ({ className, collapse, handleResize, isCollapsed, isMenuOpen,
}, { network: false })
);
const _toggleModal = (name: string): () => void =>
(): void => setModals({ ...modals, [name]: !modals[name] });
const _toggleModal = useCallback(
(name: string): () => void =>
(): void => setModals((modals: Record<string, boolean>) => ({
...modals,
[name]: !modals[name]
})),
[]
);
return (
<Responsive
@ -51,7 +57,7 @@ function SideBar ({ className, collapse, handleResize, isCollapsed, isMenuOpen,
onClick={toggleMenu}
/>
{routing.routes.map((route): React.ReactNode => (
route && route.Modal
route?.Modal
? route.Modal && modals[route.name]
? (
<route.Modal
@ -207,11 +213,20 @@ export default styled(SideBar)`
align-self: flex-end;
flex-grow: 0;
padding: 0 !important;
position: relative;
width: inherit;
.text {
padding-left: 0.5rem;
}
.ui--Badge {
margin: 0;
position: absolute;
right: 0.5rem;
top: 0.55rem;
z-index: 1;
}
}
.apps--SideBar-collapse {

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import { Link } from 'react-router-dom';
import { useAccounts, useApi } from '@polkadot/react-hooks';
@ -19,12 +19,15 @@ export default function Accounts ({ className }: Props): React.ReactElement<Prop
const { isApiReady } = useApi();
const [isHidden, setIsHidden] = useState(false);
const _onClose = useCallback(
(): void => setIsHidden(true),
[]
);
if (!isApiReady || hasAccounts || isHidden) {
return null;
}
const _onClose = (): void => setIsHidden(true);
return (
<BaseOverlay
className={className}

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
import { Icon } from '@polkadot/react-components';
@ -16,12 +16,15 @@ interface Props {
function BaseOverlay ({ children, className, icon, type }: Props): React.ReactElement<Props> | null {
const [isHidden, setIsHidden] = useState(false);
const _onClose = useCallback(
(): void => setIsHidden(true),
[]
);
if (isHidden) {
return null;
}
const _onClose = (): void => setIsHidden(true);
return (
<div className={`${className} ${type === 'error' ? 'isError' : 'isInfo'}`}>
<div className='content'>

View File

@ -6,7 +6,7 @@ import { DeriveAccountInfo } from '@polkadot/api-derive/types';
import { ActionStatus } from '@polkadot/react-components/Status/types';
import { RecoveryConfig } from '@polkadot/types/interfaces';
import React, { useState, useEffect } from 'react';
import React, { useCallback, useState, useEffect } from 'react';
import { Label } from 'semantic-ui-react';
import styled from 'styled-components';
import { AddressInfo, AddressSmall, Badge, Button, ChainLock, Forget, Icon, IdentityIcon, InputTags, LinkExternal, Menu, Popup, Input } from '@polkadot/react-components';
@ -57,7 +57,10 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
const [isSettingsOpen, toggleSettings] = useToggle();
const [isTransferOpen, toggleTransfer] = useToggle();
const _setTags = (tags: string[]): void => setTags(tags.sort());
const _setTags = useCallback(
(tags: string[]): void => setTags(tags.sort()),
[]
);
useEffect((): void => {
const { identity, nickname } = info || {};

View File

@ -9,7 +9,7 @@ import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import keyring from '@polkadot/ui-keyring';
import { getLedger, isLedger } from '@polkadot/react-api';
import { useAccounts, useFavorites } from '@polkadot/react-hooks';
import { useAccounts, useFavorites, useToggle } from '@polkadot/react-hooks';
import { Button, Input, Table } from '@polkadot/react-components';
import CreateModal from './modals/Create';
@ -39,9 +39,9 @@ async function queryLedger (): Promise<void> {
function Overview ({ className, onStatusChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { allAccounts, hasAccounts } = useAccounts();
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [isImportOpen, setIsImportOpen] = useState(false);
const [isQrOpen, setIsQrOpen] = useState(false);
const [isCreateOpen, toggleCreate] = useToggle();
const [isImportOpen, toggleImport] = useToggle();
const [isQrOpen, toggleQr] = useToggle();
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
const [sortedAccounts, setSortedAccounts] = useState<SortedAccount[]>([]);
const [filter, setFilter] = useState<string>('');
@ -66,28 +66,24 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
);
}, [allAccounts, favorites]);
const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
const _toggleImport = (): void => setIsImportOpen(!isImportOpen);
const _toggleQr = (): void => setIsQrOpen(!isQrOpen);
return (
<div className={className}>
<Banner />
{isCreateOpen && (
<CreateModal
onClose={_toggleCreate}
onClose={toggleCreate}
onStatusChange={onStatusChange}
/>
)}
{isImportOpen && (
<ImportModal
onClose={_toggleImport}
onClose={toggleImport}
onStatusChange={onStatusChange}
/>
)}
{isQrOpen && (
<QrModal
onClose={_toggleQr}
onClose={toggleQr}
onStatusChange={onStatusChange}
/>
)}
@ -96,21 +92,21 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
icon='add'
isPrimary
label={t('Add account')}
onClick={_toggleCreate}
onClick={toggleCreate}
/>
<Button.Or />
<Button
icon='sync'
isPrimary
label={t('Restore JSON')}
onClick={_toggleImport}
onClick={toggleImport}
/>
<Button.Or />
<Button
icon='qrcode'
isPrimary
label={t('Add via Qr')}
onClick={_toggleQr}
onClick={toggleQr}
/>
{isLedger() && (
<>

View File

@ -4,7 +4,7 @@
import { KeypairType } from '@polkadot/util-crypto/types';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { Button, Dropdown, Icon, Input, Modal, StatusContext } from '@polkadot/react-components';
import keyring from '@polkadot/ui-keyring';
import { assert, u8aToHex } from '@polkadot/util';
@ -58,17 +58,22 @@ export default function InjectKeys ({ onClose }: Props): React.ReactElement<Prop
}
}, [crypto, suri]);
const _onSubmit = (): void =>
queueRpc({
const _onSubmit = useCallback(
(): void => queueRpc({
rpc: { section: 'author', method: 'insertKey' } as any,
values: [keyType, suri, publicKey]
});
const _cryptoOptions = CRYPTO_MAP[keyType].map((value): { text: string; value: KeypairType } => ({
text: value === 'ed25519'
? t('ed25519, Edwards')
: t('sr15519, Schnorrkel'),
value
}));
}),
[keyType, publicKey, suri]
);
const _cryptoOptions = useMemo(
() => CRYPTO_MAP[keyType].map((value): { text: string; value: KeypairType } => ({
text: value === 'ed25519'
? t('ed25519, Edwards')
: t('sr15519, Schnorrkel'),
value
})),
[keyType, t]
);
return (
<Modal

View File

@ -8,7 +8,7 @@ import { AccountId, EraIndex, Exposure, StakingLedger, ValidatorPrefs } from '@p
import { Codec, ITuple } from '@polkadot/types/types';
import BN from 'bn.js';
import React, { useContext, useEffect, useState } from 'react';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { Trans } from 'react-i18next';
import styled from 'styled-components';
import { ApiPromise } from '@polkadot/api';
@ -162,11 +162,13 @@ function Account ({ allStashes, className, isOwnStash, next, onUpdateType, rewar
]);
}, [rewards]);
const _doPayout = (): void =>
queueExtrinsic({
const _doPayout = useCallback(
(): void => queueExtrinsic({
accountId: controllerId,
extrinsic: createPayout(api, payoutRewards)
});
}),
[api, controllerId, payoutRewards]
);
return (
<tr className={className}>

View File

@ -5,7 +5,7 @@
import { DerivedStakingOverview, DeriveStakerReward } from '@polkadot/api-derive/types';
import { ActiveEraInfo, EraIndex } from '@polkadot/types/interfaces';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Button, Table } from '@polkadot/react-components';
import { useCall, useApi, useOwnStashes } from '@polkadot/react-hooks';
import { Option } from '@polkadot/types';
@ -45,16 +45,24 @@ export default function Actions ({ allRewards, allStashes, className, isVisible,
);
}, [ownStashes, stashTypes]);
const _toggleNewStake = (): void => setIsNewStateOpen(!isNewStakeOpen);
const _onUpdateType = (stashId: string, type: 'validator' | 'nominator' | 'started' | 'other'): void =>
setStashTypes({
...stashTypes,
[stashId]: type === 'validator'
? 1
: type === 'nominator'
? 5
: 9
});
const _toggleNewStake = useCallback(
(): void => setIsNewStateOpen(
(isNewStakeOpen: boolean) => !isNewStakeOpen
),
[]
);
const _onUpdateType = useCallback(
(stashId: string, type: 'validator' | 'nominator' | 'started' | 'other'): void =>
setStashTypes((stashTypes: Record<string, number>) => ({
...stashTypes,
[stashId]: type === 'validator'
? 1
: type === 'nominator'
? 5
: 9
})),
[]
);
return (
<div className={`${className} ${!isVisible && 'staking--hidden'}`}>

View File

@ -6,7 +6,7 @@ import { AccountId, Balance, RewardPoint } from '@polkadot/types/interfaces';
import { DeriveAccountInfo, DerivedStakingQuery, DerivedHeartbeatAuthor } from '@polkadot/api-derive/types';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import ApiPromise from '@polkadot/api/promise';
import { AddressMini, AddressSmall, Badge, Icon } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
@ -122,16 +122,25 @@ function Address ({ address, className, filterName, hasQueries, heartbeat, isAut
);
}, [address, filterName, info]);
const _onFavorite = (): void => toggleFavorite(address);
const _onQueryStats = (): void => {
window.location.hash = `/staking/query/${address}`;
};
const _toggleNominators = (event: React.SyntheticEvent): void => {
event.preventDefault();
event.stopPropagation();
const _onFavorite = useCallback(
(): void => toggleFavorite(address),
[address]
);
const _onQueryStats = useCallback(
(): void => {
window.location.hash = `/staking/query/${address}`;
},
[address]
);
const _toggleNominators = useCallback(
(event: React.SyntheticEvent): void => {
event.preventDefault();
event.stopPropagation();
setIsExpanded(!isExpanded);
};
setIsExpanded((isExpanded: boolean) => !isExpanded);
},
[]
);
return (
<tr className={`${className} ${isAuthor && 'isHighlight'} ${!isVisible && 'staking--hidden'}`}>

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Button, InputAddressSimple } from '@polkadot/react-components';
@ -18,11 +18,14 @@ export default function Query ({ className }: Props): React.ReactElement<Props>
const { value } = useParams();
const [validatorId, setValidatorId] = useState<string | null>(value || null);
const _onQuery = (): void => {
if (validatorId) {
window.location.hash = `/staking/query/${validatorId}`;
}
};
const _onQuery = useCallback(
(): void => {
if (validatorId) {
window.location.hash = `/staking/query/${validatorId}`;
}
},
[validatorId]
);
return (
<div className={className}>

View File

@ -4,7 +4,7 @@
import { ValidatorInfo } from './types';
import React from 'react';
import React, { useCallback } from 'react';
import { AddressSmall, Icon } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
@ -18,10 +18,17 @@ interface Props {
function Validator ({ info: { accountId, bondOther, bondOwn, bondTotal, commissionPer, isCommission, isFavorite, isNominating, key, numNominators, rankOverall, rewardPayout, validatorPayment }, toggleFavorite }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const _onFavorite = (): void => toggleFavorite(key);
const _onQueryStats = (): void => {
window.location.hash = `/staking/query/${key}`;
};
const _onFavorite = useCallback(
(): void => toggleFavorite(key),
[key]
);
const _onQueryStats = useCallback(
(): void => {
window.location.hash = `/staking/query/${key}`;
},
[key]
);
return (
<tr className={`${isNominating && 'isHighlight'}`}>

View File

@ -7,7 +7,7 @@ import { Balance, ValidatorPrefs, ValidatorPrefsTo196 } from '@polkadot/types/in
import { ValidatorInfo } from './types';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { registry } from '@polkadot/react-api';
import { Icon, InputBalance, Spinner, Table } from '@polkadot/react-components';
@ -174,27 +174,30 @@ function Targets ({ className }: Props): React.ReactElement<Props> {
const [{ sorted, sortBy, sortFromMax }, setSorted] = useState<{ sorted?: ValidatorInfo[]; sortBy: SortBy; sortFromMax: boolean }>({ sortBy: 'rankOverall', sortFromMax: true });
const amount = useDebounce(_amount);
const _sort = (newSortBy: SortBy, unsorted = validators, isAdjust = true): void => {
const newSortFromMax = isAdjust && newSortBy === sortBy ? !sortFromMax : true;
const _sort = useCallback(
(newSortBy: SortBy, unsorted: ValidatorInfo[] = validators, isAdjust = true): void => {
const newSortFromMax = isAdjust && newSortBy === sortBy ? !sortFromMax : true;
setSorted({
sortBy: newSortBy,
sortFromMax: newSortFromMax,
sorted: unsorted
.sort((a, b): number =>
newSortFromMax
? a[newSortBy] - b[newSortBy]
: b[newSortBy] - a[newSortBy]
)
.sort((a, b): number =>
a.isFavorite === b.isFavorite
? 0
: a.isFavorite
? -1
: 1
)
});
};
setSorted({
sortBy: newSortBy,
sortFromMax: newSortFromMax,
sorted: unsorted
.sort((a, b): number =>
newSortFromMax
? a[newSortBy] - b[newSortBy]
: b[newSortBy] - a[newSortBy]
)
.sort((a, b): number =>
a.isFavorite === b.isFavorite
? 0
: a.isFavorite
? -1
: 1
)
});
},
[validators]
);
useEffect((): void => {
if (electedInfo) {

View File

@ -4,17 +4,18 @@
import { StringOrNull } from '@polkadot/react-components/types';
import { useState } from 'react';
import { useCallback, useState } from 'react';
export default function useAccountId (initialValue: StringOrNull = null, onChangeAccountId?: (_: StringOrNull) => void): [StringOrNull, (_: StringOrNull) => void] {
const [accountId, setAccountId] = useState<StringOrNull>(initialValue);
return [
accountId,
const _setAccountId = useCallback(
(accountId: StringOrNull = null): void => {
setAccountId(accountId);
onChangeAccountId && onChangeAccountId(accountId);
}
];
},
[]
);
return [accountId, _setAccountId];
}

View File

@ -2,6 +2,8 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { useCallback } from 'react';
import store from 'store';
import useApi from './useApi';
@ -9,9 +11,8 @@ import useApi from './useApi';
export default function useCacheKey <T> (storageKeyBase: string): [(defaultValue?: T) => T | undefined, (value: T) => T] {
const { api, isDevelopment } = useApi();
const STORAGE_KEY = `${storageKeyBase}:${isDevelopment ? 'development' : api.genesisHash}`;
const getter = useCallback((): T | undefined => store.get(STORAGE_KEY), []);
const setter = useCallback((value: T): T => store.set(STORAGE_KEY, value), []);
return [
(): T | undefined => store.get(STORAGE_KEY),
(value: T): T => store.set(STORAGE_KEY, value)
];
return [getter, setter];
}

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { useState } from 'react';
import { useCallback, useState } from 'react';
import useCacheKey from './useCacheKey';
@ -10,15 +10,16 @@ import useCacheKey from './useCacheKey';
export default function useFavorites (storageKeyBase: string): [string[], (address: string) => void] {
const [getCache, setCache] = useCacheKey<string[]>(storageKeyBase);
const [favorites, setFavorites] = useState<string[]>(getCache() || []);
const _toggleFavorite = (address: string): void =>
setFavorites(
setCache(
const toggleFavorite = useCallback(
(address: string): void => setFavorites(
(favorites: string[]) => setCache(
favorites.includes(address)
? favorites.filter((accountId): boolean => address !== accountId)
: [...favorites, address]
)
);
),
[]
);
return [favorites, _toggleFavorite];
return [favorites, toggleFavorite];
}

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
type FormField<T> = [
T | null,
@ -20,10 +20,11 @@ export default function useFormField<T> (defaultValue: T | null, validate: (_?:
(): boolean => isTruthy<T>(value) && validate(value),
[value]
);
const setter = useCallback((value?: T | null): void => setValue(value || null), []);
return [
value,
isValid,
(value?: T | null): void => setValue(value || null)
setter
];
}

View File

@ -4,22 +4,28 @@
import { ModalState } from './types';
import { useCallback } from 'react';
import useToggle from './useToggle';
export default function useModal (defaultIsOpen?: boolean, onOpen?: () => void, onClose?: () => void): ModalState {
const [isOpen, , setIsOpen] = useToggle(defaultIsOpen || false);
return {
isOpen,
onOpen: (): void => {
const _onOpen = useCallback(
(): void => {
setIsOpen(true);
onOpen && onOpen();
},
onClose: (): void => {
[]
);
const _onClose = useCallback(
(): void => {
setIsOpen(false);
onClose && onClose();
}
};
},
[]
);
return { isOpen, onClose: _onClose, onOpen: _onOpen };
}

View File

@ -59,9 +59,9 @@ export default function useOwnEraRewards (): OwnRewards {
const [state, setState] = useState<OwnRewards>({ rewardCount: 0 });
useEffect((): void => {
if (available && nextParams) {
setState(getRewards(available, nextParams));
}
available && nextParams && setState(
getRewards(available, nextParams)
);
}, [available, nextParams]);
return state;

View File

@ -2,13 +2,15 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { useState } from 'react';
import { useCallback, useState } from 'react';
// Simple wrapper for a true/false toggle
export default function useToggle (defaultValue = false): [boolean, () => void, (value: boolean) => void] {
const [isActive, setActive] = useState(defaultValue);
const toggleActive = useCallback(
(): void => setActive((isActive: boolean) => !isActive),
[]
);
const toggle = (): void => setActive(!isActive);
return [isActive, toggle, setActive];
return [isActive, toggleActive, setActive];
}

View File

@ -22,6 +22,7 @@ function getExtrinsic<T extends TxDefs> (api: ApiPromise, txDef: T): Submittable
const [section, method] = txDef[0].split('.');
assert(api.tx[section] && api.tx[section][method], `Unable to find api.tx.${section}.${method}`);
try {
return api.tx[section][method](...(
isFunction(txDef[1])