| Type | Location | Rule |
|---|---|---|
Generic — no business logic, fully reusable (e.g. Spinner, Icon, ScrollToTop) |
src/@core/components/ |
Must work in any project |
App-specific — knows about EventHub concepts (e.g. FileUpload, PWAPrompts) |
src/components/ |
Can import from store/configs |
Never put domain-specific logic (event titles, ticket statuses) inside
@core/.
src/components/EventCard/
├── index.js ← The component (default export)
├── EventCard.styles.js ← (optional) extracted sx/styled-component objects
└── EventCard.utils.js ← (optional) local pure helpers
For simple single-file components, just index.js is fine.
// src/components/EventCard/index.js
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography'
import Chip from '@mui/material/Chip'
import Icon from 'src/@core/components/icon'
import { formatEventDate } from 'src/@core/utils/formatDate'
/**
* EventCard
* Displays a compact summary of an event for listing pages.
*
* Props:
* event – event object from the store
* onClick – optional click handler
*/
const EventCard = ({ event, onClick }) => {
if (!event) return null
return (
<Card
onClick={onClick}
sx={{ cursor: onClick ? 'pointer' : 'default', '&:hover': { boxShadow: 4 } }}
>
<CardContent>
<Typography variant="h6" fontWeight={600} noWrap>
{event.title}
</Typography>
<Typography variant="body2" color="text.secondary" mt={0.5}>
{formatEventDate(event.start_date)}
</Typography>
<Chip label={event.status} size="small" sx={{ mt: 1 }} />
</CardContent>
</Card>
)
}
export default EventCard| Thing | Convention | Example |
|---|---|---|
| Component | PascalCase | EventCard, TicketBadge |
| Props | camelCase | onClose, isLoading |
| Handlers inside component | handle prefix |
handleSubmit, handleDelete |
| Boolean props | is / has / can prefix |
isLoading, hasError, canEdit |
- Destructure props at the top of the function — never access
props.somethinginline. - Provide defaults for optional props to avoid undefined errors.
- Document every prop in a JSDoc comment above the component.
- Never pass raw session/store objects as props when only a single value is needed.
// ✅ Pass only what the component needs
<EventCard eventId={event.id} title={event.title} status={event.status} />
// ❌ Don't pass the whole object if only 3 fields are used
<EventCard event={entireEventObjectWithBlob} />Use MUI sx prop for most styles. Extract to a styles object when reused:
// Inline sx — fine for one-off styles
<Box sx={{ display: 'flex', gap: 2, p: 2 }}>
// Extracted — for repeated or complex styles
const cardSx = {
borderRadius: 2,
transition: 'box-shadow 0.2s',
'&:hover': { boxShadow: 6 }
}
<Card sx={cardSx}>Use styled() from @mui/material/styles only when you need variant-based styles or shouldForwardProp.
- Data fetching → dispatch in
useEffectin the parent view, not inside leaf components. - UI state (open/closed, active tab) →
useStateis fine inside any component. - Shared logic → extract to
src/hooks/(app) orsrc/@core/hooks/(generic).
| Import | Usage |
|---|---|
src/@core/components/icon |
<Icon icon="tabler:calendar" fontSize={20} /> |
src/@core/components/spinner |
Full-page loading fallback |
src/@core/components/scroll-to-top |
Drop into layout, works automatically |
We use Iconify for icons. Always import from src/@core/components/icon:
import Icon from 'src/@core/components/icon'
// Usage
<Icon icon="tabler:calendar-event" fontSize={24} />
<Icon icon="tabler:ticket" color="primary" />Browse icons at icones.js.org — use tabler: prefix for consistency.
| ✅ Do | ❌ Don't |
|---|---|
| Keep components under 200 lines | Write 500-line god components |
| One component per file | Export multiple components from one file |
| Import MUI components individually | Import from @mui/material barrel (import { ... } from '@mui/material') |
Use react-hot-toast for feedback |
Use alert() or custom inline toast logic |
Wrap forms in react-hook-form |
Use uncontrolled refs for forms |