Search code examples
javascriptreactjsreach-routerreact-static

How to detect if another component is present in the document?


I have a site built with React Static that has a Header component that is always present. Depending on if the current page has a hero component or not, the Header should be either light or dark.

The Header is rendered outside of the routes and the useEffect is triggered before the children is rendered. This is probably because of the routing.

This is the current code:

// App.js
import React, { useState, useEffect } from 'react'
import { Root, Routes } from 'react-static'

export default () => {

    const [useDarkTheme, setUseDarkTheme] = useState(false);

    useEffect(() => {
        if (typeof document !== "undefined") {
            const heroPresent = document.querySelectorAll(".o-hero").length > 0;
            console.log("The hero is present: " + heroPresent);
            setUseDarkTheme(!heroPresent);
        }
    })

    return (
        <Root>
            <React.Suspense fallback={ <em>Loading...</em> }>
                <Header useDarkTheme={ useDarkTheme } />
                <Routes default />
            </React.Suspense>
        </Root>
    );
}

What will be rendered at <Routes default /> is the static pages configured in React Static's static.config.js.

Below is an example of the Hero component:

// Hero.js
import React from "react";

export default () => {
    console.log("This is the Hero rendering. If this exist, the Header should be dark.");

    return (
        <div className="o-hero">
            <p>Hero!</p>
        </div>
    );
}

When I run the application and look at the logs this is what I get:

The hero is present: false
This is the Hero rendering. If this exist, the Header should be dark.

How could I somehow detect the presence of the Hero from the Header although the Hero is in a router and the Header is not? This feels like quite a common use case, but I could not find any info on the interwebs.

Thanks in advance!


Solution

  • So I ended up using useContext to provide all children with a getter and a setter for the Header's theme (dark or light). The solution is very much inspired from this answer. The solution looks like this:

    // App.js
    import React, { useState, useContext } from 'react'
    import { Root, Routes } from 'react-static'
    import { HeaderThemeContext } from "./context";
    
    export default () => {
    
        const { theme } = useContext(HeaderThemeContext);
        const [headerTheme, setHeaderTheme] = useState(theme);
    
        return (
            <Root>
                <React.Suspense fallback={ <em>Loading...</em> }>
                    <HeaderThemeContext.Provider value={ { theme: headerTheme, setTheme: setHeaderTheme } }>
                        <Header theme={ headerTheme } />
                        <Routes default />
                    </HeaderThemeContext.Provider>
                </React.Suspense>
            </Root>
        );
    }
    
    // Hero.js
    import React from "react";
    import { headerThemes, setHeaderTheme } from "./context";
    
    export default () => {
        setHeaderTheme(headerThemes.DARK); 
    
        console.log("This is the Hero rendering. If this exist, the Header should be dark.");
    
        return (
            <div className="o-hero">
                <p>Hero!</p>
            </div>
        );
    }
    
    // context.js
    import React, { createContext, useContext } from "react";
    
    export const headerThemes = {
        LIGHT: "light",
        DARK: "dark",
    };
    
    export const HeaderThemeContext = createContext({
        theme: headerThemes.LIGHT,
        setTheme: () => {}
    });
    
    // This is a hook and can only be used in a functional component with access to the HeaderThemeContext.
    export const setHeaderTheme = theme => useContext(HeaderThemeContext).setTheme(theme);
    
    

    This gives global access to set and get the header theme, which might not be optional, but it works for now and I think it's fine. Please let me know if there is a better way of doing this.