Pass a component as props - reactjs

I have my card component, in which I need to pass another component into to display unique content for each different card. At the moment, I am passing the example component "Foot" in a ternary operator, but I also need to pass "Patio", "Fence" and "Deck", which relate to specific title props. I hope this all makes sense, here's my code -
import { useState } from "react";
import Foot from "../Footing";
const Card = (props) => {
const [showContent, setShowContent] = useState(false);
const contentClick = () => {
setShowContent(true);
};
return (
<div>
<h1>{props.job}</h1>
<button onClick={contentClick}>Calculate</button>
{showContent && <Foot />}
<hr></hr>
</div>
);
};
export default Card;

You can pass the component as prop to Card. React will render the component only if its name is in Pascal case, so renaming the content prop to Content
Card
const Card = ({content:Content,job}) => {
const [showContent, setShowContent] = useState(false);
const contentClick = () => {
setShowContent(true);
};
return (
<div>
<h1>{job}</h1>
<button onClick={contentClick}>Calculate</button>
{showContent && <Content />}
<hr></hr>
</div>
);
};
Usage
import Foot from "../Footing";
<Card content={Foot}/>
If you want to pass title prop to the Foot component,
Card
const Card = ({content,job}) => {
return
<>
{showContent && content}
</>
}
Usage
import Foot from "../Footing";
<Card content={<Foot title='foot-title'/>}/>

Related

Rendering function/Component without a call from render function

I have created a component, and I want to make it easy to use. I don't wanna call my component like the following way,
<myComponent {...someProps} />
I'm looking for a specific way, I'm still new in React I don't know exactly the name and how can I do it.
is there any way to call my component as a hook or something similar, let's take this component as an example?
export const useComponent = (props) => {
const [show, setShow] = useState('show');
const onShow = (value) => { setShow(value); }
return (
// Content
<div className={show}>
Component
<button onClick={onShow(hide)}>Hide</button>
</div>
);
}
I need to show what inside content using a function, like that
const onShow = useComponent();
//if I want to show it I will call onShow function
<button onClick={onShow('show')}>Show Component</button>
What I want basically is when I clicked on the 'Show Component' button I want to show the useComponent, without calling it inside HTML like .
it's like it gonna be easy for to everyone use my component.
One solution is to use a wrapper component using the Context API.
This is, in my opinion, one of the advanced features of React. Super useful of course, but if you are a beginner, try creating just a component :)
Anyway, you missed in your description, what the custom hook should do. If you need a more precise answer, try giving some more info :)
import React, { useState, createContext, useContext} from 'react';
export const ComponentContext = createContext(null);
/**
* Don't forget to describe your provider
* #param {object} args react args
* #param {JSX.Element | Array<JSX.Element>} args.children children to show
*/
export const ComponentProvider = ({ children }) => {
const [show, setShow] = useState('show');
const onShow = (value) => { setShow(value); }
return (
<ComponentContext.Provider
value={{ show, onShow }}
children={children}
/>
);
};
/**
* Hook used to get the props you defined
* #returns {{show:boolean,onShow:function}}
*/
export const useComponent = () => {
const res = useContext(UserContext);
return res;
};
You can then use your component in the wrapper or App.jsx
import React from 'react';
import { ComponentProvider } from './custom-hooks/ComponentProvider';
function App() {
return (
<ComponentProvider>
<YourComponent />
</ComponentProvider >
);
}
and finally, use the functions you need in the element
import { useComponent } from './custom-hooks/ComponentProvider';
...
const { onShow } = useComponent();
return (
<button onClick={()=>onShow('show')}>Show Component</button>
);
Edited:
I think you're looking for HOC. You can create Higher Order Component using useComponent and serve/provide wherever you need it by following example.
const withComponent = (WrappedComponent) => {
const WithComponent = (props) => {
function useComponent(status, setStatus) {
return (
<div>
{status == "show" && <div>
<p>Inner Contents</p>
<button onClick={() => setStatus("hide")}>Hide Inner Contents</button>
</div>}
</div>
);
}
return <WrappedComponent {...props} useCompoent={useComponent} />;
};
return WithComponent;
};
const App = (props) => {
const { useCompoent } = props;
const [status, setStatus] = useState("hide");
const getUseComponent = useCompoent(status, setStatus);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{getUseComponent}
<button onClick={() => setStatus("show")}>
Show Component
</button>
</div>
);
};
export default withComponent(App);
You can move withComponent into separate file as well.

Prevent rerender of sibling component which initiates a useState in wrapper component

