// Beginning Of File - imports always at the top
import * as React from 'react'
import { trackEvent } from '@app/lib/AnalyticsUtils'
import UserList from './UserList'
// A lot of other functions and other stuff in the middle of the file. These aren't usually as important
// so finding them immediately isn't an issue.
function doesAThing(): boolean { // Adding a return type can help make the typescript compilation faster.
return true
}
// include your prop types here, right before the component!.
interface UserSelectionProps {
initialSelection: Users[];
users: Users[];
// Optional properties at the bottom of this list.
// Functions have `on` appended to them to help easily identify/separate.
onSelect?: (user: User) => void;
onClear?: () => void;
// Prefer boolean to have `is` appendage.
isMultiSelection?: boolean;
}
/**
* Some long documentation for your main component...
*
* This default export should also be at the end of the file. Much easier to find it this way!
*/
export default function UserSelection({ // one-line: 'export default function' for your main component
users,
initialSelection,
onSelect = () => {},
onClear = () => {},
isMultiSelection = false,
}: UserSelectionProps) {
// Name functions that handle an event with the `handle` prefix.
const handleSelection = (user: User): void => {
onSelect(user);
trackEvent(AnalyticsEvents.SELECT_USER, { userId: user.id })
}
return (
<UserList
users={users}
selection={selection}
onSelect={handleSelection}
onClear={onClear}
/>
)
}
All the components you write should be functional components. Now that hooks are a thing, functional components can store state. They are more compact and easier to understand, render faster than class components, and you can abstract stateful behaviors from them.
// bad
class Listing extends React.Component {
render() {
return <div>{this.props.hello}</div>;
}
}
// bad (relying on function name inference is discouraged)
const Listing = ({ hello }) => (
<div>{hello}</div>
);
// good
function Listing({ hello }) {
return <div>{hello}</div>;
}
export default function SomeComponent({ firstName, lastName }) {
return <div>{firstName} {lastName}</div>
}
// bad - Where does 600 come from? what does this mean in the context ofthe delay function?
delay(doSomething, 600)
// Good - A comment about how we arrived at this measurement is useful!
// This is the time it takes for our parent components on mount spring animation to complete.
const CARD_ANIMATION_DELAY_MS = 600
delay(doSomething, ANIMATION_DELAY_MS)
Use PascalCase for React components and camelCase for their instances.
// bad
import reservationCard from './ReservationCard';
// good
import ReservationCard from './ReservationCard';
// bad
const ReservationItem = <ReservationCard />;
// good
const reservationItem = <ReservationCard />;
Use the filename as the component name. For example, ReservationCard.js should have a reference name of ReservationCard. However, for root components of a directory, use index.js as the filename and use the directory name as the component name.
// bad
import Footer from './Footer/Footer';
// bad
import Footer from './Footer/index';
// good
import Footer from './Footer';
Avoid using DOM component prop names for different purposes.
Why? People expect props like style and className to mean one specific thing. Varying this API for a subset of your app makes the code less readable and less maintainable, and may cause bugs.
// bad
<MyComponent style="fancy" />
// bad
<MyComponent className="fancy" />
// good
<MyComponent variant="fancy" />
Always use camelCase for prop names.
// bad
<Foo
UserName="hello"
phone_number={12345678}
/>
// good
<Foo
userName="hello"
phoneNumber={12345678}
/>
Avoid using an array index as key prop, prefer a unique ID.
// bad
{todos.map((todo, index) =>
<Todo
key={index}
{...todo}
/>
)}
// good
{todos.map(todo => (
<Todo
key={todo.id}
{...todo}
/>
))}
Hooks provide a better paradigm for encapsulating behavior, as they are basically just functions that can return something. It's much easier to reason about a hook and what it does as compared to a higher order component.
// bad - where does params come from? do I pass in this prop?
function NavLink({ params, label }) {
// ... logic
}
export default withRouter(NavLink)
// later
<NavLink label="Foo" />
// good - now we know that there is only one prop
function NavLink({ label }) {
const params = useRouterParams()
// ... logic
}
<NavLink label="Foo" />
// bad - this is pretty hard to read, and proper order could be better.
<button type="submit" disabled onClick={() => null} className="a-long-class-name">
Click here
</button>
// okay
<button type="submit" className="a-long-class-name" onClick={() => null}>
Click here
</button>
// good
<button className="a-long-class-name">Click here</button>
// best
<button
className="long-class-name"
disabled={loading}
onClick={() => null}
type="submit"
>
Click here
</button>
// bad - try not to nest components inside other components. Prefer to abstract behavior.
function Component({ label, options, onSelect }) {
const label = (<div>Some Big Long Contrived Example Component {label}</div>);
const selection = (
<div>
{
options.map((option) => (<div onClick={() => onSelect(option)}>{option.name}</div>))
}
</div>
);
return <MyComponent>{label}{selection}</MyComponent>;
}
// good, much more separated concerns
function ContrivedExampleBody({ label }) {
return (<div>Some Big Long Contrived Example Component {label}</div>)
}
function ContrivedExampleSelection({ options, onSelect }) {
return (
<div>
{
options.map((option) => (<div onClick={() => onSelect(option)}>{option.name}</div>))
}
</div>
)
}
function Component({ label, options, onSelect }) {
return (
<MyComponent>
<ContrivedExampleBody label={label} />
<ContrivedExampleSelection options={options} onSelect={onSelect} />
</MyComponent>
);
}
// bad - Hard to read, and know what the markup structure is like.
<div className="example"><span class="highlight">Bad</span> example</div>
// good - Structure is more clear
<div className="example">
<span className="highlight">Good</span> example
</div>