I need some ideas for a folder structure/mental model in React.
I have an Item component. It stays the same.
It can be wrapped in either a Link with a URL prop or a Button with an onClick prop.
My current folder structure solution looks like this:
-Item
-wrapperComponents
-Link
-Button
Both the Link and Button components wrap around the children prop. Much like this:
react stuff ...
return(
<button onClick={props.handleOnClick}>
{props.children}
</button>
)
And this is how I call them:
<Button>
<Item />
</Button>
or
<Link>
<Item />
</Link>
I am looking for a better, more elegant solution.
I've tried sending the wrapper components to the Item but React doesn't allow to use them as a wrapper that would take children.
This is what you can do:
import "./styles.css";
import Item from "./Item";
import Wrapper1 from "./Wrapper1";
export default function App() {
return (
<div className="App">
<Item wrapper={Wrapper1} />
</div>
);
}
Item component:
const Item = ({ wrapper: Wrapper }) => {
return (
<Wrapper>
<div>This is my child</div>
</Wrapper>
);
};
export default Item;
Wrapper component:
const Wrapper1 = ({ children }) => {
return (
<div>
This is my wrapper
{children}
</div>
);
};
export default Wrapper1;
https://codesandbox.io/s/quirky-vaughan-4op2l1?file=/src/index.js
I've come up with this solution with the help you you guys
https://codesandbox.io/s/create-react-app-forked-wlmkyv?file=/src/App.js
Main component App.js:
import * as React from "react";
import Wrapper from "./components/Wrapper";
import Item from "./components/Item";
function App() {
return (
<div>
<Item wrapper={Wrapper} wrapperProps={{ color: "orange" }} />
</div>
);
}
export default App;
Item component Item.js:
import * as React from "react";
function Item(props) {
const Wrapper = props.wrapper;
const wrapperProps = props.wrapperProps;
return (
<Wrapper {...wrapperProps}>
<div>An Item</div>
</Wrapper>
);
}
export default Item;
Wrapper component Wrapper.js:
import * as React from "react";
function Wrapper(props) {
return (
<div style={{ backgroundColor: props.color || "red" }}>
{props.children}
</div>
);
}
export default Wrapper;
Related
I want to pass a value which I am receiving in a function like this:
const ViewDetails = item => () => {
console.log(item);
toggleModal();
}
I want to pass the item to Modal component like open,onclose which is called in the Main function:
return (
<Layout title="Dashboard" className="container-fluid">
{<Modal open={modalStatus} onClose={() => setModalStatus(false)} />}
<div className="row">
<div className="col-sm-3">
<UserLinks />
</div>
<div className="col-sm-9">
<UserInfo />
{orders ? <PurchaseHistory /> : ""}
</div>
</div>
</Layout>
)
I am expecting to have something like this:
{<Modal open={modalStatus} onClose={() => setModalStatus(false)} ***item={item}***/>}
so that I can use the values inside item in Modal component.
I would like to add more to #GODWIN GODWIN comment in regards context API, by providing a very simple example along with the React docs about Context hook
Generally in practice people tend to wrap providers at App.js, for the sake of simplicity I am going to wrap at index.js file.
src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App'
import { ModalProvider } from './context/ModalContext'
ReactDOM.createRoot(
document.querySelector('#root')
).render(
/**
* #dev Note everything inside ModalPrivder has access
* to the values provided, such as open, setOpen
*/
<ModalProvider>
<App />
</ModalProvider>
)
src/context/ModalContext.jsx
import React, {useState, createContext, useContext } from 'react'
/**
* #dev inside your createContext object you can pass in
* default values that will be passed in value at provider
*/
export const ModalContext = createContext({
open: false
})
/**
* #dev your provider will enable you to access value all your
* children components. NOTE it will not be able to access your
* parent components.
*/
export function ModalProvider(props) {
const [open, setOpen] = useState(false)
return (
<ModalContext.Provider value={{ open, setOpen }}>
{props.children}
</ModalContext.Provider>
)
}
src/components/Modal.jsx
import { useContext } from 'react'
function Modal(props) {
const { open, setOpen } = useContext(ModalContext)
return (
<>
{ open ?
(<div>
<p>test</p>
<>{props.children}</>
<button onClick={() => setOpen(false)}>Close Close</button>
</div>) :
(<button onClick={() => setOpen(true)}>Open Modal</button>)
}
</>
)
}
export default Modal
src/App.jsx
function App(props) {
return (
<div className='App'>
<h1>Hello React.</h1>
<h2>Start editing to see some magic happen!</h2>
<Modal>
<p> You see content here</p>
</Modal>
</div>
);
}
export default App
I hope this give you a good direction on how to use React's context hook, please note that this is a very basic source code, to understand how props.children works and context hook.
You can try this:
import React from "react";
function Eexample() {
const ViewDetails = (item) => () => {
console.log(item);
toggleModal();
return (
<Modal item={item} /> //This is passing item into the Modal component
)
};
return <div>
{ViewDetails} {/*This returns the function viewDetails*/}
</div>;
}
export default Eexample;
For your Modal component:
function Modal({ item }) { // with this distructuring, you can use the item inside the Modal component
return (
<Layout title="Dashboard" className="container-fluid">
{<Modal open={modalStatus} onClose={() => setModalStatus(false)} />}
<div className="row">
<div className="col-sm-3">
<UserLinks />
</div>
<div className="col-sm-9">
<UserInfo />
{orders ? <PurchaseHistory /> : ""}
</div>
</div>
</Layout>
)
}
Or Consider using context API, it enables you to dispatch the item to your reducer.js file and pull it in your Modal component using StateProvider.js file.
You have to take state for this item. When viewDetails function triggered from inside this function you can set this state with this item afte can be pass this state as a props any component
I have a Navbar component with an anchor tag containing an onClick event. On click, a value (navvalue) is passed to the function Testfunction, which is a separate component. I want to import Testfunction into the Content component so that I can have access and display the value coming from Navbar (navvalue). How do I access “navvalue” in Content? This is an assignment in a react course I´m taking. I should use props. I´m not supposed to use either state or React Route since we haven´t reach those topics yet. Thank you for your help!
Here´s my code:
App.js
import "./styles.css";
import React from "react";
import Navbar from "./Navbar";
import Content from "./Content";
import Testfunction from "./Testfunction";
export default function App() {
return (
<div className="App">
<Navbar onPageChange={Testfunction} />
<Content />
</div>
);
}
Navbar.js
import React from "react";
const Navbar = (props) => {
const navvalue = "Nav Value";
return (
<a
className="nav-link active text-uppercase"
aria-current="page"
href="#"
onClick={() => props.onPageChange(navvalue)}
>
{navvalue}
</a>
);
};
export default Navbar;
Testfunction.js
const Testfunction = (navvalue) => {
return navvalue;
};
export default Testfunction;
Content.js
import React from "react";
import Testfunction from "./Testfunction";
const Content = () => {
const navvalue = Testfunction();
return (
<p>Here´s the content. Insert value coming from Navbar here: {navvalue}</p>
);
};
export default Content;
To share some state in both <Navbar /> and <Content /> you can put state to their parent -> App. Also to <Navbar /> we pass setter function to update state which is in parent. So it can be like this:
import React from "react";
export default function App() {
const [navValue, setNavValue] = React.useState();
return (
<div className="App">
<Navbar setNavValue={setNavValue} />
<Content navValue={navValue} />
</div>
);
}
const Navbar = ({ setNavValue }) => {
const value = "Nav Value";
return <button onClick={() => setNavValue(value)}>{value}</button>;
};
const Content = ({ navValue }) => {
return (
<p>Here´s the content. Insert value coming from Navbar here: {navValue}</p>
);
};
I'm new to Typescript and not very familiar with the useContext hook. Basically, I have two simple components. I would like to add the items from my left component to the list on the right when I click on the button under them. My items have a name and description property. I just watch to display item.name on the side div on the right.
I would like to try and do it with useContext but I'm not sure where to start even after reading the documentation and a bunch of examples. They all seem too complicated for my tiny little example.
From what I understand, I need to:
Create something like AppContext.tsx
Create a context with createContext() // not sure about the arguments I have to put it in here with Typescript
Create a provider? // not sure about that either
Wrap my two components with the context provider
So any hint on the procedure would be appreciated. Thank you!
function App() {
return (
<div className="App">
<ItemList />
<ItemContainer />
</div>
);
}
My item list component:
function ItemList() {
return (
<div className="itemlist">
{items.map((item, index) => (
<div key={index}>
<div>{item.name}</div>
<div>{item.description}</div>
<button>Add to sidebar</button>
</div>
))}
</div>
);
}
And finally, my container on the right side:
function ItemContainer() {
return (
<div>
<h1>List of items</h1>
<p>Number of items: {}</p>
</div>
);
}
You can do something like this:
First create a context file named for example ItemList.context.tsx :
import React, {
createContext,
Dispatch,
FunctionComponent,
useState
} from "react";
import { Item } from "./data";
type ItemListContextType = {
itemList: Item[]; // type of your items that I declare in data.ts
setItemList: Dispatch<React.SetStateAction<Item[]>>; //React setState type
};
export const ItemListContext = createContext<ItemListContextType>(
{} as ItemListContextType
);
export const ItemListContextProvider: FunctionComponent = ({ children }) => {
const [itemList, setItemList] = useState<Item[]>([]);
return (
<ItemListContext.Provider
value={{ itemList: itemList, setItemList: setItemList }}
>
{children}
</ItemListContext.Provider>
);
};
You can then add the context provider in the parent component ( App.tsx in your example):
import "./styles.css";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { ItemListContextProvider } from "./ItemList.context";
export default function App() {
return (
<div className="App">
<ItemListContextProvider>
<ItemList />
<ItemContainer />
</ItemListContextProvider>
</div>
);
}
and you can finally access your Item List by using the hook useContext in your two components:
for ItemList.tsx where you need to set the list (and optionally get the list to avoid putting twice an item):
import { useContext } from "react";
import { data, Item } from "./data";
import { ItemListContext } from "./ItemList.context";
const items: Item[] = data;
export default function ItemList() {
const { itemList, setItemList } = useContext(ItemListContext); // here you get your list and the method to set the list
const addItemToItemList = (item: Item) => {
//you are using the itemList to see if item is already in the itemList
if (!itemList.includes(item)) setItemList((prev) => [...prev, item]);
};
return (
<div className="itemlist">
{items.map((item, index) => (
<div style={{ marginBottom: 15 }} key={index}>
<div style={{ fontWeight: 800 }}>{item.name}</div>
<div>{item.description}</div>
<button onClick={() => addItemToItemList(item)}>
Add to sidebar
</button>
</div>
))}
</div>
);
}
And in your ItemContainer.tsx you only need the list so you can import only the setItemList from the context with useContext:
import { useContext } from "react";
import { ItemListContext } from "./ItemList.context";
export default function ItemContainer() {
const { itemList } = useContext(ItemListContext);
return (
<div style={{ flexGrow: 4 }}>
<h1 style={{ textAlign: "center" }}>List of items</h1>
<p>Number of items: {itemList.length}</p>
{itemList.length > 0 && (
<ul>
{itemList.map((item, i) => (
<li key={i}>{item.name}</li>
))}
</ul>
)}
</div>
);
}
UPDATE with a Router
It's quite the same thing you only need to wrap your browser router in the context provider if you want it to be at the highest place in your app (for a them provider or a dark mode provider for example):
export default function App() {
return (
<div className="App">
<ItemListContextProvider>
<BrowserRouter>
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/itemList" component={ItemListPage} />
</Switch>
</BrowserRouter>
</ItemListContextProvider>
</div>
);
}
but I suggest you to put your provider the nearest place where your subscribers components are.
You can for example create a page component that will be use in the browser router and put in it the provider, like this:
ItemListPage.tsx
import React, { FunctionComponent } from "react";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { Link } from "react-router-dom";
import { ItemListContextProvider } from "./ItemList.context";
const ItemListPage: FunctionComponent = () => {
return (
<>
<ItemListContextProvider>
<h1 style={{ alignSelf: "flex-start" }}>ITEM LIST</h1>
<Link to="/">homePage</Link>
<div className="itemListPage">
<ItemList />
<ItemContainer />
</div>
</ItemListContextProvider>
</>
);
};
export default ItemListPage;
and of course you remove the context provider in your App.tsx and it should look like :
App.tsx
import React, { FunctionComponent } from "react";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { Link } from "react-router-dom";
import { ItemListContextProvider } from "./ItemList.context";
const ItemListPage: FunctionComponent = () => {
return (
<>
<ItemListContextProvider>
<h1 style={{ alignSelf: "flex-start" }}>ITEM LIST</h1>
<Link to="/">homePage</Link>
<div className="itemListPage">
<ItemList />
<ItemContainer />
</div>
</ItemListContextProvider>
</>
);
};
export default ItemListPage;
Hi I mimic a project and there is no problem but can not display the ui,I am not sure what kind of problem it is.The object project link:https://codesandbox.io/s/learning-react-color-organizer-2-iytxb?file=/src/StarRating.js
My project link:https://codesandbox.io/s/nervous-flower-xv669?file=/src/App.js
My project has 2 warnings,this first warning is Each child in a list should have a unique "key" prop. which direct to the app.js:
import React, { useState } from "react";
import Data from "./color-data.json";
import RatingList from "./RatingList";
export default function App() {
const [colors,setColors]=useState(Data)
const removeId=id=>{
const newcolors=colors.filter(color=>color.id !== id);
setColors(newcolors);
}
return (
<div>
{
<RatingList
colors={colors}
onDelete={removeId}
/>
}
</div>
);
}
the second warning is Cannot update a component (App) while rendering a different component (ColorRating).which direct to the ColorRating.js:
import React from "react";
import Star from "./Star";
import { AiFillDelete } from "react-icons/ai";
export default function ColorRating({id,
title,
color,
rating,
onDelete = (f) => f,
}) {
return (
<div>
<h1>{title}</h1>
<h1>
<AiFillDelete onClick={onDelete(id)} />
</h1>
<h1 style={{ backgroundColor: color, padding: "30px" }}> </h1>
{[...Array(5)].map((n, i) => (
<Star key={i} selected={rating > i} />
))}
<p>{rating} of 5 stars</p>
</div>
);
}
No, I mean remove the { } curly brackets from the return block.
And also, change this line:
<AiFillDelete onClick={onDelete(id)} />
to this (right now you invoke it's on every rerender):
<AiFillDelete onClick={() => onDelete(id)} />
I have a component called Component1 in which I have the following code:
import React, { useState } from "react";
import Popover from "material-ui-popup-state/HoverPopover";
import Fab from "#material-ui/core/Fab";
import {
usePopupState,
bindHover,
bindPopover
} from "material-ui-popup-state/hooks";
import PaletteIcon from "#material-ui/icons/Palette";
import Colors from "./Colors";
const DEFAULT_COLOR = "red";
const COLORS = [/*list of colors*/];
const Component1 = ({ classes }) => {
const popupState = usePopupState({
variant: "popover",
popupId: "demoPopover"
});
const [selectedColor, setSelectedColor] = useState(DEFAULT_COLOR);
return (
<div className="box" style={{ backgroundColor: selectedColor }}>
<Fab variant="extended" {...bindHover(popupState)}>
<PaletteIcon />
</Fab>
<Popover
>
<div className="color-palette">
{COLORS.map((color) => (
<Colors
key={color}
selected={selectedColor === color}
onClick={setSelectedColor}
color={color}
/>
))}
</div>
</Popover>
</div>
);
};
export default Component1;
This component is imported in Component2 where the code is:
import React from "react";
import Component1 from "./Component1";
import Fab from "#material-ui/core/Fab";
import DeleteIcon from "#material-ui/icons/Delete";
function Component2(props) {
function handleClick() {
props.onDelete(props.id);
}
return (
<div className="note" style={{ backgroundColor: "selectedColor" }}>
<h1>{props.title}</h1>
<p>{props.content}</p>
<Fab onClick={handleClick}>
<DeleteIcon fontSize="small" />
</Fab>
<HoverPopover />
</div>
);
}
export default Component2;
In component2 I need to use the const selectedColor for styling purpose for div with class="note". However the issue is when I select colors from COLORS list the background-color of div with class="note" is not changing. I tried many options but I don't understand how to do it correctly. Please tell me how to do it right.
To share the "selectedColor" variable, which is actually a state, you would have to pass it through the props to the child component
Your "Component2" should declare the state "selectedColor", and this state and its function must be passed by the props to your "Component1".
https://reactjs.org/tutorial/tutorial.html#lifting-state-up