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();
}