DEV Community

Stefano Magni
Stefano Magni

Posted on

RouteManager UI coding patterns: React

This is an non-exhaustive list of the coding patterns the WorkWave RouteManager's front-end team follows. The patterns are based on years of experience writing, debugging, and refactoring front-end applications with React and TypeScript but evolves constantly. Most of the possible improvements and the code smells are detected during the code reviews and the pair programming sessions.

(last update: 2021, July)

Prefer repetition over abstraction when dealing with low complexity and boilerplate

Don't get code "smarter" just for the sake of avoiding a bit of duplication. Often, duplication improves readability and gets code ready for future changes.

// ❌ don't
const filterValues = {
  all: 'all',
  winners: 'winner',
  losers: 'loser',
}

function FilterBar(props) {
  const isActive = (filter) => filter === props.currentFilter;
  const filters = Object.values(filterValues);

  return filters.map(filter =>
    <div className={isActive(filter) ? 'active' : ''}>{filter}</div>
  )
}

// ✅ do
function FilterBar(props) {
  const { currentFilter } = props

  return <>
    <div className={currentFilter === 'all' ? 'active' : ''}>all</div>
    <div className={currentFilter === 'winner' ? 'active' : ''}>winner</div>
    <div className={currentFilter === 'loser' ? 'active' : ''}>loser</div>
  </>
}
Enter fullscreen mode Exit fullscreen mode

Avoid "return null" components

The component's parent decides if the component is rendered or not.

// ❌ don't
function Poll() {
  const [pollEnabled, setPollEnabled] = useState(false)

  return <>
    <TogglePoll setPollEnabled={setPollEnabled} />
    <PollConfirmation pollEnabled={pollEnabled} />
  </>
}

function PollConfirmation(props) {
  if(!props.pollEnabled) return null

  return <>Poll is enabled</>
}

// ✅ do
function Poll() {
  const [pollEnabled, setPollEnabled] = useState(false)

  return <>
    <TogglePoll setPollEnabled={setPollEnabled} />
    {pollEnabled && <PollConfirmation  />}
  </>
}

function PollConfirmation() {
  return <>Poll is enabled</>
}
Enter fullscreen mode Exit fullscreen mode

Move long conditions out of the JSX

Components' JSX can get unreadable in a while, always move out long conditions.

// ❌ don't
function Foo() {
  /* ... rest of the code... */

  return <>
    {(cond1 && status === 'bar' || date === today) && <Bar />}
    {(cond2 && cond3 date !== today) && <Baz />}
  </>
}

// ✅ do
function Foo() {
  /* ... rest of the code... */

  const renderBar = cond1 && status === 'bar' || date === today
  const renderBaz = cond2 && cond3 date !== today

  return <>
    {renderBar && <Bar />}
    {renderBaz && <Baz />}
  </>
}
Enter fullscreen mode Exit fullscreen mode

Get JSX combinations/states clear

JSX with a lot ternaries and logic conditions can make simple cases hard to read. The reader will spend a significant amount of mental energies to detect what is rendered and what isn't.

// ❌ don't
function Theme(props) {
  const { theme } = props

  return <>
    {theme === 'dark' ? <div>
      <BlackBackgroundButton />
      <GreyBackgroundButton />
    </div> : <div>
      <WhiteBackgroundButton />
      <SepiaBackgroundButton />
    </div>}

    <ColorsPalette theme={theme} />

    {theme === 'dark' ? <DarkThemeSample /> : <LightThemeSample />}

    {theme === 'light' && <LightThemesList />}
  </>
}

// ✅ do
function Theme(props) {
  const { theme } = props

  switch(theme) {
    case 'dark':
      return <>
        <div>
          <BlackBackgroundButton />
          <GreyBackgroundButton />
        </div>

        <ColorsPalette theme='dark' />
        <DarkThemeSample />
      </>

    case 'light':
      return <>
        <div>
          <WhiteBackgroundButton />
          <SepiaBackgroundButton />
        </div>

        <ColorsPalette theme='light' />
        <LightThemeSample />
        <LightThemesList />
      </>
  }
}
Enter fullscreen mode Exit fullscreen mode

The next step is returning a new, dedicated component, for every switch case.

// ❌ don't
function Theme(props) {
  const { theme } = props

  switch(theme) {
    case 'dark':
      return <>
        <div>
          <BlackBackgroundButton />
          <GreyBackgroundButton />
        </div>

        <ColorsPalette theme='dark' />
        <DarkThemeSample />
      </>

    case 'light':
      return <>
        <div>
          <WhiteBackgroundButton />
          <SepiaBackgroundButton />
        </div>

        <ColorsPalette theme='light' />
        <LightThemeSample />
        <LightThemesList />
      </>
  }
}

// ✅ do
function Theme(props) {
  switch(props.theme) {
    case 'dark':
      return <DarkTheme />

    case 'light':
      return <LightTheme />
  }
}
Enter fullscreen mode Exit fullscreen mode

Prefer CSS Variables to optimize Material UI styles

Material UI injects a lot of styles into the DOM. CSS variables limit the amount of injected styles for dynamic properties.

// ❌ don't
const useStyles = makeStyles({
  foo: props => ({
    backgroundColor: props.backgroundColor,
  }),
});

function MyComponent(props) {
  const classes = useStyles(props);

  return <div className={classes.foo} />
}

// ✅ do
const useClasses = makeStyles({
  foo: { backgroundColor: 'var(--background-color)' }
})

function useStyles(backgroundColor: string) {
  const classes = useClasses()

  const styles = useMemo(() => {
    return {
      foo: { '--background-color': `${backgroundColor}` } as CSSProperties,
    }
  }, [backgroundColor])

  return [classes, styles] as const
}

function MyComponent(props) {
  const [classes, styles] = useStyles(props.backgroundColor)

  return <div className={classes.foo} styles={styles.foo} />
}

Enter fullscreen mode Exit fullscreen mode

Check if the component is still mounted in async callbacks

A component can be unmounted before an asynchronous execution completes, resulting in unexpected behaviors and errors.

// ❌ don't
export function Component() {
  const [deleted, setDeleted] = useState(false)

  const onClick = useCallback(async () => {
    await deleteItemRequest()
    setDeleted(true)
  }, [])

  return <>
    <button onClick={onClick}>Delete</button>
    {deleted && <>Deleted</>}
  </>
}

// ✅ do
export function Component() {
  const [deleted, setDeleted] = useState(false)
  const unmounted = useRef(false)

  const onClick = useCallback(async () => {
    await deleteItemRequest()

    if (unmounted.current) return

    setDeleted(true)
  }, [])

  useEffect(() => {
    return () => {
      unmounted.current = true
    }
  }, [])

  return <>
      <button onClick={onClick}>Delete</button>
      {deleted && <>Deleted</>}
    </>
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)