# 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> ); })} ```