Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix useStream unsub #2010

Merged
merged 2 commits into from
Dec 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 6 additions & 12 deletions packages/app-staking/src/Targets/Summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ function Summary ({ lastReward, t, totalStaked }: Props): React.ReactElement<Pro

useEffect((): void => {
if (totalInsurance) {
setTotal(totalInsurance.toString());
setTotal(
`${formatBalance(totalInsurance, false)}${formatBalance.calcSi(totalInsurance.toString()).value}`
);
}
}, [totalInsurance]);

useEffect((): void => {
if (totalInsurance && totalStaked?.gtn(0)) {
setStakeInfo({
percentage: `${(totalStaked.muln(10000).div(totalInsurance).toNumber() / 100).toFixed(2)}%`,
staked: totalStaked.toString()
staked: `${formatBalance(totalStaked, false)}${formatBalance.calcSi(totalStaked.toString()).value}`
});
}
}, [totalInsurance, totalStaked]);
Expand All @@ -48,19 +50,11 @@ function Summary ({ lastReward, t, totalStaked }: Props): React.ReactElement<Pro
<SummaryBox>
<section className='ui--media-small'>
<CardSummary label={t('total staked')}>
{
staked
? `${formatBalance(staked, false)}${formatBalance.calcSi(staked).value}`
: '-'
}
{staked || '-'}
</CardSummary>
<CardSummary label=''>/</CardSummary>
<CardSummary label={t('total issuance')}>
{
total
? `${formatBalance(total, false)}${formatBalance.calcSi(total).value}`
: '-'
}
{total || '-'}
</CardSummary>
</section>
<CardSummary label={t('staked')}>
Expand Down
119 changes: 62 additions & 57 deletions packages/app-staking/src/Targets/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { ValidatorInfo } from './types';
import BN from 'bn.js';
import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { registry } from '@polkadot/react-api';
import { InputBalance, Table } from '@polkadot/react-components';
import { useAccounts, useApi, useFavorites, useStream } from '@polkadot/react-hooks';
import { useAccounts, useApi, useDebounce, useFavorites, useStream } from '@polkadot/react-hooks';
import { createType } from '@polkadot/types';

import { STORE_FAVS_BASE } from '../constants';
import translate from '../translate';
Expand Down Expand Up @@ -89,16 +91,72 @@ function sortValidators (list: ValidatorInfo[]): ValidatorInfo[] {
);
}

function extractInfo (allAccounts: string[], amount: BN = new BN(0), baseInfo: BaseInfo[], favorites: string[], lastReward: BN): AllInfo {
let totalStaked = new BN(0);
const validators = sortValidators(
baseInfo.map(({ accountId, stakers, validatorPrefs }): ValidatorInfo => {
const exposure = stakers || {
total: createType(registry, 'Compact<Balance>'),
own: createType(registry, 'Compact<Balance>'),
others: createType(registry, 'Vec<IndividualExposure>')
};
const prefs = (validatorPrefs as (ValidatorPrefs | ValidatorPrefsTo196)) || {
commission: createType(registry, 'Compact<Perbill>')
};
const bondOwn = exposure.own.unwrap();
const bondTotal = exposure.total.unwrap();
const perValidatorReward = lastReward.divn(baseInfo.length);
const validatorPayment = (prefs as ValidatorPrefsTo196).validatorPayment
? (prefs as ValidatorPrefsTo196).validatorPayment.unwrap() as BN
: (prefs as ValidatorPrefs).commission.unwrap().mul(perValidatorReward).div(PERBILL);
const key = accountId.toString();
const rewardSplit = perValidatorReward.sub(validatorPayment);
const rewardPayout = rewardSplit.gtn(0)
? amount.mul(rewardSplit).div(amount.add(bondTotal))
: new BN(0);
const isNominating = exposure.others.reduce((isNominating, indv): boolean => {
return isNominating || allAccounts.includes(indv.who.toString());
}, allAccounts.includes(key));

totalStaked = totalStaked.add(bondTotal);

return {
accountId,
bondOther: bondTotal.sub(bondOwn),
bondOwn,
bondShare: 0,
bondTotal,
isCommission: !!(prefs as ValidatorPrefs).commission,
isFavorite: favorites.includes(key),
isNominating,
key,
commissionPer: (((prefs as ValidatorPrefs).commission?.unwrap() || new BN(0)).muln(10000).div(PERBILL).toNumber() / 100),
numNominators: exposure.others.length,
rankBonded: 0,
rankOverall: 0,
rankPayment: 0,
rankReward: 0,
rewardPayout,
rewardSplit,
validatorPayment
};
})
);

return { totalStaked, validators };
}

