14

I have found a code that solved my problem in Next JS re rendering when changing pages. But now i need to send props to the children component. I got no idea how i can make it works here, this is my layout.js code. As you can see i can send props to Header component but for children i dont know how, because it is a variable and not a component.

import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";

export default function Layout({ children, ...pageProps }) {
  return (
    <Fragment>
      <Header
        isRegisterPage={pageProps.isRegisterPage}
        isLoginPage={pageProps.isLoginPage}
        outHome={pageProps.outHome}
      />
      {children}
      <Footer />
    </Fragment>
  );
}

Thank you for the help

JDLR
  • 520
  • 1
  • 4
  • 20
  • 1
    This is the kind of stuff where redux (essentially react context with nice dev tools) shines - maintain a global state variable that is used (via react context and `useSelector`) by any component that wants to use it, rather than prop drilling. – Adam Jenkins Mar 01 '20 at 01:01

4 Answers4

4

Have you considered using React's Context API? The idea is that when using the Context API your component's state get's lifted, to be managed at a global scale. If a component needs a prop, instead of passing props down manually (prop drilling) you can simply wrap you component in what's known as a context provider. This will allow that Component to access the global state of your application. This is good because, when your application gets bigger, you may need to pass props down through many components which can clutter and add unneeded confusion.

React provides some great documentation to set your React application up to use the Context API. Highly recommend checking it out!

https://reactjs.org/docs/context.html

Lucas Raza
  • 499
  • 1
  • 4
  • 13
  • 1
    I don't like this solution because it couples the components to each other. What if you have some generic component that you want to have as a child, but you're also using it in other parts of your app where the context that you need (for when it's a child) is not appropriate in other parts of your app? – nikk wong Nov 18 '22 at 01:16
  • Using context is a rather opinionated design decision. – gutscdav000 May 04 '23 at 10:57
  • https://stackoverflow.com/questions/76349135/how-to-persist-and-set-global-state-for-client-from-server-in-nextjs-13-4 for app router using zustand – darren z Jun 01 '23 at 23:38
2

You can use React's cloneElement to achieve that.

React.cloneElement(children, {
    isRegisterPage: pageProps.isRegisterPage,
    isLoginPage: pageProps.isLoginPage,
    outHome: pageProps.outHome
})

Complete example in your case:

import Header from "../components/header";
import Footer from "../components/footer";
import React, { Fragment } from "react";

export default function Layout({ children, ...pageProps }) {
  return (
    <Fragment>
      <Header
        isRegisterPage={pageProps.isRegisterPage}
        isLoginPage={pageProps.isLoginPage}
        outHome={pageProps.outHome}
      />
      {
          React.cloneElement(children, {
              isRegisterPage: pageProps.isRegisterPage,
              isLoginPage: pageProps.isLoginPage,
              outHome: pageProps.outHome
          })
      }
      <Footer />
    </Fragment>
  );
}
Darryl RN
  • 7,432
  • 4
  • 26
  • 46
2

Try this

import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";

export default function Layout({ children, ...pageProps }) {

  function recursiveMap(children, fn) {
    return React.Children.map(children, child => {
      if (!React.isValidElement(child) || typeof child.type == 'string') {
        return child;
      }
  
      if (child.props.children) {
        child = React.cloneElement(child, {
          children: recursiveMap(child.props.children, fn)
        });
      }
  
      return fn(child);
    });
  }

  // Add props to all child elements.
  const childrenWithProps = recursiveMap(children, child => {
    // Checking isValidElement is the safe way and avoids a TS error too.
    if (isValidElement(child)) {
      // Pass additional props here
      return cloneElement(child, { currentUser: { ...user } })
    }

    return child;
  });

  return (
    <Fragment>
      <Header
        isRegisterPage={pageProps.isRegisterPage}
        isLoginPage={pageProps.isLoginPage}
        outHome={pageProps.outHome}
      />
      {childrenWithProps}
      <Footer />
    </Fragment>
  );
}
Akshit
  • 21
  • 2
  • 4
    Hey Akshit, welcome to Stack Overflow! Good start on providing a solution for this problem, but try to also give an explanation why this solution works so that Joangel and other's reading your answer understand the solution as well. This is especially usefull for others who don't have the exact same code, but a similar problem. – Axel Köhler Dec 30 '20 at 21:44
-1

From the answer of Lucas Raza, below is an example that uses Context API to apply themes to different components

1.Create a context File

//ThemeContex.js
import { createContext, useState } from "react";

export const ThemeContext = createContext();

export const withThemeContext = Component => {
    const WrappedComp = props => {
  
        const [darkColor,lightColor] = ["#3b3b3b", "#ddd"]
        const [lightBackgoround,darkBackgoround] = ["#ececec","#1d2a35"]       

        const darkTheme = {
            backgroundColor: darkBackgoround,
            color:lightColor,
        }

        const lightTheme = {
            backgroundColor: lightBackgoround,
            color:darkColor,
        }

        const themes = {
            darkTheme, lightTheme
        }
    
        const [theme, setTheme] = useState(lightTheme)

    
        const children ={
            theme,
            themes, 
            setTheme,
        }
    
        return(
            <StylesContext.Provider value={{...children}} >
                <Component {...props} />    
            </StylesContext.Provider>
        )
    }
    
    return WrappedComp;
}
  1. In _app.js, import withThemeContext higher component and wrap MyApp with it when exporting it.

     import { withThemeContext } from '../components'
    
     function MyApp({ Component, pageProps }) {
           return <Component {...pageProps} />
     }
    
     export default withThemeContext(MyApp)
    
  2. You can know use theme any where in a component

     import { useContext } from 'react'
     import {ThemeContext} from '../components'
    
     export default function Home() {
         const { theme } =useContext(ThemeContext)
    
         return (
             <div id="home" style={theme}>            
                 // Home logic...
             </div>
         )
     }
    
newbie
  • 126
  • 5