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.
Related
I am try to add search feature to an existing lists of robot names.
In order to do so I am trying to useState hooks. I have an App component and Header component which has the input tag for search field.
Error I am getting is 'InputEvent' is assigned a value but never used.
Below is the code for App component (main component).
import "./App.css";
import Header from "./Header";
import Robo from "./Robo";
import { robots } from "./robots";
import { useState } from "react";
function App() {
const [query, setQuery] = useState("");
const InputEvent = (e) => {
const data = e.target.value;
setQuery(data);
const extraction = robots
.filter((curElem, index) =>
robots[index].name.toLowerCase().includes(query)
)
.map((curElem, index) => {
return (
<Robo
key={robots[index].id}
id={robots[index].id}
name={robots[index].name}
email={robots[index].email}
/>
);
});
return (
<div className="App">
<Header query={query} InputEvent={InputEvent} />
<div className="robo-friends-container">{extraction};</div>
</div>
);
};
}
export default App;
Child component
import React from "react";
import "./header.css";
const Header = ({ query, InputEvent }) => {
return (
<>
<div className="headerText">ROBO FRIENDS</div>
<div>
<input
type="text"
id="lname"
name="lname"
placeholder="Search"
value={query}
onChange={InputEvent}
/>
</div>
</>
);
};
export default Header;
Here is my answer in stackblitz app
https://stackblitz.com/edit/stackoverflow-robots-filter?file=App.tsx,Robo.tsx,Header.tsx,robots.ts
I have altered the code a bit.. you can fork the project and play with it..
You can add debounce option to your input, which prevents unwanted re-renders
Adding the changes:
function App() {
const [query, setQuery] = useState(undefined);
const [filteredRobots, setFilteredRobots] = useState([]);
useEffect(() => {
console.log(query);
const filteredRobots = robots.filter((robot) => {
return robot.name.includes(query);
});
if (filteredRobots.length) {
setFilteredRobots(filteredRobots);
}
}, [query]);
const onQueryChange = (e) => {
const data = e.target.value;
setQuery(data);
};
const renderRobots = () => {
if (!query || !query.length) {
return <p>{'Search to find Robots'}</p>;
}
if (filteredRobots && filteredRobots.length && query && query.length) {
return filteredRobots.map((filteredRobot) => (
<Robo
key={filteredRobot.id} //id is unique key in your data
name={filteredRobot.name}
id={filteredRobot.id}
email={filteredRobot.email}
/>
));
}
return <p>{'No Robots Found'}</p>;
};
return (
<div className="App">
<Header query={query} InputEvent={onQueryChange} />
{renderRobots()}
</div>
);
}
Problems in your code:
Const InputChange is a function that can be used as prop for any React component .. but you have added InputChange inside the InputChange named function itself which is incorrect
Extraction is a jsx variable which is created from Array.filter.. on each item, filter passes a item[index] to the filter function.. you dont want to do robots[index].name.toLowerCase().includes(query).. instead you could have done curElem.name.toLowerCase().includes(query) and same applies for Array.map
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'/>}/>
Code like this:
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
const MenuItems = () => {
const renderMenuItems = () => {
if (menuitems && menuitems.length){
console.log("Render")
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
}
return (
renderMenuItems()
)
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{menuitems && <MenuItems/>}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
When the input tag is used, the variable MenuItems is reloaded. What's wrong in my code? Why is it rerendering and how to prevent this from happening?
As far as I understand, this happens after setting the variable "searchTi" through the function "setSearchTic". This updates the variable "menuitems " and reloads this section of code:
{menuitems && <MenuItems/>}
you are using MenuItems like it was a component, but it's only a render function. should just call it like this:
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
const renderMenuItems = () => {
if (menuitems && menuitems.length){
console.log("Render")
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
return null;
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{renderMenuItems()}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
Compact example:
Also, there's no need to check to the menuitems.length. Best way to render the menu items would be something like this:
const renderMenuItems = () => menuitems?.map((name) => <button key={name}>{name}</button>);
useMemo:
If you want to avoid re-render the menu items over and over, you should also use React.useMemo like this:
const renderMenuItems = useMemo(() => menuitems?.map((name) => <button key={name}>{name}</button>), [menuitems]);
Note that it's now an object (similar to your JSX), and you should not call it, just put it as part of your JSX like this:
return (
<div className="App">
{renderMenuItems}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
I came across your question and it seemed interesting so I researched about it and finally, I found out that NEVER CREATE A COMPONENT INSIDE ANOTHER FUNCTION COMPONENT.
And I found an article written by Kuldeep Bora.
you can go through the article to understand this completely.
https://dev.to/borasvm/react-create-component-inside-a-component-456b
React components automatically re-render whenever there is a change in their state or props.
Function renderMenuItems will re-create on every re-render and it is not an issue.
But if you don't want this behavior you can use the useCallback hook, and then the function will re-create only when one of the dependencies will change.
useCallback hook docs: https://reactjs.org/docs/hooks-reference.html#usecallback
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
// this function will re-create for every re-render
const renderMenuItems = () => {
if (menuitems && menuitems.length){
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{renderMenuItems()}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
im having this issue since i moved the code from React Javascript to React Typescript
I have this simple hook that switch state from on/off (or true false) (code below)
I'm struggling into this since this code were working on Javascript
Code here to test it
So the toggle function has some error but cannot figure it out. Some help would be much appreciated
// useToggle.tsx
import { useState } from "react";
export const useToggle = (initialMode = false) => {
const [open, setOpen] = useState(initialMode);
const toggle = () => setOpen(!open);
return [open, setOpen, toggle];
};
My switch component
// Switch.tsx
import { useToggle } from "./useToggle";
import React from "react";
export const Switch = () => {
const [open, toggle] = useToggle();
const [open2, toggle2] = useToggle();
return (
<>
<p>Testing toggle 1: {`${open}`}</p>
<p>Testing toggle 2: {`${open2}`}</p>
<button
onClick={() => {
toggle();
}}
>
Toggle 1
</button>
<button
onClick={() => {
toggle2();
}}
>
Toggle 2
</button>
</>
);
};
And now i use my switch component
// App.tsx
import * as React from "react";
import "./styles.css";
import { Switch } from "./Switch";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Switch />
</div>
);
}
Two changes you need to do to solve the problem
First, to get rid of the error you need to return the value with const assertion like
return [open, toggle, setOpen] as const;
Here is a Link to github issue for the error
and secondly you are using toggle as the second argument while destructing, so you need to return it as the second argument too
Working DEMO
[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>
);
}