Skip to content

React

Main render function

This is our own convention - not something React requires, but we've found it really helps with code quality and readability.

Why we like this approach:

  • Easier to read: Components have a predictable structure, so you always know where to look
  • Consistency across the team: Everyone knows where to find the main rendering logic
  • Faster code reviews: No time wasted figuring out how someone structured their component
  • Clean separation: Keep your state/business logic separate from your rendering logic
  • Scoped variables: Variables that are only needed for rendering stay contained in the render function

Impact on performance:

  • This pattern doesn't slow anything down, and has no impact on performance
  • Modern JavaScript engines handle the extra function call like it's nothing
  • React gets the same JSX either way, so your DOM looks identical

Function declarations vs Arrow functions

We prefer function declarations over arrow functions.

Function declaration (preferred):

async function save(formData: any) {

vs

Arrow function:

const save = async (formData: any) => {

Why we like this approach:

  • Easier to read: Function declarations stand out clearly from variable assignments
  • Easier to code: Less syntax to think about - just write function name() and you're done
  • Familiar pattern: Aligns with how functions work in most other programming languages
  • Better separation: Creates visual distinction between function declarations and constants/variables defined within functions

Component props

Why we destructure non-function props only

We prefer deconstructing non-function props at the top of the component, while keeping function props prefixed with props..

This approach prioritizes code clarity and maintainability, making it immediately obvious which variables are data and which are functions passed from parent components.

  • Consistent visual pattern: The pattern creates a visual separation: destructured variables for data/state, and props. prefix for actions/callbacks. This makes it easy to scan code and understand what's local vs. passed-in.
  • Clear function origin identification: By keeping function props as props.functionName(), it's immediately clear that these are passed-in functions rather than local component functions. This distinction is valuable during code reviews and debugging.
  • Prevents naming conflicts: Not destructuring function props avoids potential naming conflicts between passed-in functions and local component functions that might have similar names.

Example pattern

function MobileHeader(props: IProps) {
  // Destructure data/state props for cleaner usage
  const { isDesktop, isSidebarOpen, backgroundColor, textColor } = props;

  /* -------------------------------- */
  /* RENDER METHODS */
  /* -------------------------------- */

  function render() {
    return (
      <button
        onClick={() => {
          // Keep function calls with `props.` prefix for clarity
          props.setIsSidebarOpen(true);
        }}
      >
        Click me
      </button>
    );
  }

  return render();
}