DEV Community

Cover image for 8 Best Practices for React.js Component Design

8 Best Practices for React.js Component Design

Blossom Babs on July 17, 2023

React is one of the most popular JavaScript libraries for building user interfaces, and one of the reasons it gained so much popularity is its Comp...
Collapse
 
jacksonkasi profile image
Jackson Kasi

hi @lukeshiru it's good to go with typescript, but inn our team just few people only know typescript!
So as for now we continue the prop-types but if i component with PropTypes.oneOf this is not auto suggest. so i feel it's useless ☚ī¸

This is my code! can u give me suggestion or idea why oneOf not auto suggest when i import & use this component?

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Link } from 'react-router-dom';

const Button = ({
  children,
  variant = 'default',
  size = 'md',
  color,
  shape = 'round',
  disabled = false,
  iconStart,
  iconEnd,
  active = false,
  loading = false,
  block = false,
  className,
  position = 'center',
  link,
  target,
  ...props
}) => {
  const positionClasses = classNames({
    '!justify-start': position === 'start',
    '!justify-center': position === 'center',
    '!justify-end': position === 'end',
  });

  const buttonClasses = classNames(
    'inline-flex items-center justify-center font-poppins font-semibold transition-all duration-200  transform active:scale-95',
    positionClasses,
    {
      'bg-primary-blue text-white border-2 border-transparent hover:bg-primary-blue':
        variant === 'default',
      'bg-transparent text-primary-blue border-2 border-primary-blue hover:text-primary-blue':
        variant === 'twoTone',
      'bg-transparent text-current': variant === 'plain',
      'bg-white text-primary-blue border-2 border-primary-blue hover:bg-primary-blue hover:text-white':
        variant === 'outline',
      'px-5 py-2 text-base sm:leading-8 sm:text-lg': size === 'md',
      'px-4 py-2 text-sm sm:leading-7 sm:text-base': size === 'sm',
      'px-3 py-1 text-xs sm:leading-6 sm:text-sm': size === 'xs',
      'px-6 py-3 text-xl sm:leading-9 sm:text-2xl': size === 'lg',
      'rounded-lg': shape === 'round',
      'rounded-full': shape === 'circle',
      'rounded-none': shape === 'none',
      'cursor-not-allowed': disabled || loading,
      'w-full': block,
    },
    color && `text-${color} border-${color}`,
    !disabled &&
      !loading &&
      color &&
      variant === 'outline' &&
      `hover:bg-${color} hover:text-white`,
  );

  return link ? (
    <Link
      to={link}
      target={target}
      className={`${buttonClasses} ${className}`}
      role="button"
      aria-disabled={disabled || loading}
      {...props}
    >
      {iconStart && <span className="mr-2">{iconStart}</span>}
      {loading ? 'Loading...' : children}
      {iconEnd && <span className="ml-2">{iconEnd}</span>}
    </Link>
  ) : (
    <button
      className={`${buttonClasses} ${className}`}
      disabled={disabled || loading}
      {...props}
    >
      {iconStart && <span className="mr-2">{iconStart}</span>}
      {loading ? 'Loading...' : children}
      {iconEnd && <span className="ml-2">{iconEnd}</span>}
    </button>
  );
};

Button.propTypes = {
  children: PropTypes.node,
  variant: PropTypes.oneOf(['solid', 'twoTone', 'plain', 'default', 'outline']),
  position: PropTypes.oneOf(['start', 'center', 'end']),
  size: PropTypes.oneOf(['lg', 'md', 'sm', 'xs']),
  color: PropTypes.string,
  shape: PropTypes.oneOf(['round', 'circle', 'none']),
  disabled: PropTypes.bool,
  icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  active: PropTypes.bool,
  loading: PropTypes.bool,
  block: PropTypes.bool,
  link: PropTypes.string,
  target: PropTypes.string,
};

export default Button;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mickmister profile image
Michael Kochell

Typescript is definitely worth learning. Your teammates that currently don't know Typescript will start writing much better code if they learn it! Also you can combine Typescript with runtime prop types checks if you're using babel github.com/milesj/babel-plugin-typ...

Thread Thread
 
jacksonkasi profile image
Jackson Kasi

it's looks interesting!

Collapse
 
mickmister profile image
Michael Kochell

For item #1, I typically use arrow functions, but that doesn't allow you to have a one liner for a default export. i.e. this invalid syntax:

export default const MyComponent = () => {
Enter fullscreen mode Exit fullscreen mode

But this works:

export default function MyComponent() {
Enter fullscreen mode Exit fullscreen mode

and this works:

const MyComponent = () => {
}

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

I prefer the one liner so I typically choose function approach for default exports. You can still do named exports with const though:

export const MyComponent = () => {
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jake0011 profile image
JAKE

i enjoyed every bit of the article and even more the discussions in the comments.

thank you all guys, it’s helping my newbie career.

Collapse
 
mickmister profile image
Michael Kochell

You can have a nameless default export like that, but then your editor doesn't know how to import it by name when using autocomplete to resolve something that's not imported yet.

Tho I generally advocate to not use default exports at all, and just export everything with a name (makes refactoring way easier).

That makes sense, though I like the clean look on the import side in the case of default exports. When I read the top of a file, I generally think that named exports in an import statement are "extras" of what that file is exporting, whereas the default is the "main" thing to import. It's also slightly less things for our eyes to parse when scanning imports, when the curly braces are absent. Definitely all preference though. I think pragmatically have them all named makes sense, as you suggested.

Thread Thread
 
fjones profile image
FJones

You can have a nameless default export like that, but then your editor doesn't know how to import it by name when using autocomplete to resolve something that's not imported yet.

This is very much an IDE issue, not a code issue. There's no technical reason not to autocomplete the folder or file name for the import (and, I could be wrong, but I thought jetbrains IDEs did exactly that?).

That said, I'm personally not a fan of immediate default exports anyway. Having a named const that later gets default-exported allows, for instance, attaching further properties to the exported object without needing to rearrange code.

Thread Thread
 
mickmister profile image
Michael Kochell

This is very much an IDE issue, not a code issue.

Totally agree. Nice points here, thank you 👍

Collapse
 
dev_bre profile image
Andy

Great overview article. I really liked you mentioned container components, might have been good to expand on that and mention custom hooks to handle logic and state.

Collapse
 
blossom profile image
Blossom Babs

Great suggestion, I should expand on this

Collapse
 
stretch0 profile image
Andrew McCallum • Edited

Some great tips in there.

I'd argue point 1 is negligible though. I don't think mixing between how you define your components makes it harder to read. Messy maybe, but hard to read, no.

As @mickmister alluded to, different ways of defining them affect how you export and ultimately import your component so maybe allowing both patterns is advantageous in different scenarios?

 
jacksonkasi profile image
Jackson Kasi

wow! really thanks @lukeshiru 😊

Thread Thread
 
blossom profile image
Blossom Babs

@lukeshiru thank you for introducing JSdocs! Great perspective.

Typescripts is the default type safety for most websites building for prop-types is good to know for those who are yet to adopt TS

Collapse
 
devnaqvi profile image
devnaqvi

The reason you shouldn't mutate props is that props are immutable in the user interface.

Collapse
 
chantal profile image
Chantal • Edited

That's right @lukeshiru and Typescript makes things easy and smart.