---
# System prepended metadata

title: DMusic + Superyacht VFT Viewer Review

---

# DMusic + Superyacht VFT Viewer Review

[https://github.com/NoumenaDigital/superyacht-nft-viewer](https://github.com/NoumenaDigital/superyacht-nft-viewer)

[https://github.com/NoumenaDigital/dmusic-nft-viewer](https://github.com/NoumenaDigital/dmusic-nft-viewer)

## Overview

Both apps are pretty much the exact same — but this document was written while reviewing Superyacht

**In code snippet suggestions `+` are for additions `-` are for deletions**

## The Not so Good 😣

- Highly recommend converting to TypeScript
- Setup docs could be improved — but booted up no problem
- Leftovers from CRA - code, config, docs & images
    - `manifest.json`
    - `logo192.png`
    - `logo512.png`
    - `README.md`
- No CSS reset
- No linter - recommend ESLint & prettier
- All styles are in one file - We recommend making this more modular by breaking up the Sass into smaller bits - usually this is done by component
- `import`/`export` setup could be improved with a few more directories and `index.js` files
    - Also if the Sass is broken up by component a few `index.scss` imports wouldn't be a bad idea
    
    For example:
    
    ```bash
    > ls components
     MyComponent/ index.ts index.sass
    > cd components/MyComponent
    > ls .
     MyComponent.jsx MyComponent.sass index.ts
    ```
    
- Recommend to use relative CSS units over absolute CSS units
- Declared but not used - address is not used
    
    ```jsx
    // NFTList.js line 5
    let { chain, address } = useParams();
    ```
    
- Should be using semantic HTML
    
    ```html
    <!-- App.js line 11 -->
    - <div className="app">
    + <main className="app">
    
    <!-- Header.js line 8-10 -->
    + <nav>
        <Link to="/">
          <LogoIcon logoClass="header-logo" />
        </Link>
    + </nav>
    ```
    
- Unnecessary use of `let` - never reassigned
    
    ```jsx
    // SearchAddress.js line 7
    let navigate = useNavigate();
    
    // NFTDetails.js line 7
    let { chain, hash, id } = useParams();
    
    // NFTList.js line 5
    let { chain, address } = useParams();
    ```
    
- Unnecessary clean up function
    
    ```jsx
    // NFTListPage.js line 26
    return () => {};
    
    // NFTListPage.js line 31
    return () => {};
    
    // NFTDetails.js line 15
    return () => {};
    ```
    
- Unnecessary comments - code could be self documenting
    
    ```jsx
    // NFTListPage.js line 48-57
    - // loading state
    if (loading)
     return <span className="info-messages">Loading please wait...</span>;
    
    - // error state
    - if (error || !filteredItems || filteredItems.length === 0)
    + const isError = error || !filteredItems || filteredItems.length === 0;
    + if (isError)
        return <span className="info-messages">No NFTs on this address</span>;
    
    - // regular data workflow
    return <NFTList nfts={filteredItems} />;
    
    // NFTDetails.js
    - // loading state
    if (loading)
      return <span className="info-messages">Loading please wait...</span>;
    
    - // error state
    if (error)
      return <span className="info-messages">No NFT on this address</span>;
    
    - // regular data workflow
    return (
    ```
    
- Catch but no UI error handling
    
    ```jsx
    // NFTDetails.js line 39-45 - will result in empty description in sad path
    const parsedDescription = (() => {
      try {
        return JSON.parse(description);
      } catch (err) {
        return {};
      }
    })();
    ```
    
- Could make better use of object destructuring of `details` in `<NFTList />`
- Code could me more modular
    - `renderData` functions could be their own component
    - store could have `slices/` and `selectors/` directories
    
    ```jsx
    // NFTDetails.js line 22-26 - could be a helper func (difficult to test as is)
    const renderAddress = (address) => {
      return address.length > 8
        ? address.substring(0, 4) + "..." + address.substring(address.length - 4)
        : address;
    };
    ```
    
- Slice reducer could be using `extraReducers` and `createAsyncThunk` from RTK
    - [https://redux-toolkit.js.org/api/createAsyncThunk](https://redux-toolkit.js.org/api/createAsyncThunk)
- Could use `await` in slice fetches
    - I recommend moving over to `extraReducers` and `createAsyncThunk` from RTK but if you’re going to keep this approach I recommend making use of the `async`/`await` syntax
    - Below shows a pattern in `detailsSlice.js` that also exists in `listSlice.js`
    
    ```jsx
    // detailsSlice.js line 34-48
    export function fetchDetails(params) {
      return async (dispatch) => {
        dispatch(setLoading());
    -    api
    -     .get(
    -       `nft-viewer/api/v1/chains/${params.chain}/contracts/${params.hash}/nfts/${params.id}`
    -     )
    -     .then((response) => {
    -       dispatch(setDetails(response.data));
    -     })
    -     .catch(() => {
    -       dispatch(setError());
    -     });
    +     try {
    +       const { data } = await api.get(
    +       `nft-viewer/api/v1/chains/${params.chain}/contracts/${params.hash}/nfts/${params.id}`				
    +       )
    +
    +       dispatch(setDetails(data));
    +     } catch () {
    +       dispatch(setError())
    +     }
      };
    }
    ```
    

## The Really not so Good 😱

- `.env` and other config in `nomad/` pushed to the repo
    - Create a `.env.example` and remove config from repo
- No tests
- Hacky page setup for `/`
    - Create a `pages` directory
    
    ```jsx
    // App.js line 15
    <Route path="/" element={<SearchAddress />} /> // wrap in a Home.js
    
    // SearchAddress.js line 32 - This could be handled in a Home.js page wrapper
    useEffect(() => {
        document.body.classList.add("home-page");
        return () => {
          document.body.classList.remove("home-page");
        };
      }, []);
    ```
    
- `index` as react key - can lead to issues
    
    ```jsx
    // NFTDetails.js Line 46-55
    return attributes.map((attribute, index) => {
      return (
        parsedDescription[attribute] && (
          <div key={index}>
            <span className="attribute">{attribute}: </span>
            <span className="value">{parsedDescription[attribute]}</span>
          </div>
        )
      );
    });
    
    // NFTList.js lines 21-47
    {nfts.map((nft, index) => {
      return (
        <tr key={index}>
          <td>
            <img
              onError={addDefaultSrc}
              src={
                window.__RUNTIME_CONFIG__.REACT_APP_BASE_ENDPOINT +
                  "ipfs-store/api/v1/ipfs/" +
                   nft.ipfs_image_hash || `/album.png`
              }
              className="thumbnail"
              alt="thumbnail"
            />
          </td>
          <td>{nft.name}</td>
          <td>
            <Link
              to={`/chains/${chain}/contracts/${nft.contract_hash}/nfts/${nft.id}`}
              className="link"
            >
              VIEW
            </Link>
          </td>
        </tr>
      );
    })}
    ```