I have a functional element in react js like this,
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{["Category", "Design", "Size", "Style"].map((ourOption) => (
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="filter__options__container"
>
<div className="filter__options__button">
{ourOption}
</div>
{isShown && <div className="filter__options__content"> Here I want to return the element using props </div>}
</div>
))}
</div>
);
}
I have created a files called, Category.js, Design.js, Size.js, Style.js.
Now I want to use the props so that I can concatenate like this <{ourOption}> <{ourOption}/> so that this will return element.
Any idea how to do this guys?
Choosing the Type at Runtime
First: Import the components used and create a lookup object
import Category from 'Category';
import Design from 'Design';
import Size from 'Size';
import Style from 'Style';
// ... other imports
const components = {
Category,
Design,
Size,
Style,
// ... other mappings
};
Second: Lookup the component to be rendered
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{["Category", "Design", "Size", "Style"].map((ourOption) => {
const Component = components[ourOption];
return (
...
<div className="filter__options__button">
<Component />
</div>
...
))}}
</div>
);
}
Alternatively you can just import and specify them directly in the array to be mapped.
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{[Category, Design, Size, Style].map((Component) => (
...
<div className="filter__options__button">
<Component />
</div>
...
))}
</div>
);
}
Instead of strings you could iterate over Array of Components
{[Category, Design, Size, Style].map((Component) => (
<Component/>
);
Ill do this as react document
//create components array
const components = {
photo: Category,
video: Design
.....
};
{
Object.keys(components).map((compName) => {
const SpecificSection = components[compName];
return <SpecificSection />;
})
}
Here is a small sample code that you can work with. Use direct component instead of trying to determine by strings.
const Comp1 = () => {
return <p>Comp1 Here</p>
}
const Comp2 = () => {
return <p>Comp 2 Here</p>
}
export default function App() {
return (
<div className="App">
{[Comp1, Comp2].map(Komponent => {
// use Komponent to prevent overriding Component
return <Komponent></Komponent>
})}
</div>
);
}
Related
I'm new to Reactjs. I'm unable to extract image url from the API. I'm really sorry if similar type of threads already exist in stackoverflow. Full code is below.
The API I used is: https://api.thecatapi.com/v1/breeds
import React, { Component } from "react";
import ReactDOM from "react-dom";
const Cat = ({
cat: {name, image},
}) => {
return(
<div className='catMain'>
<div className='catImage'>
<img src={image.url} alt={name} />
</div>
</div>
)
}
class App extends Component {
state = {
data: [],
};
componentDidMount() {
this.fetchCatData();
}
fetchCatData = async () => {
const url1 = "https://api.thecatapi.com/v1/breeds"
const response = await fetch(url1)
const data = await response.json()
this.setState({
data,
})
}
render() {
return (
<div className='main'>
<div className="cats">
<div className="catsInfo">
{this.state.data.map((cat) => (
<Cat cat={cat}/>
))}
</div>
</div>
</div>
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If you want to display every cat even though they might not have an image, you can do this:
const Cat = ({ cat: { name, image } }) => {
return (
<div className="catMain">
{image && (
<div className="catImage">
<img src={image.url} alt={name} />
</div>
)}
</div>
);
};
Then you can just display whatever else you want about the cat, without having to worry about image issues.
You have an assumption that every cat has an image, that's not the case, therefore you can filter empty images beforehand (there are tons of solutions, conditional rendering can be done too):
// cat.image is an object, which is truthy value therefore you can filter by it.
this.state.data.filter(cat => cat.image).map(cat => <Cat cat={cat} />)
// OR, conditional rendering
this.state.data.map(cat => cat.image && <Cat cat={cat} />)
// Or in the component itself, etc.
const Cat = ({ cat: { name, image } }) => {
return image ? (
<div className="catMain">
<div className="catImage">
<img src={image.url} alt={name} />
</div>
</div>
) : (
<Placeholder />
);
};
I'm trying to filter through some posts based on their category if a button is clicked. For example I have a button that when clicked the only posts that show up are related to software projects.
I have set up a function called searchHandler that I've passed through to my SidebarOptions component, which has the onclick event. But when I pass it through nothing happens.
Here is the code in the (parent) Home Component where the searchHandler is:
function Home() {
const [posts, setPosts] = useState([]);
const [filteredPosts, setFilteredPosts] = useState(null);
const searchHandler = (event) => {
const { value } = event.target;
setFilteredPosts(
value
? posts.filter(
(post) =>
post.question.question.includes(value)
)
: null
);
};
useEffect(() => {
db.collection("questions")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) =>
setPosts(
snapshot.docs.map((doc) => ({
id: doc.id,
question: doc.data(),
}))
)
);
}, []);
return (
<div className="home">
<div></div>
<Header searchHandler={searchHandler} />
<div className="home__content">
<Sidebar searchHandler={searchHandler} />
<Feed posts={filteredPosts || posts} />
<Widget />
</div>
</div>
);
}
Here is the (child) Sidebar component that receives it:
import React from "react";
import "../Style/Sidebar.css";
import SidebarOptions from "./SidebarOptions";
function Sidebar({ searchHandler }) {
return (
<div className="sidebar">
<SidebarOptions searchHandler={searchHandler} />
</div>
);
}
export default Sidebar;
And here is the (grandchild)SidebarOptions that the function is finally sent to:
function SidebarOptions({ searchHandler }) {
return (
<div className="sidebarOptions">
<div className="sidebarOption" onChange={() => searchHandler}>
<img
src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
</div>
);
};
I think you need to revisit your SideBarOptions component. I wonder if the onChange handler makes sense on a div. I think it should be input rather than a div if you want your user to type. Also, you need to call your handler with the value that is typed, here you are not calling the handler (notice the missing () after searchHandler in your code for SideBarOptions). Also, it will be better to add something like a debounce so that the filter is not triggered for every character that a user types. It should ideally be triggered once a user stops typing, debounce is precisely that.
Putting some code snippet below based on my guess about how it might work.
const SideBarOptions = ({ searchHandler }) => {
const [filterText, setFilterText] = useState("");
const handleFilter = () => {
searchHandler(filterText);
}
return (
<div className="sidebarOptions">
<input name="filterText" value={filterText} onChange={(e) => setFilterText(e.target.value)} />
<div className="sidebarOption" onChange={() => searchHandler}>
<img src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d" srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
<button onClick={handleFilter}>Filter</button>
</div>
</div>
);
}
So I was able to solve this by making a new function called categoryfilter in the Home component that went through the options and looked for the category of the posts in the database:
const categoryFilter = (category = "All") => {
const filtered =
category === "All"
? posts
: posts.filter(({ question }) => question.option === category);
setFilteredPosts(filtered);
};
I then passed that code as a prop to the sidebarOptions div after cleaning up the code a bit and used it to filter the posts based on the category name:
function SidebarOptions({ categoryFilter }) {
const categories = [
//Add all projects
{
name: "All",
imgUrl: "",
},
{
name: "Software Project",
imgUrl:
"https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d",
},
{
name: "Engineering Project",
imgUrl:
"https://c.pxhere.com/photos/a7/72/gears_cogs_machine_machinery_mechanical_printing_press_gears_and_cogs_technology-818429.jpg!d",
},
];
return (
<div className="sidebarOptions">
{categories.map((category) => (
<div
className="sidebarOption"
onClick={() => categoryFilter(category.name)}
>
{category.imgUrl && (
<img
src={category.imgUrl}
srcSet={category.imgUrl}
alt={category.name}
/>
)}
<p>{category.name}</p>
</div>
))}
<div className="sidebarOption">
<Add />
<p>Suggest Project Space</p>
</div>
</div>
);
}
export default SidebarOptions;
I have a few components, they have the same parameter with iterative values, like this:
import React from "react";
import Panel from "./Panel";
import Navbar from "./Navbar";
export default function App() {
return (
<div className="App">
<Panel id={1} />
<Navbar id={2} />
</div>
);
}
const Panel = ({ id }) => {
return (
<div>The id is {id}</div>
);
};
const Navbar = ({ id }) => {
return (
<div>The id is {id}</div>
);
};
Working example here: https://codesandbox.io/s/staging-pond-mpnnp
Now I'd like to use map to render those components at once in App.js, something like this:
export default function App() {
const compnentArray = ['Panel', 'Navbar'];
const RenderComponents = () => {
let _o = [];
return (
componentArray.map((item, index) => _o.push(<{item} id={index} />))
)
}
return (
<div className="App">
{RenderComponents()}
</div>
);
}
So that item renders component names. Is this possible?
Sure, you could make use of Array.map()'s second parameter which gives you the index in the array:
import React from "react";
import Panel from "./Panel";
import Navbar from "./Navbar";
const components = [Panel, Navbar];
export default function App() {
return (
<div className="App">
{components.map((Component, i) => (
<Component key={i} id={i + 1} />
))}
</div>
);
}
As mentioned in React's documentation, to render a component dynamically, just make sure you assign it to a variable with a capital first letter and use it like you'd use any other component.
You could swap strings with your actual component references and itererate over them directly in your JSX part, like this :
export default function App() {
const componentsArray = [Panel, Navbar];
return (
<div className="App">
{componentsArray.map((Component, index) => <Component key={index} id={index + 1} />)}
</div>
);
}
Though I would suggest to memoize them to improve performance once you're confortable enough with React to start using memoization.
import React from "react";
import Panel from "./Panel";
import Navbar from "./Navbar";
const components = [Panel, Navbar]; // notice you are using the components as items, not strings;
/*
if the components need props from the parent,
the `renderComponents()` function should be declared
inside the parent component (and possibly with a `useCallback()`
hook, to avoid unnecessary re-declarations on re-renders)
*/
function renderComponents() {
return components.map((comp, index) => <comp key={index} id={index} />) || null;
}
export default function App() {
return (
<div className="App">
{renderComponents()}
</div>
);
}
I have a parent and a child component, child component has a button, which I'd like to disable it after the first click. This answer works for me in child component. However the function executed on click now exists in parent component, how could I pass the attribute down to the child component? I tried the following and it didn't work.
Parent:
const Home = () => {
let btnRef = useRef();
const handleBtnClick = () => {
if (btnRef.current) {
btnRef.current.setAttribute("disabled", "disabled");
}
}
return (
<>
<Card btnRef={btnRef} handleBtnClick={handleBtnClick} />
</>
)
}
Child:
const Card = ({btnRef, handleBtnClick}) => {
return (
<div>
<button ref={btnRef} onClick={handleBtnClick}>Click me</button>
</div>
)
}
In general, refs should be used only as a last resort in React. React is declarative by nature, so instead of the parent "making" the child disabled (which is what you are doing with the ref) it should just "say" that the child should be disabled (example below):
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({isDisabled, onButtonClick}) => {
return (
<div>
<button disabled={isDisabled} onClick={onButtonClick}>Click me</button>
</div>
)
}
Actually it works if you fix the typo in prop of Card component. Just rename hadnlBtnClick to handleBtnClick
You don't need to mention each prop/attribute by name as you can use javascript Object Destructuring here.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = (props) => {
return (
<div>
<button {...props}>Click me</button>
</div>
)
}
You can also select a few props and use them differently in the child components. for example, see the text prop below.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card text="I'm a Card" isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({text, ...restProps}) => {
return (
<div>
<button {...restProps}>{text}</button>
</div>
)
}
I've created a very simplified code version of my problem to understand the REACT rendering using typescript. When I click a button which changes state in the lowest child element all parent elements are updated by the renderer and their children on other forks. How can I change the below so it doesn't do that.
import * as React from 'react';
import { connect } from 'react-redux';
import './Grid.css';
const RenderPopup = (key: number) => {
const open = () => setShowDialog(true);
const [showDialog, setShowDialog] = React.useState(false);
const close = () => setShowDialog(false);
if (!showDialog) {
return (
<div>
<button onClick={open}>do it</button>
</div>
)
}
else {
return (
<div>
<button onClick={close}>close
</button>
</div>
)
}
}
function Cell(key:number) {
return (
<div key={key}>
{key}
{RenderPopup(key)}
</div>
)
}
const Header = () => {
return (
<div className="gridRow">
{Cell(0)}
{Cell(1)}
{Cell(2)}
</div>
)
}
const Person = (rowNum: number) => {
return (
<div key={rowNum} className="gridRow">
{Cell(0)}
{Cell(1)}
{Cell(2)}
</div>
)
}
const Persons = () => {
return (
<div>
{Person(1)}
{Person(2)}
{Person(3)}
</div>
)
}
const Grid = () => {
return (
<div>
<Header />
<Persons />
</div>
);
}
export default connect()(Grid);