Reputation: 45
I have a Layout component as follows:
import React, { useRef, useEffect, useState } from "react"
import Navbar from "./Navbar"
import "../styles/mains.scss"
const Layout = ({ children }) => {
return (
<>
<Navbar></Navbar>
<main>
{children}
</main>
</>
)
}
export default Layout
I use Layout with some nested children in my Home component among other places:
import React from "react";
import Layout from "./layout";
import Header from "./Header";
import Work from "./Work";
import About from "./about";
import Footer from "./Footer";
export const Home = () => {
return (
<Layout>
<Header/>
<Work/>
<About/>
<Footer/>
</Layout>
);
};
I now have a requirement that I pass props down into specific children. Specifically I need to pass a function into my child Work component. But I have no idea how to parse through the children and conditionally pass a function to a specific child component if it exists. Note that in many cases the Layout will not have the Work component as a child.
Upvotes: 3
Views: 553
Reputation: 5770
You can use the Context API to have a clean contract between components. Notice here that this solution doesn't make any assumptions about your component tree besides requiring the Layout
component being at the top, which makes the dependency between components much simpler.
Here is how you can apply on your example:
On your Layout
component, use the Context API to pass values down the component tree, in this case, the function you want. I'm calling this function here as doSomething
:
// file: layout.js
import { createContext, useCallback } from "react";
const LayoutContext = createContext();
LayoutContext.displayName = "LayoutContext";
export const Layout = ({ children }) => {
// Here we use `useCallback` to make sure that
// our function identity is the same between renders
const doSomething = useCallback(() => {
console.log("do something from context");
}, []);
return (
<LayoutContext.Provider value={{ doSomething }}>
<div>
<h1>Layout header</h1>
{children}
</div>
</LayoutContext.Provider>
);
};
Now you can create a custom hook that makes the use of this newly created context easier across components.
At the bottom of the layout.js
file, add this:
import { useContext } from 'react'
export function useDoSomething() {
const context = useContext(LayoutContext);
// Here we check if the context is available
// otherwise, it's probably due to the `Layout`
// component not being on the top of the component tree.
if (typeof context === "undefined") {
throw new Error(
"doSomething is not available on your component tree" +
"\n" +
"Make sure to use `Layout` before using `useDoSomething` hook"
);
}
return context;
}
Now on your Work
component, you can use our newly created custom hook to grab the function you need.
// file: work.js
import { Layout, useDoSomething } from "./Layout";
export const Work = () => {
// Here you can consume the context via our
// custom hook.
const { doSomething } = useDoSomething();
doSomething();
return <h1>Work component</h1>;
};
And here is how the top level component could look like:
// file: app.js
import {Layout} from './layout'
import {Work} from './work'
export default function App() {
return (
<Layout>
<Work />
</Layout>
);
}
Upvotes: 1
Reputation: 173
I can't comment because of my reputation.
What about passing props on demand on the parents?
If you would like to conditionally add a prop for the layout's childrens, I would go for a React.cloneElement() approach like this:
{
React.Children.map(children, child =>
React.cloneElement(child, ...(child.props.id === 'workID' && {
propToBeAdded
}))
)
}
So, you'll need to add and id to the Work component.
<Work id="workID" />
Despite it's possible (I haven't tried out the code for a fast response, let me know if I'm missing something), I wouldn't do this, are you reusing it EVERYWHERE? It feels like a smell.
Upvotes: 0