Pro React Documentation

website·Indexed 29 days ago

https://surjitsahoo.github.io/pro-react

A guide to professional React development focusing on architecture, performance, and coding principles. Covers topics such as folder structure, separation of concerns, the Open-Close Principle, Single Responsibility Principle, state management pitfalls with Context, and common useEffect mistakes.

Tokens
10.4K
Snippets
41
Records
60
Agent score
73%

What's inside Pro React

  1. Avoid defining nested components in React

    Do not define a component function inside another component function. When a component is defined inside another, the inner component is re-created on every render of the parent. This causes the inner component to be treated as a brand new component type, leading to complete unmounting and remounting of the subtree, which destroys local state and degrades performance.

    Instead, define components at the top level (outside the parent) and pass necessary data via props.

    // ❌ BAD: Nested components are re-created on every Parent render
    function Parent(props) {
      const [state, setState] = useState(props.initialData);
    
      function Child1() {
        return <div>Child1: {state}</div>;
      }
    
      return <Child1 />;
    }
    
    // ✅ GOOD: Components defined outside the parent
    function Child1({ state }) {
      return <div>Child1: {state}</div>;
    }
    
    function Parent(props) {
      const [state, setState] = useState(props.initialData);
    
      return <Child1 state={state} />;
    }
  2. Improve user experience by caching API data

    To avoid showing loading screens every time a user navigates between pages, implement API caching. Instead of triggering a new API call on every page visit, return existing cached data. New API requests should only be triggered when necessary, such as during the first visit to a page or when request parameters (e.g., filters or IDs) change.
  3. Organize React project folder structure for components and features

    Every component or feature should reside in its own dedicated folder to keep all related assets together. This ensures a clean public API via an index.ts file and keeps styles, tests, and documentation colocated with the logic.

    Each folder should contain:

    • The main component/feature file (.tsx)
    • Styles (.css or .scss)
    • Test files (.test.tsx)
    • Storybook files (.stories.tsx)
    • An index.ts file to export the public API
    • A helpers folder for data transformation logic (primarily used at the feature level)
    .
    ├── components/
    │   ├── button/
    │   │   ├── button.tsx
    │   │   ├── button.css
    │   │   ├── button.test.tsx
    │   │   ├── button.stories.tsx
    │   │   └── index.ts
    │   └── modal/
    │       ├── modal.tsx
    │       ├── modal.css
    │       ├── modal.test.tsx
    │       ├── modal.stories.tsx
    │       └── index.ts
    └── features/
        ├── feedbackForm/
        │   ├── feedbackForm.tsx
        │   ├── feedbackForm.css
        │   ├── feedbackForm.test.tsx
        │   ├── feedbackForm.stories.tsx
        │   └── index.ts
        └── feedbackPopup/
            ├── feedbackPopup.tsx
            ├── feedbackPopup.css
            ├── feedbackPopup.test.tsx
            ├── feedbackPopup.stories.tsx
            └── index.ts
  4. Avoid using useEffect for derived state

    Derived state is a value calculated from other states or props. Using useEffect to calculate this value and store it in a separate state triggers redundant re-renders.

    • For simple computations: Calculate the value directly during rendering.
    • For expensive computations: Use the useMemo hook to memoize the result based on specific dependencies.
    // ❌ BAD: Redundant state and effect
    function Form() {
      const [firstName, setFirstName] = useState('Taylor');
      const [lastName, setLastName] = useState('Swift');
      const [fullName, setFullName] = useState('');
    
      useEffect(() => {
        setFullName(firstName + ' ' + lastName);
      }, [firstName, lastName]);
    }
    
    // ✅ GOOD (Simple): Calculated during render
    function Form() {
      const [firstName, setFirstName] = useState('Taylor');
      const [lastName, setLastName] = useState('Swift');
      const fullName = firstName + ' ' + lastName;
    }
    
    // ✅ GOOD (Expensive): Using useMemo
    function TodoList({ todos, filter }) {
      const visibleTodos = useMemo(
        () => getFilteredTodos(todos, filter),
        [todos, filter]
      );
    }
  5. Determine when to use Local State in React

    Use local state when data is only required within a single component and does not need to be accessed by other components outside of it. Common examples include form input values and their associated validation or error states.
    import { useState } from 'react';
    
    function FormComponent() {
      const [inputValue, setInputValue] = useState('');
      const [error, setError] = useState(null);
      // ...
    }
  6. Use Agile iterations to identify Open-Closed Principle violations

    Agile methodologies help developers identify failures in the Open-Closed Principle early by utilizing short sprints (1-2 weeks) and small, deployable tasks.

    If implementing a new feature in a subsequent sprint requires modifying code written in a previous sprint (e.g., changing a logout feature to accommodate a new login implementation), it is a signal that the original code violated the Open-Closed Principle. Identifying these violations quickly through short iterations prevents the accumulation of heavy technical debt and allows for timely refactoring.

  7. Avoid chaining multiple useEffect hooks for state synchronization

    Avoid creating 'chains' of useEffect hooks where one effect updates a state that triggers another effect, and so on. This leads to complex data flows and multiple unnecessary re-renders. If one state change logically triggers a sequence of other state updates, consolidate that logic into a single event handler function or derive the state during render.
    function Game() {
      const [card, setCard] = useState(null);
      const [goldCardCount, setGoldCardCount] = useState(0);
      const [round, setRound] = useState(1);
    
      // Derive state during render instead of using an effect
      const isGameOver = round > 5;
    
      function handlePlaceCard(nextCard) {
        if (isGameOver) {
          throw Error('Game already ended.');
        }
    
        // Calculate all next state updates in the event handler
        setCard(nextCard);
        if (nextCard.gold) {
          if (goldCardCount < 3) {
            setGoldCardCount(goldCardCount + 1);
          } else {
            setGoldCardCount(0);
            setRound(round + 1);
            if (round === 5) {
              alert('Good game!');
            }
          }
        }
      }
    
      return <div>Game UI</div>;
    }
  8. Avoid storing JSX in React state

    React state should always be a serializable JavaScript object. Avoid storing JSX elements or using React.createElement() to manually create UI elements in state or variables. JSX is converted to React objects at compile time and is not always serializable. Instead, store the raw data (strings, numbers, objects) in state and define the UI structure within the component's return statement.
    // ❌ DON'T: Store JSX in state
    function Greet() {
      const [greet, setGreet] = useState(<div>Say Hi</div>);
      const change = () => setGreet(<div>Say Hello</div>);
    
      return (
        <>
          {greet}
          <button onClick={change}>change</button>
        </>
      );
    }
    
    // ✅ DO: Store data in state and render JSX in return
    function Greet() {
      const [greet, setGreet] = useState('Hi');
      const change = () => setGreet('Hello');
    
      return (
        <>
          <div>Say {greet}</div>
          <button onClick={change}>change</button>
        </>
      );
    }
  9. Reset component state when a prop changes using the key prop

    Avoid using useEffect to manually reset multiple state variables when a prop (like an ID) changes. This approach is inefficient because it causes an unnecessary double render: first for the prop change, and second when the useEffect triggers the state resets. Instead, provide a unique key prop to the component from its parent. When the key changes, React treats it as a new component instance and automatically resets all state to initial values.
    // DO: Use the key prop to reset state
    function Parent() {
      const userId = useUserId();
    
      return (
        <Profile
          key={userId}
          userId={userId}
        />
      );
    }
    
    function Profile({ userId }) {
      const [userName, setUserName] = useState();
      const [email, setEmail] = useState();
    
      return null;
    }
  10. Improve code readability by avoiding nested if-checks

    Deeply nested if-conditions are difficult to read because they force the developer to track multiple levels of indentation to understand which 'else' or return statement corresponds to which condition. To improve maintainability, use 'guard clauses' to validate data and handle error conditions first, returning early if the requirements for the main logic are not met.
    // ❌ Hard to read: Deeply nested logic
    if (authModule.initialized) {
      const authHeader = req.headers['Authentication'];
      if (authHeader) {
        const headerParts = authHeader.split(' ');
        if (headerParts.length === 2) {
          const token = headerParts[1];
          if (token !== '') {
            try {
              const payload = authModule.validateToken(token);
              return payload;
            } catch (err) {
              return 401;
            }
          }
          return 401;
        }
        return 401;
      }
      return 401;
    }
    return 401;
    
    // ✅ Easy to read: Guard clauses (roll out error conditions first)
    if (!authModule.initialized) throw new Error('Auth Module is not initialized');
    
    const authHeader = req.headers['Authentication'];
    if (!authHeader) return 401; // Auth header doesn't exist
    
    const headerParts = authHeader.split(' ');
    if (headerParts.length !== 2) return 401; // Header value is wrong
    
    const token = headerParts[1];
    if (token === '') return 401; // Empty token
    
    try {
      const payload = authModule.validateToken(token);
      return payload;
    } catch (err) {
      return 401;
    }