Skip to content

Instantly share code, notes, and snippets.

@arangates
Created September 21, 2024 19:05
Show Gist options
  • Save arangates/a4b5726362909ee1ff895be3af5d6ebf to your computer and use it in GitHub Desktop.
Save arangates/a4b5726362909ee1ff895be3af5d6ebf to your computer and use it in GitHub Desktop.
# Advanced React Patterns and Best Practices
As an experienced React developer and code reviewer, I'll walk you through some of the most effective and widely acknowledged patterns and practices in React development. These have been recognized by senior engineers worldwide as the key to building robust, scalable, and maintainable applications.
## 1. Component Design Patterns
### a. Container-Presenter Pattern
- **What it is:** The container is responsible for handling state, logic, and data fetching, while the presentational component is stateless and focuses purely on rendering UI based on props.
- **Why it’s important:** This separation of concerns makes your components more reusable and easier to test. Presentational components are pure and focus on rendering, making them easier to reason about.
- **Example:**
```jsx
// Presentational Component
const UserList = ({ users }) => (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
// Container Component
const UserContainer = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers().then(setUsers);
}, []);
return <UserList users={users} />;
};
```
### b. Higher-Order Components (HOC)
- **What it is:** A function that takes a component and returns a new component with enhanced behavior (often state or props injection).
- **Why it’s important:** HOCs help in code reuse by abstracting common functionality (e.g., authentication, logging) and applying it to multiple components.
- **Example:**
```jsx
function withAuth(WrappedComponent) {
return function(props) {
const isAuthenticated = useAuth(); // Custom hook
return isAuthenticated ? <WrappedComponent {...props} /> : <Redirect to="/login" />;
};
}
const ProtectedPage = withAuth(PageComponent);
```
### c. Render Props Pattern
- **What it is:** A component uses a function (render prop) to decide what to render, allowing logic to be shared across components.
- **Why it’s important:** It enables better reuse of logic across components without HOCs, promoting flexibility.
- **Example:**
```jsx
const MouseTracker = ({ render }) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
return <div onMouseMove={handleMouseMove}>{render(position)}</div>;
};
const App = () => (
<MouseTracker render={({ x, y }) => <p>Mouse is at ({x}, {y})</p>} />
);
```
## 2. State Management
### a. Context + Reducer Pattern (React's native replacement for Redux)
- **What it is:** Using the `useReducer` hook with React Context to manage global state in a scalable way.
- **Why it’s important:** This pattern gives you a clean, maintainable structure for state management without the overhead of external libraries like Redux.
- **Example:**
```jsx
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
const CounterContext = createContext();
const CounterProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
};
const Counter = () => {
const { state, dispatch } = useContext(CounterContext);
return (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
};
```
### b. Recoil for fine-grained state management
- **What it is:** Recoil offers atomized state management, where each piece of state can be independently subscribed to and updated.
- **Why it’s important:** Recoil's fine-grained control over state updates results in better performance for complex applications compared to a monolithic Redux store.
- **Example:**
```jsx
const textState = atom({
key: 'textState', // unique ID
default: '', // default value
});
const TextInput = () => {
const [text, setText] = useRecoilState(textState);
return <input type="text" value={text} onChange={(e) => setText(e.target.value)} />;
};
const TextDisplay = () => {
const text = useRecoilValue(textState);
return <p>{text}</p>;
};
```
## 3. Hooks Mastery
### a. Custom Hooks
- **What it is:** Custom hooks encapsulate logic that can be reused across multiple components, similar to HOCs but without component wrapping.
- **Why it’s important:** Custom hooks allow separation of concerns and keep component code clean by extracting complex logic.
- **Example:**
```jsx
function useFetchData(url) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url).then((response) => response.json()).then(setData);
}, [url]);
return data;
}
const DataComponent = () => {
const data = useFetchData('/api/data');
return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
};
```
### b. `useMemo` and `useCallback` for Performance Optimization
- **What it is:** These hooks allow you to memoize expensive functions and values to avoid unnecessary recalculations or re-renders.
- **Why it’s important:** Helps in optimizing performance, especially in large applications where re-renders or recalculations can be costly.
- **Example:**
```jsx
const MemoizedComponent = ({ data }) => {
const expensiveCalculation = useMemo(() => {
return data.reduce((sum, item) => sum + item.value, 0);
}, [data]);
const handleClick = useCallback(() => {
console.log('Button clicked!');
}, []);
return (
<div>
<p>Calculated Value: {expensiveCalculation}</p>
<button onClick={handleClick}>Click Me</button>
</div>
);
};
```
## 4. Performance Optimization Patterns
### a. Code-Splitting with `React.lazy()` and `Suspense`
- **What it is:** Lazy load components as they are needed instead of loading the entire application upfront.
- **Why it’s important:** Improves initial load times by reducing the amount of JavaScript that needs to be parsed and executed.
- **Example:**
```jsx
const LazyComponent = React.lazy(() => import('./LazyComponent'));
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
```
### b. Memoizing Components with `React.memo`
- **What it is:** Prevents re-renders of a component if its props haven’t changed, similar to `shouldComponentUpdate` in class components.
- **Why it’s important:** Great for performance optimizations in components that receive frequent updates but don’t always need to re-render.
- **Example:**
```jsx
const ExpensiveComponent = React.memo(({ data }) => {
return <div>{data}</div>;
});
```
## Conclusion
Mastering these patterns and techniques can significantly improve the efficiency, maintainability, and performance of your React applications. They represent the "secret knowledge" of top-tier React engineers, built over years of experience with large-scale projects. Whether you're focusing on component design, state management, performance optimization, or best use of hooks, these practices will help you write React code that stands out in terms of clarity, scalability, and maintainability.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment