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>
);
}
Related
I use a color theme in my project for this, I used a React context to pass the value in several components, everything worked fine for me until I decided to add another property inside the object in order to apply different colors to different components, for example a ThemeBackground property that takes green as a value it will be applied to the RoutesPage component and a side property that takes an orange color it will be applied to the SideBar component. The problem is that I cannot apply the side property for the SideBar component, I tried several options, but I did not succeed right now, I will show you everything in more detail in the pictures so that you clearly understand the problem and then I will provide you with the code
Notice the ThemesBackground property is successfully applied to the content but the problem is that I want to apply the side property to my sidebar at the moment I imported the ThemeBackground property for my sidebar so my sidebar applies red color but I think you already understood the problem in short property ThemeBackground should be applied to content and side property to sidebar
LessonThemes.jsx
import React, { useState, useEffect, createContext } from "react";
import SideBar from "./SideBar";
import RoutesPage from "../pages/Routes";
export const CounterContext = createContext(["color"]);
export default function LessonThemes(props) {
const [BackgroundTheme, SetBackgroundTheme] = useState(localStorage.getItem("color"));
const [themes, setThemes] = useState([
{ name: "G", ThemeBackground: "maroon", side: "orange" },
{ name: "R", ThemeBackground: "red", side: "aqua" },
{ name: "B", ThemeBackground: "blue", side: "pink" },
])
useEffect(() => {
localStorage.setItem("color", BackgroundTheme);
})
const SideBarPageContent = (SideBarPageContentBackground) => {
localStorage.setItem('color', SideBarPageContentBackground);
SetBackgroundTheme(SideBarPageContentBackground);
}
const list = themes.map((theme, index) => {
return (
<label key={index}>
<input
onChange={() => SideBarPageContent(theme.ThemeBackground)}
type="radio"
name="background"
/>{theme.name}</label>
);
})
return (
<CounterContext.Provider value={[BackgroundTheme, SetBackgroundTheme]}>
<SideBar list={list} {...props} />
<RoutesPage path={props.match} />
</CounterContext.Provider>
);
}
SideBar.jsx
import React from 'react';
import {CounterContext} from "./LessonThemes";
import SideBarMenu from "./SideBarMenu";
import '../css/Sidebar.css'
export default function SideBar(props) {
const [BackgroundTheme, SetBackgroundTheme] = React.useContext(CounterContext);
return (
<div className="wrappers">
<nav id="sidebar" className="sidebar-wrapper modal">
<div style={{background: BackgroundTheme}} className={"sidebar-page-content"}>
<div className="sidebar-brand">
<div className="sidebar-brand-container">
<div>
{props.list}
</div>
<div>
<span href="#">Theme</span>
</div>
</div>
</div>
<div className="sidebar-menu">
<SideBarMenu path={props.match.path}/>
</div>
...
</div>
</nav>
</div>
);
}
I don't know if it will be useful to you or not, but in addition I want to demonstrate the RoutesPage component, although everything is in order there
RoutesPage.jsx
import React from "react";
import {Route, Switch} from "react-router-dom";
import '../css/Sidebar.css'
import {CounterContext} from "../components/LessonThemes";
function RoutesPage(props) {
const {path} = props.path;
const routes = [
{
path: `${path}`,
exact: true,
component: () => <h2>Home</h2>
},
{
path: `${path}/Calendar`,
component: () => <h2>Test123</h2>
},
{
path: `${path}/Guardian`,
component: () => <h2>Shoelaces</h2>
}
];
const [BackgroundTheme, SetBackgroundTheme] = React.useContext(CounterContext);
return (
<>
<main style={{background: BackgroundTheme}} className="page-content">
<div className="page-container">
<h2>Pro Sidebar</h2>
<hr/>
<div className="tabs">
<Switch>
{routes.map((route, index) => (
<Route
key={index}
path={route.path}
exact={route.exact}
component={route.component}
/>
))}
</Switch>
</div>
</div>
</main>
</>
);
}
export default RoutesPage;
Is it possible that in the SideBar.jsx, the div style needs to be set to BackgroundTheme not SideBarBackgroundTheme? Also wouldn't it be passing in the object so you would still need to key into it for specific colors? Like BackgroundTheme.side?
The problem is that the side property is never being stored to the value of the context. Your context value is still just a string. The complete theme object exists is the local state of the LessonThemes component only.
In the onChange handler of your input you call SideBarPageContent which in turn calls SetBackgroundTheme, which updates the BackgroundTheme property which you pass to the context provider. The argument that you are passing to this function call is theme.ThemeBackground -- which is the background color only and not the entire object.
You likely want to refactor your code so that the context contains the whole object.
Within the SideBar component, it's unclear where you think that the variable SideBarBackgroundTheme is coming from, but that variable doesn't exist.
LessonThemes.jsx
import React, {useState, useEffect, createContext} from "react";
import SideBar from "./SideBar";
import RoutesPage from "../pages/Routes";
export const CounterContext = createContext([]);
export default function LessonThemes(props) {
const [SideBarTheme, SetSideBarTheme] = useState(localStorage.getItem("SideBarKey"));
const [PageContentTheme, SetPageContentTheme] = useState(localStorage.getItem("PageContentKey"));
const [themes, setThemes] = useState([
{
name: "G",
SideBar: "maroon",
PageContent: "blue",
},
{
name: "R",
SideBar: "gray",
PageContent: "green",
},
])
useEffect(() => {
localStorage.setItem("SideBarKey", SideBarTheme, "PageContentKey", PageContentTheme);
})
const SideBarPageContent = (PageContent, SideBar) => {
localStorage.setItem('PageContentKey', PageContent, 'SideBarKey', SideBar);
SetPageContentTheme(PageContent);
SetSideBarTheme(SideBar);
}
const list = themes.map((theme, index) => {
return (
<label key={index}>
<input
onChange={() => SideBarPageContent(theme.PageContent, theme.SideBar)}
type="radio"
name="background"
/>{theme.name}</label>
);
})
return (
<CounterContext.Provider value={{
SideBarValue: [SideBarTheme, SetSideBarTheme],
PageContentValue: [PageContentTheme, SetPageContentTheme]
}}>
<SideBar list={list} {...props} />
<RoutesPage path={props.match}/>
</CounterContext.Provider>
);
}
SideBar.jsx
export default function SideBar(props) {
const { SideBarValue } = React.useContext(CounterContext);
const [SideBarTheme, SetSideBarTheme] = SideBarValue;
return (
<div className="wrappers">
<nav id="sidebar" className="sidebar-wrapper modal">
<div style={{background: SideBarTheme}} className={"sidebar-page-content"}>
<div className="sidebar-brand">
<div className="sidebar-brand-container">
<div>
{props.list}
</div>
<div>
<span href="#">Theme</span>
</div>
</div>
...
RoutesPage.jsx
import React from "react";
import {Route, Switch} from "react-router-dom";
import '../css/Sidebar.css'
import {CounterContext} from "../components/LessonThemes";
function RoutesPage(props) {
const {path} = props.path;
const routes = [
{
path: `${path}`,
exact: true,
component: () => <h2>Home</h2>
},
{
path: `${path}/Calendar`,
component: () => <h2>Test123</h2>
},
{
path: `${path}/Guardian`,
component: () => <h2>Shoelaces</h2>
}
];
const { PageContentValue } = React.useContext(CounterContext);
const [PageContentTheme, SetPageContentTheme] = PageContentValue;
return (
<>
<main style={{background: PageContentTheme}} className="page-content">
...
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>
);
}
I am fairly new to Context API. So bassicaly, I want when I press on the Button in the ButtonComponent everything in ButtonComponent disapears as well in ImageComponent but when I click on the Button nothing happens. I am kind of stuck with this I would be very grateful if I got someone to help me if possible. Thanks in Advance!
//HiddenContext
import React from "react";
export const HiddenContext = React.createContext(false);
function HiddenProvider({ children }) {
const [hideButton, setHideButton] = React.useState(false);
function handleClick() {
setHideButton(true);
}
return (
<HiddenContext.Provider value={{ hideButton, handleClick }}>
{children}
</HiddenContext.Provider>
);
}
// App Component/Parent
import React, { useState } from 'react';
import './App.css';
export const HiddenContext = React.createContext(false);
function HiddenProvider({ children }) {
const [hideButton, setHideButton] = React.useState(false);
function handleClick() {
setHideButton(true);
}
return (
<HiddenContext.Provider value={{ hideButton, handleClick }}>
{children}
</HiddenContext.Provider>
);
}
function App() {
const { hideButton } = React.useContext(HiddenContext);
return (
<HiddenProvider>
<div className="App">
<ImageComponent hideButton={hideButton} />
</div>
</HiddenProvider>
);
}
//ButtonComponent
import React, {useState,ReactFragment} from 'react'
import { HiddenContext, } from './HiddenContext';
function ButtonComponent() {
const { hideButton, handleClick } = React.useContext(HiddenContext);
return (
<React.Fragment>
{!hideButton && (
<li>
<img className="image" src="./icons" alt="" />
<Button
onClick={handleClick}
variant="outlined"
className="button__rightpage"
>
Hide
</Button>
<caption className="text"> Hide</caption>
</li>
)}
</React.Fragment>
);
}
// ImageComponent
import React, {useState, ReactFragment} from 'react'
import { HiddenContext, } from './HiddenContext';
const ImageComponent = () => {
const { hideButton } = React.useContext(HiddenContext);
return (
<div>
{!hideButton && (
<React.Fragment>
<img src="icons/icon.png" alt="" />
<caption>Image </caption>
</React.Fragment>
)}
</div>
);
};
We created here 2 context - instead of 1
I make a codesendbox for us to see the fix.
https://codesandbox.io/s/focused-night-i95fr
We should create context only one time, to wrap the App component with the provider, and we can use this context like you did wherever we want
And relative to the beginner, it seems from your code that you understand what you are doing
about your comment - attached a pic
You are trying to access the context value outside the provider (in App).
Try to remove this from App, like so:
function App() {
return (
<HiddenProvider>
<div className="App">
<ImageComponent />
</div>
</HiddenProvider>
);
}
The title is pretty straightforward, I need to access a property (a ref to be precise) on a child element that is passed through the children of my component, which means that I can't pass the ref in the parent afaik.
Here's a minimal example to highlight my issue:
import React from "react";
class Child extends React.Component {
myRef = React.createRef();
render() {
return <div ref={this.myRef}>child</div>;
}
}
const Parent = ({ children }) => {
const myChild = React.Children.toArray(children).find(
child => child.type === Child
);
// I want to access this
console.log(myChild.myRef);
// but it's undefined
return (
<div>
<h1>Parent</h1>
{children}
</div>
);
};
// I can't really change this component
export default function App() {
return (
<div className="App">
<Parent>
<Child />
</Parent>
</div>
);
}
I made a codesandbox highlighting my issue https://codesandbox.io/s/eloquent-wing-e0ejh?file=/src/App.js
Rather than declaring ref in <Child/>, you should declare ref in your <Parent/> and pass it to the child.
import React from "react";
class Child extends React.Component {
render() {
return <div ref={this.props.myRef}>child</div>;
}
}
const Parent = ({ children }) => {
const myRef = React.useRef(null);
// access it from here or do other thing
console.log(myRef);
return (
<div>
<h1>Parent</h1>
{ children(myRef) }
</div>
);
};
export default function App() {
return (
<div className="App">
<Parent>
{myRef => (
<Child myRef={myRef} />
)}
</Parent>
</div>
);
}
I'm working on a simple list maker, to do list app using create-react-app and I'm having some trouble puzzling out the functionality. What I'm trying to accomplish with this app:
I want to be able to enter text into an input, push the button or press enter, and whatever text will be listed on the body of the app.
I want to be able to create a button that will delete the list items once the task or objective is complete
My code is broken up into these components so far:
App,
ListInput,
ItemList,
Item
The code for App is
import React, { Component } from 'react';
import './App.css';
import Navigation from './components/Navigation';
import ListInput from './components/ListInput';
import ListName from './components/ListName';
import Item from './components/Item';
import ItemList from './components/ItemList';
class App extends Component {
constructor() {
super();
this.state = {
input: '',
items: []
};
}
addItem = () => {
this.setState(state => {
let inputValue = this.input.current.value;
if (inputValue !== '') {
this.setState({
items: [this.state.items, inputValue]
})
}
})
}
onButtonEnter = () => {
this.addItem();
}
render() {
return (
<div className="App">
<Navigation />
<ListName />
<ListInput addItem={this.addItem}
onButtonEnter={this.onButtonEnter} />
<Item />
<ItemList />
</div>
);
}
}
export default App;
The code for ListInput is :
import React from 'react';
import './ListInput.css';
const ListInput = ({ addItem, onButtonEnter }) => {
return (
<div>
<p className='center f2'>
{'Enter List Item'}
</p>
<div className='center'>
<div className='center f3 br-6 shadow-5 pa3 '>
<input type='text'
className='f4 pa2 w-70 center'
placeholder='Enter Here'
/>
<button className='w-30 grow f4 link ph3 pv2 dib white bg-black'
onClick={onButtonEnter}
onSubmit={addItem} >
{'Enter'}
</button>
</div>
</div>
</div>
);
}
export default ListInput;
The code for Item is:
import React from 'react';
const Item = ({text}) =>{
return (
<div>
<ul>{text}</ul>
</div>
)}
export default Item;
And the code for ItemList is :
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{item.map(items => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
In my react app I am returning an error of 'item' is not defined and I'm confused why.
In your App.js you need to pass items as a prop to ItemList component like
<ItemList items={this.state.items} />
Also in addItem function pushing inputValue to items array isn’t correct do something like below
addItem = () => {
this.setState(state => {
let inputValue = this.input.current.value;
if (inputValue !== '') {
this.setState(prevState => ({
items: [...prevState.items, inputValue]
}));
}
})
}
And in ItemList.js do conditional check before doing .map also some typo errors in .map
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{items && items.map(item => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
Try with above changes This would work
Please excuse me if there are any typo errors because I am answering from my mobile
Your ItemList was not correct. Take a look at corrected snippet below you need to map on items and not item (hence the error item is not defined). Also, you need to items as a prop to ItemList in your app.js
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{items.map(item => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
In app.js add following line. Also, I don't see what is doing in your app.js remove it.
<ItemList items={this.state.items}/>
Seems like you have a typo in ItemList.
It receives items (plural) as prop but you are using item.
const ItemList = ({ items }) => {
return (
<div>
{items.map(items => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
And don't forget to actually pass the items prop to 'ItemList':
<ItemList items={this.state.items} />