Advanced patterns and techniques in React.js

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

What are Render Props?

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.

Example of Render Props

				
					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 = () => (
    <DataFetcher render={data => (
        <div>{data ? JSON.stringify(data) : 'Loading...'}</div>
    )} />
);

				
			

Explanation

  • DataFetcher Component: This component fetches data and passes it to the render prop.
  • Usage: In the App component, we provide a function that receives the fetched data and returns JSX to render it.

Output:

When data is fetched successfully, it will be displayed as a JSON string; otherwise, it will show “Loading…”.

Higher-Order Components (HOCs)

What are HOCs?

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.

Example of HOCs

				
					import React from 'react';

// Higher-Order Component
const withLoading = (WrappedComponent) => {
    return class extends React.Component {
        render() {
            const { isLoading, ...otherProps } = this.props;
            return isLoading ? <div>Loading...</div> : <WrappedComponent {...otherProps} />;
        }
    };
};

// Example Component
const DataDisplay = ({ data }) => <div>{data}</div>;

// Enhanced Component
const EnhancedDataDisplay = withLoading(DataDisplay);

// Usage
const App = () => (
    <EnhancedDataDisplay isLoading={true} data="Here is your data!" />
);

				
			

Explanation

  • withLoading: This HOC takes a component and returns a new component that conditionally renders a loading message or the wrapped component based on the isLoading prop.
  • Enhanced Component: EnhancedDataDisplay is a version of DataDisplay that has loading logic applied.

Output:

If isLoading is true, it shows “Loading…”; otherwise, it displays the data.

Compound Components

What are Compound Components?

Compound components are components that work together to share state. This allows for cleaner APIs and improved encapsulation.

Example of Compound Components

				
					import React, { useContext, useState } from 'react';

// Create a context
const TabContext = React.createContext();

const Tab = ({ children }) => {
    const [activeIndex, setActiveIndex] = useState(0);
    return (
        <TabContext.Provider value={{ activeIndex, setActiveIndex }}>
            <div>{children}</div>
        </TabContext.Provider>
    );
};

const TabTitle = ({ index, children }) => {
    const { activeIndex, setActiveIndex } = useContext(TabContext);
    return (
        <button onClick={() => setActiveIndex(index)}>
            {children} {activeIndex === index ? ' (Active)' : ''}
        </button>
    );
};

const TabContent = ({ index, children }) => {
    const { activeIndex } = useContext(TabContext);
    return activeIndex === index ? <div>{children}</div> : null;
};

// Usage of compound components
const App = () => (
    <Tab>
        <TabTitle index={0}>Tab 1</TabTitle>
        <TabTitle index={1}>Tab 2</TabTitle>
        <TabContent index={0}>Content for Tab 1</TabContent>
        <TabContent index={1}>Content for Tab 2</TabContent>
    </Tab>
);

				
			

Explanation

  • Tab Component: Manages the active tab state and provides it to its children via context.
  • TabTitle: Changes the active tab when clicked and indicates which tab is active.
  • TabContent: Displays content for the active tab only.

Output:

Clicking on a tab title will display the corresponding content below.

Controlled vs. Uncontrolled Components

Controlled Components

Controlled components are React components that do not maintain their own state; instead, they receive their state from the parent component.

Example of Controlled Components

				
					import React, { useState } from 'react';

const ControlledInput = () => {
    const [value, setValue] = useState('');

    const handleChange = (e) => {
        setValue(e.target.value);
    };

    return (
        <input type="text" value={value} onChange={handleChange} />
    );
};

				
			

Uncontrolled Components

Uncontrolled components store their state internally and you can access it using refs.

Example of Uncontrolled Components

				
					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 (
        <form onSubmit={handleSubmit}>
            <input type="text" ref={inputRef} />
            <button type="submit">Submit</button>
        </form>
    );
};

				
			

Explanation

  • Controlled Input: The value is managed by React, making it predictable and easy to debug.
  • Uncontrolled Input: Uses refs to access the value directly from the DOM, suitable for simple forms.

Custom Hooks

What are Custom Hooks?

Custom hooks allow you to extract component logic into reusable functions. They start with the word “use” and can leverage other hooks inside them.

Example of a Custom Hook

				
					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 <div>Loading...</div>;
    return <div>{JSON.stringify(data)}</div>;
};

				
			

Explanation

  • useFetch: This custom hook fetches data from a given URL and returns the data along with a loading state.
  • Usage: The App component uses the useFetch hook to retrieve data.

Output:

When the data is fetched successfully, it is displayed as a JSON string; otherwise, it shows “Loading…”.

Context API

What is the Context API?

The Context API allows you to share data between components without passing props explicitly through every level of the component tree.

Example of Context API

				
					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 <div style={{ background: theme === 'dark' ? '#333' : '#FFF' }}>Themed Component</div>;
};

// Usage
const App = () => (
    <ThemeContext.Provider value="dark">
        <ThemedComponent />
    </ThemeContext.Provider>
);

				
			

Explanation

  • ThemeContext: We create a context for theme values.
  • ThemedComponent: This component uses the context to determine its background color.

Output:

The ThemedComponent will have a dark background based on the context value provided.

Portals

What are Portals?

Portals provide a way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

Example of Portals

				
					import React from 'react';
import ReactDOM from 'react-dom';

const Modal = ({ children }) => {
    return ReactDOM.createPortal(
        <div className="modal">{children}</div>,
        document.getElementById('modal-root')
    );
};

// Usage
const App = () => (
    <div>
        <h1>My App</h1>
        <Modal>I'm a modal!</Modal>
    </div>
);
				
			

Explanation

  • Modal Component: This component uses ReactDOM.createPortal to render its children into a different part of the DOM.
  • Usage: The App component displays the modal in a different part of the DOM.

Output:

The modal will be rendered in the specified modal-root div, regardless of its position in the React component tree.

Error Boundaries

What are Error Boundaries?

Error boundaries are components that catch JavaScript errors anywhere in their child component tree and log those errors, display a fallback UI, or both.

Example of Error Boundaries

				
					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 <h1>Something went wrong.</h1>;
        }
        return this.props.children; 
    }
}

// Usage
const App = () => (
    <ErrorBoundary>
        <ComponentThatMayError />
    </ErrorBoundary>
);

				
			

Explanation

  • ErrorBoundary Component: This component catches errors in its children and displays a fallback UI when an error occurs.
  • Usage: The App component wraps a potentially error-prone component with the ErrorBoundary.

Output:

If ComponentThatMayError throws an error, “Something went wrong.” will be displayed.

Code Splitting

What is Code Splitting?

Code splitting allows you to split your code into separate bundles that can be loaded on demand. This improves load time and performance.

Example of Code Splitting with React.lazy

				
					import React, { Suspense, lazy } from 'react';

// Lazy load the component
const LazyComponent = lazy(() => import('./LazyComponent'));

const App = () => (
    <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
    </Suspense>
);

				
			

Explanation

  • React.lazy: This function enables code splitting by dynamically importing the component.
  • Suspense: This component allows you to define a loading state while the lazy-loaded component is being fetched.

Output:

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!❤️

Table of Contents