0
\$\begingroup\$

I built a table component for an admin panel app using React and Typescript.

This admin panel is for a Discord bot where you can do your moderation solely through a web app. This table lists the members in the Discord server. There are three sections: All Members, Muted, and Banned.

I would like to know how I could improve the code for this component:

import type { FunctionComponent} from "react";
import { useState } from "react";
import BanModal from "./BanModal";
import { ChangeMuteModal } from "./ChangeMuteModal";
import { MemberListRow } from "./MemberListRow";
import { MuteModal } from "./MuteModal";
import UnbanModal from "./UnbanModal";

type MemberListTableProps = {
    members: Array<Object>,
    accentColor: string,
    tableType: string,
}

export const MemberListTable: FunctionComponent<MemberListTableProps> = (
{members, tableType}: MemberListTableProps) => {
    const [selectedRows, setSelectedRows] = useState<string[]>([]);

    const [showMuteModal, setShowMuteModal] = useState(false);
    const [showChangeMuteModal, setShowChangeMuteModal] = useState(false);
    const [showBanModal, setShowBanModal] = useState(false);
    const [showUnbanModal, setShowUnbanModal] = useState(false);
    
    return  <>
                {showMuteModal ? <MuteModal openHook={[showMuteModal, setShowMuteModal]} /> : <></>}
                {showChangeMuteModal ? <ChangeMuteModal openHook={[showChangeMuteModal, setShowChangeMuteModal]} /> : <></>}
                {showBanModal ? <BanModal /> : <></>}
                {showUnbanModal ? <UnbanModal /> : <></>}
                <div>
                    {tableType === "members" ? 
                        <div className="flex gap-1">
                            <input type="search" id="default-search" 
                            className="inline-block text-sm text-gray-900 bg-gray-50 
                            rounded-lg border border-gray-300 focus:ring-green-700 
                            focus:border-green-700"
                            placeholder="Filter" />
                            {selectedRows.length > 0 ? 
                                <div className="flex gap-1">
                                    <button type="button" className="button" onClick={() => setShowMuteModal(true)}
                                        style={{backgroundColor: "#EAB308"}}>Mute</button>
                                    <button type="button" className="button" onClick={() => setShowBanModal(true)}
                                        style={{backgroundColor: "#DC2626"}}>Ban</button>
                                </div>
                            : <></>}
                        </div>
                    : tableType === "muted" ?
                        <div className="flex gap-1">
                            <input type="search" id="default-search" 
                            className="inline-block text-sm text-gray-900 bg-gray-50 
                            rounded-lg border border-gray-300 focus:ring-yellow-400 
                            focus:border-yellow-400"
                            placeholder="Filter" />
                            {selectedRows.length > 0 ? 
                                <div className="flex gap-1">
                                    <button type="button" className="button" onClick={() => setShowChangeMuteModal(true)}
                                        style={{backgroundColor: "#EAB308"}}>Change Mute</button>
                                    <button type="button" className="button" onClick={() => setShowBanModal(true)}
                                        style={{backgroundColor: "#DC2626"}}>Ban</button>
                                </div>
                            : <></>}
                        </div>
                    : tableType === "banned" ?
                        <div className="flex gap-1">
                            <input type="search" id="default-search" 
                            className="inline-block text-sm text-gray-900 bg-gray-50 
                            rounded-lg border border-gray-300 focus:ring-red-600 
                            focus:border-red-600"
                            placeholder="Filter" />
                            {selectedRows.length > 0 ? 
                                <div className="flex gap-1">
                                    <button type="button" className="button" onClick={() => setShowUnbanModal(true)}
                                        style={{backgroundColor: "#DC2626"}}>Unban</button>
                                </div>
                            : <></>}
                            
                        </div>
                    : <></>}
                </div>
                <table className="table-auto border-collapse border border-slate-500">
                    <thead>
                        <tr>
                            <th className="border border-slate-300 pl-3 py-2 text-left"></th>
                            <th className="border border-slate-300 pl-3 py-2 text-left">Nickname</th>
                            <th className="border border-slate-300 pl-3 py-2 text-left">Username</th> 
                            <th className="border border-slate-300 pl-3 py-2 text-left">User ID</th>
                        </tr>
                    </thead>
                    <tbody>
                        {members.map((user: any, index) => (
                            tableType === "members" ?
                                <MemberListRow key={index}
                                nickname={user.nickname}
                                username={user.username}
                                userID={user.userID}
                                accent="green"
                                onSelect={() => setSelectedRows([...selectedRows, user.username])}
                                onDeselect={() => setSelectedRows(
                                    selectedRows.filter(row => row !== user.username)
                                )} />
                            : tableType === "muted" ?
                                <MemberListRow key={index}
                                nickname={user.nickname}
                                username={user.username}
                                userID={user.userID}
                                accent="yellow"
                                onSelect={() => setSelectedRows([...selectedRows, user.username])}
                                onDeselect={() => setSelectedRows(
                                    selectedRows.filter(row => row !== user.username)
                                )} />
                            : tableType === "banned" ?
                                <MemberListRow key={index}
                                nickname={user.nickname}
                                username={user.username}
                                userID={user.userID}
                                accent="red"
                                onSelect={() => setSelectedRows([...selectedRows, user.username])}
                                onDeselect={() => setSelectedRows(
                                    selectedRows.filter(row => row !== user.username)
                                )} />
                            : <></>
                        ))}
                    </tbody>
                </table>
            </>
}
```
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

First, using too many useState hooks which do the same thing. You can solve this by adding making use of object mapping:

// also do not pass setState as a prop, not a good practice

const modals = {
   mute: <MuteModal />,
   unmute: <ChangeMuteModal/>,
   ban: <BanModal />,
   unban: <UnbanModal />
}

// and then modify useState like this
const [modal, setModal] = useState({open: false, type: ''});

// now whenever you want to open a specific modal use this handler
const handleOpenModal = (modal) => e => {
   e.preventDefault() // assuming its an onClick func
   setModal({ open: true, type: modal });
}

// close
const handleCloseModal = (e) => {
   e.preventDefault() // assuming its an onClick func
   setModal({open: false, type: ''})
}

// in your return use it like this
{modal?.open ? modals[modal?.type || 'give some default here'] : '' }

This was one issue, I also see problems regarding writing the same code again in your return statements with tableType. Try to convert it into one component and pass a prop of tableType to it. Can even pass click handlers. And with object mapping, this will become easier.

Another is your tailwind CSS, it's very tempting to write CSS inline but not good when your project becomes big. You also have the same CSS everywhere. like:

className="inline-block text-sm text-gray-900 bg-gray-50 
                            rounded-lg border border-gray-300 focus:ring-green-700 
                            focus:border-green-700"

 className="inline-block text-sm text-gray-900 bg-gray-50 
                            rounded-lg border border-gray-300 focus:ring-yellow-400 
                            focus:border-yellow-400"

The only difference is that 'focus' CSS. Try to keep a const like

const tableCss = {
   default: 'inline-block text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300',
   // can add more here
}

// and use it like this
className={`${tableCss['default']} focus:ring-green-700 focus:border-green-700`}

// you will realise the power of it when you have a team and you don't have to explain your code too much

I also believe it's better to move things like input and buttons to UI and use the tailwind approach as I mentioned before. Trust me, you'll save more time and solving bugs will be more fun.

Let me know if this helps.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.