function Targets ({ className, sessionRewards, t }: Props): React.ReactElement<Props> {
const { api } = useApi();
const { allAccounts } = useAccounts();
const [amount, setAmount] = useState<BN | undefined>(new BN(1000));
const [_amount, setAmount] = useState<BN | undefined>(new BN(1000));
const electedInfo = useStream<DerivedStakingElected>(api.derive.staking.electedInfo, []);
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
const [lastReward, setLastReward] = useState(new BN(0));
const lastBase = useRef<number>(0);
const [baseInfo, setBaseInfo] = useState<BaseInfo[]>([]);
const [{ validators, totalStaked }, setWorkable] = useState<AllInfo>({ totalStaked: new BN(0), validators: [] });
const amount = useDebounce(_amount);

useEffect((): void => {
if (sessionRewards && sessionRewards.length) {
Expand All @@ -125,60 +183,7 @@ function Targets ({ className, sessionRewards, t }: Props): React.ReactElement<P
}, [electedInfo]);

useEffect((): void => {
let totalStaked = new BN(0);
const numValidators = baseInfo.length;
const validators = sortValidators(
baseInfo.map(({ accountId, stakers, validatorPrefs }): ValidatorInfo => {
const exposure = stakers || {
total: api.createType('Compact<Balance>'),
own: api.createType('Compact<Balance>'),
others: api.createType('Vec<IndividualExposure>')
};
const prefs = (validatorPrefs as (ValidatorPrefs | ValidatorPrefsTo196)) || {
commission: api.createType('Compact<Perbill>')
};
const bondOwn = exposure.own.unwrap();
const bondTotal = exposure.total.unwrap();
const perValidatorReward = lastReward.divn(numValidators);
const validatorPayment = (prefs as ValidatorPrefsTo196).validatorPayment
? (prefs as ValidatorPrefsTo196).validatorPayment.unwrap() as BN
: (prefs as ValidatorPrefs).commission.unwrap().mul(perValidatorReward).div(PERBILL);
const key = accountId.toString();
const rewardSplit = perValidatorReward.sub(validatorPayment);
const calcAmount = amount || new BN(0);
const rewardPayout = rewardSplit.gtn(0)
? calcAmount.mul(rewardSplit).div(calcAmount.add(bondTotal))
: new BN(0);
const isNominating = exposure.others.reduce((isNominating, indv): boolean => {
return isNominating || allAccounts.includes(indv.who.toString());
}, allAccounts.includes(key));

totalStaked = totalStaked.add(bondTotal);

return {
accountId,
bondOther: bondTotal.sub(bondOwn),
bondOwn,
bondShare: 0,
bondTotal,
isCommission: !!(prefs as ValidatorPrefs).commission,
isFavorite: favorites.includes(key),
isNominating,
key,
commissionPer: (((prefs as ValidatorPrefs).commission?.unwrap() || new BN(0)).muln(10000).div(PERBILL).toNumber() / 100),
numNominators: exposure.others.length,
rankBonded: 0,
rankOverall: 0,
rankPayment: 0,
rankReward: 0,
rewardPayout,
rewardSplit,
validatorPayment
};
})
);

setWorkable({ totalStaked, validators });
setWorkable(extractInfo(allAccounts, amount, baseInfo, favorites, lastReward));
}, [allAccounts, amount, baseInfo, favorites, lastReward]);

return (
Expand All @@ -195,7 +200,7 @@ function Targets ({ className, sessionRewards, t }: Props): React.ReactElement<P
help={t('The amount that will be used on a per-validator basis to calculate rewards for that validator.')}
label={t('amount to use for estimation')}
onChange={setAmount}
value={amount}
value={_amount}
/>
<Table>
<Table.Body>
Expand Down
8 changes: 5 additions & 3 deletions packages/app-staking/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ function App ({ basePath, className, t }: Props): React.ReactElement<Props> {
const { hasAccounts } = useAccounts();
const { pathname } = useLocation();
const [next, setNext] = useState<string[]>([]);
const stakingControllers = useStream<[string[], string[]]>(api.derive.staking.controllers, [], { transform: transformStakingControllers });
const [allStashes, allControllers] = (useStream<[string[], string[]]>(api.derive.staking.controllers, [], {
defaultValue: EMPTY_ALL,
transform: transformStakingControllers
}) as [string[], string[]]);
const recentlyOnline = useStream<DerivedHeartbeats>(api.derive.imOnline.receivedHeartbeats, []);
const stakingOverview = useStream<DerivedStakingOverview>(api.derive.staking.overview, []);
const sessionRewards = useSessionRewards(MAX_SESSIONS);
const hasQueries = hasAccounts && !!(api.query.imOnline?.authoredBlocks);
const [allStashes, allControllers] = stakingControllers || EMPTY_ALL;
const validators = stakingOverview && stakingOverview.validators;
const validators = stakingOverview?.validators;

useEffect((): void => {
validators && setNext(
Expand Down
1 change: 1 addition & 0 deletions packages/react-hooks/src/track/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type Param = any;
export type Params = [] | [Param] | [Param, Param] | [Param, Param, Param];

export interface Options <T> {
defaultValue?: T;
paramMap?: (params: any) => Params;
transform?: (value: any) => T;
}
14 changes: 8 additions & 6 deletions packages/react-hooks/src/track/usePromise.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ interface TrackFn <T> {
// tracks a promise, typically an api.* call (query, query.at, rpc) that
// - returns a promise with the value
// FIXME The typings here need some serious TLC
export default function usePromise <T> (fn: TrackFn<T> | undefined, params: Params, { paramMap = transformIdentity, transform = transformIdentity }: Options<T> = {}): T | undefined {
const [value, setValue] = useState<T | undefined>();
export default function usePromise <T> (fn: TrackFn<T> | undefined, params: Params, { defaultValue, paramMap = transformIdentity, transform = transformIdentity }: Options<T> = {}): T | undefined {
const [value, setValue] = useState<T | undefined>(defaultValue);
const tracker = useRef<{ serialized: string | null }>({ serialized: null });

const _subscribe = (params: Params): void => {
Expand All @@ -34,12 +34,14 @@ export default function usePromise <T> (fn: TrackFn<T> | undefined, params: Para

// on changes, re-get
useEffect((): void => {
const [serialized, mappedParams] = extractParams(fn, params, paramMap);
if (fn) {
const [serialized, mappedParams] = extractParams(fn, params, paramMap);

if (mappedParams && serialized !== tracker.current.serialized) {
tracker.current.serialized = serialized;
if (mappedParams && serialized !== tracker.current.serialized) {
tracker.current.serialized = serialized;

_subscribe(mappedParams);
_subscribe(mappedParams);
}
}
}, [fn, params]);

Expand Down
19 changes: 13 additions & 6 deletions packages/react-hooks/src/track/useStream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ interface TrackFn {
// - returns a promise with an unsubscribe function
// - has a callback to set the value
// FIXME The typings here need some serious TLC
export default function useStream <T> (fn: TrackFn | undefined, params: Params, { paramMap = transformIdentity, transform = transformIdentity }: Options<T> = {}): T | undefined {
const [value, setValue] = useState<T | undefined>();
export default function useStream <T> (fn: TrackFn | undefined, params: Params, { defaultValue, paramMap = transformIdentity, transform = transformIdentity }: Options<T> = {}): T | undefined {
const [value, setValue] = useState<T | undefined>(defaultValue);
const tracker = useRef<{ serialized: string | null; subscriber: TrackFnResult }>({ serialized: null, subscriber: dummyPromise });

const _unsubscribe = (): void => {
Expand All @@ -54,14 +54,21 @@ export default function useStream <T> (fn: TrackFn | undefined, params: Params,
});
};

// initial effect, we need an unsubscription
useEffect((): () => void => {
return _unsubscribe;
}, [fn, params]);

// on changes, re-subscribe
useEffect((): void => {
const [serialized, mappedParams] = extractParams(fn, params, paramMap);
if (fn) {
const [serialized, mappedParams] = extractParams(fn, params, paramMap);

if (mappedParams && serialized !== tracker.current.serialized) {
tracker.current.serialized = serialized;
if (mappedParams && serialized !== tracker.current.serialized) {
tracker.current.serialized = serialized;

_subscribe(mappedParams);
_subscribe(mappedParams);
}
}
}, [fn, params]);

Expand Down
5 changes: 1 addition & 4 deletions packages/react-hooks/src/useFavorites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
// of the Apache-2.0 license. See the LICENSE file for details.

import { useState } from 'react';
import store from 'store';

import useCacheKey from './useCacheKey';

// hook for favorites with local storage
export default function useFavorites (storageKeyBase: string): [string[], (address: string) => void] {
const [getCache, setCache] = useCacheKey<string[]>(storageKeyBase);

// retrieve from the new style first, if not available, fallback to old-style
const [favorites, setFavorites] = useState<string[]>(getCache() || store.get(storageKeyBase, []));
const [favorites, setFavorites] = useState<string[]>(getCache() || []);

const _toggleFavorite = (address: string): void =>
setFavorites(
Expand Down