I am not very experienced with React but I have a very simple Setup.
export default function App() {
const [title, setTitle] = useState("still-empty");
const myFunction = title => {
setTitle(title);
};
return (
<div className="App">
<ComponentA myFunction={myFunction} />
<br />
<br />
<ComponentB title={title} />
</div>
);
}
const ComponentA = ({ myFunction }) => {
console.log("Rendering Component A");
return (
<div onClick={() => myFunction(Math.random() * 1000)}> Component A </div>
);
};
export default ComponentA;
const ComponentB = ({ title }) => {
return <div> Title : {title}</div>;
};
export default ComponentB;
Here is a sandbox to test this: https://codesandbox.io/s/musing-cookies-g7szr
See that if you click on "ComponentA", that exact ComponentA gets rerendered (you can see it in console) although no props are changed on this component. This is a simplified example of my real use case. In my real use case, ComponentA is a map where a lot of stuff (zoom, center)
will be reset. I want to prevent these resets and also the 1 second it takes for rerendering. Therefor I present this simplified example.
So how do I pass an information from ComponentA to ComponentB, without rerendering ComponentA itself? Thanks for helping out here.
use useCallback in Parent so that the function is not created again and again but only on initial render.
use React.memo so that when no props are changed the component wont re-render.
App
export default function App() {
const [title, setTitle] = useState("still-empty");
const myFunction = useCallback(title => {
setTitle(title);
}, []);
return (
<div className="App">
<ComponentA myFunction={myFunction} />
<br />
<br />
<ComponentB title={title} />
</div>
);
}
ComponentA
import React, { memo } from "react";
const ComponentA = ({ myFunction }) => {
console.log("Rendering Component A");
return (
<div onClick={() => myFunction(Math.random() * 1000)}> Component A </div>
);
};
export default memo(ComponentA);
Working demo is here:
https://codesandbox.io/s/affectionate-boyd-v7g2t?file=/src/App.js

How to return a component and a function, or what's the alternative?

[React] What is the "way" to send/share a function between components?
Better explained in (useless) code
Here I have no problem since everything is in the same component (https://codesandbox.io/s/compassionate-ishizaka-uzlik)
import React, { useState } from "react";
export default function App() {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
const Card = () => <div onClick={letbook}>hey</div>;
const MyCom = () => {
return <div><Card /></div>;
};
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom />
<div>{bookmarks}</div>
</div>
);
}
But then, if now I want to split code, how do I do this? The problem is how to share letbook (this code doesn't work)
import React, { useState } from "react";
export default function App() {
const Card = () => <div onClick={letbook}>hey</div>;
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom />
<div>{bookmarks}</div>
</div>
);
}
const MyCom = () => {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return (
<div>
<Card />
</div>
);
};
I could use a hook that returned the component and the function
const [letbook, MyCom] = useMyCom
But this is not recommended (https://www.reddit.com/r/reactjs/comments/9yq1l8/how_do_you_feel_about_a_hook_returning_components/)
Then I can use a hook and a component, as with the following code, but the code itself seems obfuscated to me, to a point that I doubt whether I should split the code or not
Unless (and this is the question) whether there is a smarter way to do this
import React, { useState } from "react";
export default function App() {
const [bookmarks, setBookmarks, letbook] = useMyCom();
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom card={props => <Card letbook={letbook} />} />
<div>{bookmarks}</div>
</div>
);
}
const Card = ({letbook}) => <div onClick={letbook}>hey</div>;
const useMyCom = () => {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return [bookmarks, setBookmarks, letbook];
};
const MyCom = ({ letbook, card }) => <div>{card(letbook)}</div>;
Split your component to reuse it is definitely a good idea. But make sure your are using and manipulate a single state in the same file an pass it as props. Also, it is important that you avoid to re-render your child component. Only when your main component change props that are necessary to re-render your child component.
import React, { useState, memo } from "react";
const MyCom = memo(props => {
return <div>{props.bookmarks}</div>;
});
export default function App() {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom bookmarks={bookmarks} />
</div>
);
}

React complains element type is invalid when trying to use context

