Try   HackMD
tags: web

Use react-query as state manager in NextJS

Managing the server side data (API request ..etc) is always a pain. We need to handle all possible case when we start fetching the data.

  1. UseEffect: To decide when to update data
  2. Many useState: To handle all kind of possible fetch states in order to show it on page (isLoading/ error/ refetch etc)
  3. Create custom hook and provider to feed it to all compoents

react-query is a perfect tool to make our life easier in such case.

Start with react-query with nextJS

In this case, we will utilize our existing project of "solana-dapp" since managing solana blockchain's complicated state is alwasy a pain.
We will make a useToken custom hook by utilizing useQuery to let all components to access registered splToken globally.

Install react-query

yarn add react-query # or
npm install react-query

The store lib and create the hook

Create src/store/tokenStore.tsx file. We will make the simple version first.

Simple version

// src/store/tokenStore.tsx
import { TokenListProvider, TokenInfo } from "@solana/spl-token-registry";
import { useQuery } from "react-query";

export async function fetchSPLTokens() {
  let rawTokenList = await new TokenListProvider().resolve();
  return rawTokenList.filterByClusterSlug("mainnet-beta").getList();
}
export function useToken() {
  return useQuery("tokens", fetchSPLTokens, {
    refetchOnMount: false,
  });
}

Super easy, now all components can access the data by using useToken. And useQuery will hanle all possible aync data fetching situation for us and return state to us. (ex. isLoading, error, status etc)

Now we can try to access it on a component called SearchToken

So will need to have components/searchToken/index.tsx

import { TokenInfo } from "@solana/spl-token-registry";
import { useRef, useState } from "react";
import { useToken } from "../../store/tokenStore";

export default function SearchToken() {
  
  const { data: tokenList, isLoading, isFetching } = useToken();
  const [selectedToken, setSelectedToken] = useState<TokenInfo>();
  const [filteredTokens, setFilteredTokens] = useState<TokenInfo[]>();
  const inputRef = useRef<HTMLInputElement>(null);
  function handleInput() {
    let filtered = tokenList?.filter((t) =>
      t.symbol.toUpperCase().startsWith(inputRef.current!.value.toUpperCase())
    );
    setFilteredTokens(filtered);
  }

  function handleSelectToken(token: TokenInfo) {
    setSelectedToken(token);
  }
  if (isLoading || isLoading) {
    return <div>Loading</div>;
  }

  return (
    <>
      <div
        style={{
          height: "50vh",
        }}
      >
        <div style={{ height: "50vh", overflow: "scroll" }}>
          <input
            style={{ position: "fixed" }}
            ref={inputRef}
            onChange={handleInput}
            placeholder={"Enter token symbol"}
          />
          {!isLoading && (
            <div>
              {filteredTokens &&
                filteredTokens.map((token, id) => (
                  <div key={id} onClick={() => handleSelectToken(token)}>
                    {token.symbol}
                  </div>
                ))}
            </div>
          )}
        </div>
        <div style={{ height: "30vh" }}>
          {selectedToken && (
            <div>
              <div> Mint: {selectedToken.address}</div>
              <div>Decimal: {selectedToken.decimals}</div>
              <div>Symbol: {selectedToken.symbol}</div>
              <div>Name: {selectedToken.name}</div>
              <div>Token image: {selectedToken.logoURI}</div>
            </div>
          )}
        </div>
      </div>
    </>
  );
}

This is a very plain unstyled element, but you get how it works. We just make use of the useToken custom hook to get the data.