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: react hooks observers #55

Merged
merged 13 commits into from
Mar 12, 2024
5 changes: 5 additions & 0 deletions .changeset/gold-news-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuels/react': minor
---

fix: fuel hooks will only re-render tracked properties, instead of listening to every useQuery property.
6 changes: 3 additions & 3 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The Fuel Wallet React Hooks provide a set of hooks to seamless integrate the [Fu
## Installation

```bash
npm install fuels @fuel-wallet/react
npm install fuels @fuels/react
```

Note that the fuels package is also required as a dependency for better integration with other applications built using the [Fuels TS SDK](https://github.com/FuelLabs/fuels-ts).
Expand All @@ -19,7 +19,7 @@ Note that the fuels package is also required as a dependency for better integrat
Adding the providers on the upper level of the application that will use the hooks.

```tsx
import { FuelProvider } from '@fuel-wallet/react';
import { FuelProvider } from '@fuels/react';

import { App } from './App';

Expand All @@ -41,7 +41,7 @@ import {
useConnectors,
useDisconnect,
useIsConnected,
} from '@fuel-wallet/react';
} from '@fuels/react';

export default function App() {
const [connector, setConnector] = useState('');
Expand Down
21 changes: 7 additions & 14 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,23 @@
"description": "Interact with the Fuel Wallet Extension in React.",
"version": "0.15.3",
"license": "Apache-2.0",
"main": "src/index.ts",
"main": "./src/index.ts",
"exports": {
".": {
"require": "./dist/index.js",
"default": "./dist/index.mjs"
}
".": "./src/index.ts"
},
"publishConfig": {
"access": "public",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"typings": "dist/index.d.ts",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"typings": "./dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.js",
"default": "./dist/index.mjs"
}
}
},
"files": [
"dist"
],
"scripts": {
"build": "pnpm ts:check && tsup",
"ts:check": "pnpm tsc --noEmit"
Expand All @@ -40,12 +34,11 @@
"events": "^3.3.0"
},
"devDependencies": {
"@fuel-wallet/sdk": "^0.15.0",
"@fuels/ts-config": "workspace:*",
"@fuels/tsup-config": "workspace:*",
"styled-components": "^6.1.1",
"compare-versions": "^6.1.0",
"fuels": "0.74.0",
"styled-components": "^6.1.1",
"tsup": "^7.2.0"
}
}
1 change: 1 addition & 0 deletions packages/react/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useNamedQuery';
110 changes: 110 additions & 0 deletions packages/react/src/core/useNamedQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import type {
DefinedUseQueryResult,
QueryKey,
UseQueryOptions,
UseQueryResult,
} from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';

type ExcludeData<T> = Omit<T, 'data'>;

type NamedUseQueryResult<
TName extends string,
TQueryFnData = unknown,
TError = unknown,
> = ExcludeData<UseQueryResult<TQueryFnData, TError>> & {
[key in TName]: UseQueryResult<TQueryFnData, TError>['data'];
};

type DefinedNamedUseQueryResult<
TName extends string,
TQueryFnData = unknown,
TError = unknown,
> = ExcludeData<DefinedUseQueryResult<TQueryFnData, TError>> & {
[key in TName]: DefinedUseQueryResult<TQueryFnData, TError>['data'];
};

function createProxyHandler<
TName extends string,
TData = unknown,
TError = unknown,
>(name: TName) {
const handlers: ProxyHandler<UseQueryResult<TData, TError>> = {
get(target, prop) {
if (prop === name) {
return target.data;
}

return Reflect.get(target, prop);
},
};

return handlers;
}

/**
* When initialData is not provided "data" will be always TQueryFnData | undefined.
* It might need some type checking to be sure that the data is not undefined.
*/
export function useNamedQuery<
TName extends string,
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
name: TName,
options: Omit<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'initialData'
> & { initialData?: () => undefined },
): NamedUseQueryResult<TName, TData, TError>;

/**
* When initialData is provided "data" will be always TQueryFnData.
* Never undefined.
*/
export function useNamedQuery<
TName extends string,
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
name: TName,
options: Omit<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'initialData'
> & { initialData: TQueryFnData | (() => TQueryFnData) },
): DefinedNamedUseQueryResult<TName, TData, TError>;

/**
* useNamedQuery is a wrapper for useQuery that allows you to override the "data" property with a custom name.
*
* @param name a identifier to override "data" property with this name
* @param options UseQueryOptions
* @returns useQuery
*/
export function useNamedQuery<
TName extends string,
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
name: TName,
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
): NamedUseQueryResult<TName, TData, TError> {
const query = useQuery(options);

const proxy = useMemo(() => {
return new Proxy(query, createProxyHandler(name)) as NamedUseQueryResult<
TName,
TData,
TError
>;
}, [name, query]);

return proxy;
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
import { useQuery } from '@tanstack/react-query';

import { useNamedQuery } from '../core';
import { useFuel } from '../providers';
import { QUERY_KEYS } from '../utils';

export const useAccount = () => {
const { fuel } = useFuel();

const { data, ...queryProps } = useQuery(
[QUERY_KEYS.account],
async () => {
return useNamedQuery('account', {
queryKey: [QUERY_KEYS.account],
queryFn: async () => {
try {
const currentFuelAccount = await fuel?.currentAccount();
return currentFuelAccount || null;
} catch (error: unknown) {
return null;
}
},
{
initialData: null,
},
);

return {
account: data,
...queryProps,
};
initialData: null,
});
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
import { useQuery } from '@tanstack/react-query';

import { useNamedQuery } from '../core';
import { useFuel } from '../providers';
import { QUERY_KEYS } from '../utils';

export const useAccounts = () => {
const { fuel } = useFuel();

const { data, ...queryProps } = useQuery(
[QUERY_KEYS.accounts],
async () => {
return useNamedQuery('accounts', {
queryKey: [QUERY_KEYS.accounts],
queryFn: async () => {
try {
const accounts = await fuel.accounts();
return accounts || [];
} catch (error: unknown) {
return [];
}
},
{
initialData: [],
},
);

return {
accounts: data,
...queryProps,
};
initialData: [],
});
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Asset } from '@fuel-wallet/sdk';
import { useMutation } from '@tanstack/react-query';
import type { Asset } from 'fuels';

import { useFuel } from '../providers';
import { MUTATION_KEYS } from '../utils';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
import type { Asset } from '@fuel-wallet/sdk';
import { useQuery } from '@tanstack/react-query';
import type { Asset } from 'fuels';

import { useNamedQuery } from '../core';
import { useFuel } from '../providers';
import { QUERY_KEYS } from '../utils';

export const useAssets = () => {
const { fuel } = useFuel();

const { data, ...queryProps } = useQuery(
[QUERY_KEYS.assets],
async () => {
return useNamedQuery('assets', {
queryKey: [QUERY_KEYS.assets],
queryFn: async () => {
try {
const assets = (await fuel.assets()) as Array<Asset>;
return assets || [];
} catch (error: unknown) {
return [];
}
},
{
initialData: [],
},
);

return {
assets: data,
...queryProps,
};
initialData: [],
});
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query';
import type { BytesLike } from 'fuels';
import { Address } from 'fuels';
import { useEffect } from 'react';

import { useNamedQuery } from '../core';
import { QUERY_KEYS } from '../utils';

import { useProvider } from './useProvider';
Expand All @@ -16,9 +16,9 @@ export const useBalance = ({
}) => {
const { provider } = useProvider();

const query = useQuery(
[QUERY_KEYS.balance, address, assetId],
async () => {
const query = useNamedQuery('balance', {
queryKey: [QUERY_KEYS.balance, address, assetId],
queryFn: async () => {
try {
// TODO: replace with ETH_ASSET_ID from asset-list package after this task gets done
// https://linear.app/fuel-network/issue/FRO-144/make-asset-list-package-public-and-publish-in-npm
Expand All @@ -32,25 +32,21 @@ export const useBalance = ({
return null;
}
},
{
initialData: null,
enabled: !!provider,
},
);

const listenerAccountFetcher = () => {
query.refetch();
};
initialData: null,
enabled: !!provider,
});

useEffect(() => {
const listenerAccountFetcher = () => {
query.refetch();
};

window.addEventListener('focus', listenerAccountFetcher);

return () => {
window.removeEventListener('focus', listenerAccountFetcher);
};
}, []);
}, [query]);

return {
balance: query.data,
...query,
};
return query;
};
Loading
Loading