We will explore advanced patterns and techniques in React.js that can enhance your application's architecture, improve performance, and foster code reusability. Understanding these patterns can significantly increase your productivity as a React developer and help you write more maintainable and scalable code.
Render props is a technique for sharing code between React components using a prop whose value is a function. This allows you to pass data to the component and have it render based on that data.
import React from 'react';
class DataFetcher extends React.Component {
state = { data: null };
componentDidMount() {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
return this.props.render(this.state.data);
}
}
// Usage of DataFetcher with render prop
const App = () => (
(
{data ? JSON.stringify(data) : 'Loading...'}
)} />
);
render
prop.App
component, we provide a function that receives the fetched data and returns JSX to render it.When data is fetched successfully, it will be displayed as a JSON string; otherwise, it will show “Loading…”.
A Higher-Order Component is a function that takes a component and returns a new component. HOCs are used to enhance components with additional functionality.
import React from 'react';
// Higher-Order Component
const withLoading = (WrappedComponent) => {
return class extends React.Component {
render() {
const { isLoading, ...otherProps } = this.props;
return isLoading ? Loading... : ;
}
};
};
// Example Component
const DataDisplay = ({ data }) => {data};
// Enhanced Component
const EnhancedDataDisplay = withLoading(DataDisplay);
// Usage
const App = () => (
);
isLoading
prop.EnhancedDataDisplay
is a version of DataDisplay
that has loading logic applied.If isLoading
is true, it shows “Loading…”; otherwise, it displays the data.
Compound components are components that work together to share state. This allows for cleaner APIs and improved encapsulation.
import React, { useContext, useState } from 'react';
// Create a context
const TabContext = React.createContext();
const Tab = ({ children }) => {
const [activeIndex, setActiveIndex] = useState(0);
return (
{children}
);
};
const TabTitle = ({ index, children }) => {
const { activeIndex, setActiveIndex } = useContext(TabContext);
return (
);
};
const TabContent = ({ index, children }) => {
const { activeIndex } = useContext(TabContext);
return activeIndex === index ? {children} : null;
};
// Usage of compound components
const App = () => (
Tab 1
Tab 2
Content for Tab 1
Content for Tab 2
);
Clicking on a tab title will display the corresponding content below.
Controlled components are React components that do not maintain their own state; instead, they receive their state from the parent component.
import React, { useState } from 'react';
const ControlledInput = () => {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
};
return (
);
};
Uncontrolled components store their state internally and you can access it using refs.
import React, { useRef } from 'react';
const UncontrolledInput = () => {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
alert(`A name was submitted: ${inputRef.current.value}`);
};
return (
);
};
Custom hooks allow you to extract component logic into reusable functions. They start with the word “use” and can leverage other hooks inside them.
import React, { useState, useEffect } from 'react';
// Custom Hook
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(url);
const result = await response.json();
setData(result);
setLoading(false);
};
fetchData();
}, [url]);
return { data, loading };
};
// Usage of Custom Hook
const App = () => {
const { data, loading } = useFetch('https://api.example.com/data');
if (loading) return Loading...;
return {JSON.stringify(data)};
};
App
component uses the useFetch
hook to retrieve data.When the data is fetched successfully, it is displayed as a JSON string; otherwise, it shows “Loading…”.
The Context API allows you to share data between components without passing props explicitly through every level of the component tree.
import React, { createContext, useContext } from 'react';
// Create a Context
const ThemeContext = createContext('light');
// A component that consumes the context
const ThemedComponent = () => {
const theme = useContext(ThemeContext);
return Themed Component;
};
// Usage
const App = () => (
);
The ThemedComponent
will have a dark background based on the context value provided.
Portals provide a way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
import React from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ children }) => {
return ReactDOM.createPortal(
{children},
document.getElementById('modal-root')
);
};
// Usage
const App = () => (
My App
I'm a modal!
);
ReactDOM.createPortal
to render its children into a different part of the DOM.App
component displays the modal in a different part of the DOM.The modal will be rendered in the specified modal-root
div, regardless of its position in the React component tree.
Error boundaries are components that catch JavaScript errors anywhere in their child component tree and log those errors, display a fallback UI, or both.
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error("Error caught by Error Boundary: ", error);
}
render() {
if (this.state.hasError) {
return Something went wrong.
;
}
return this.props.children;
}
}
// Usage
const App = () => (
);
App
component wraps a potentially error-prone component with the ErrorBoundary
.If ComponentThatMayError
throws an error, “Something went wrong.” will be displayed.
Code splitting allows you to split your code into separate bundles that can be loaded on demand. This improves load time and performance.
import React, { Suspense, lazy } from 'react';
// Lazy load the component
const LazyComponent = lazy(() => import('./LazyComponent'));
const App = () => (
Loading...
The application will display “Loading…” until LazyComponent
is ready to render.
Render Props and HOCs allow for code reuse and logic sharing among components. Compound Components help manage state across related components, making the API cleaner. Understanding Controlled vs. Uncontrolled Components is crucial for handling form inputs effectively. Custom Hooks enable the extraction of reusable logic, enhancing component modularity. The Context API provides a way to pass data deeply through the component tree without prop drilling. Portals allow rendering of components outside their parent DOM structure. Error Boundaries enable graceful error handling in applications. Finally, Code Splitting optimizes performance by loading only necessary code. Happy Coding!❤️