I'm trying to use React Context to update navbar title dynamically from other child components. I created NavbarContext.js as follows. I have wrapped AdminLayout with NavContext.Provider and use useContext in Course.js to dynamically update navbar title inside useEffect. However, when I'm doing this, react throws the following error on the screen.
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
How can I use context properly so that I can update Header title from Course.js inside its useEffect?
NavbarContext.js
import React, {useState} from 'react'
export default () => {
const [name,setName] = useState("")
const NavContext = React.createContext({
name: "",
changeName: name => setName(name)
})
const NavProvider = NavContext.Provider
const NavConsumer = NavContext.Consumer
return NavContext
}
AdminLayout.js
<NavContext.Provider>
<div className={classes.wrapper}>
<Sidebar
routes={routes}
logoText={"Widubima"}
logo={logo}
image={image}
handleDrawerToggle={handleDrawerToggle}
open={mobileOpen}
color={color}
{...rest}
/>
<div className={classes.mainPanel} ref={mainPanel}>
<Navbar
routes={routes}
handleDrawerToggle={handleDrawerToggle}
{...rest}
/>
{/* On the /maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */}
{getRoute() ? (
<div className={classes.content}>
<div className={classes.container}>{switchRoutes}</div>
</div>
) : (
<div className={classes.map}>{switchRoutes}</div>
)}
</div>
</div>
</NavContext.Provider>
Navbar.js
import NavContext from "context/NavbarContext"
export default function Header(props) {
function makeBrand() {
var name;
props.routes.map(prop => {
if (window.location.href.indexOf(prop.layout + prop.path) !== -1) {
name = prop.name;
document.title = name;
}
return null;
});
return name;
}
return (
<AppBar className={classes.appBar + appBarClasses}>
<Toolbar className={classes.container}>
<div className={classes.flex}>
{/* Here we create navbar brand, based on route name */}
<NavContext.Consumer>
{({ name, setName }) => (
<Button
color="transparent"
href="#"
className={classes.title}
style={{ fontSize: "1.5em", marginLeft: "-2%" }}
>
{makeBrand() || name}
</Button>
)}
</NavContext.Consumer>
</Toolbar>
</AppBar>
);
}
Course.js
import React, { useState, useEffect, useContext } from "react";
import NavContext from "context/NavbarContext"
const AdminCourse = props => {
const context = useContext(NavContext);
useEffect(() => {
Axios.get('/courses/'+props.match.params.courseId).then(
res => {
context.changeName("hello")
}
).catch(err => {
console.log(err)
})
return () => {
setCourseId("");
};
});
return (
<GridContainer>
</GridContainer>
);
};
export default AdminCourse;
i think problem is there with your NavbarContext.js.
you are not exporting NavContext also.
you are defining provider, consumer but you are not using them either.
here's how you can solve your problem.
first create context and it's provider in a file as following.
NavContext.js
import React, { useState } from "react";
const NavContext = React.createContext();
const NavProvider = props => {
const [name, setName] = useState("");
let hookObject = {
name: name,
changeName: setName
};
return (
<NavContext.Provider value={hookObject}>
{props.children}
</NavContext.Provider>
);
};
export { NavProvider, NavContext };
in above code first i am creating context with empty value.
the i am creating NavProvider which actually contains value name as a state hook inside it.hookObject exposes state as per your naming conventions in code.
now i for testing purpose i defined two consumers.
one is where we update name in useEffect, that is ,
ConsumerThatUpdates.js
import React, { useContext, useEffect } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatUpdates = () => {
const { changeName } = useContext(NavContext);
useEffect(() => {
changeName("NEW NAME");
}, [changeName]);
return <div>i update on my useeffect</div>;
};
export default ConsumerThatUpdates;
you can update useEffect as per your needs.
another is where we use the name,
ConsumerThatDisplays.js
import React, { useContext } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatDisplays = () => {
const { name } = useContext(NavContext);
return <div>{name}</div>;
};
export default ConsumerThatDisplays;
and finally my App.js looks like this,
App.js
import React from "react";
import "./styles.css";
import { NavProvider } from "./NavContext";
import ConsumerThatDisplays from "./ConsumerThatDisplays";
import ConsumerThatUpdates from "./ConsumerThatUpdates";
export default function App() {
return (
<div className="App">
<NavProvider>
<ConsumerThatDisplays />
<ConsumerThatUpdates />
</NavProvider>
</div>
);
}
hope this helps!!
if you want to know more about how to use context effectively, i recooHow to use React Context effectively

React js Passing props to a component

Hello basically I need to pass some data to a component to be able to popular that component:
const Data = {
name: "a",
title: "b"
};
export default function App() {
return (
<Div className="App">
<Card name={Data.name} title={Data.title} />
</Div>
);
}
my card componen The component I need to receive data:
const Card = ({ props }) => {
return (
<div>
<h1>{props.name}</h1>
<p>{props.title}</p>
</div>
);
};
export default Card;
For some reason I am not getting these props on my card component
could someone help me with a solution to move props from a functional component to a functional component
example:
https://codesandbox.io/s/friendly-swanson-syuln
Your component declaration is wrong. You should only use brackets to destructure props, not for the entire props object.
const Card = (props) => {
just get rid of the curly braces around props in cards.js
import React from "react";
const Card = ( props ) => {
return (
<div>
<h1>{props.name}</h1>
<p>{props.title}</p>
</div>
);
};
export default Card;

Resources