I have been looking on google a lot about how to pass props between functional components but very little information seems to be out there(either that or I don't know what keywords to input into google).
I do not need redux or data stored globally, I simply want to pass a JSON object stored in a hook from one component file to another.
I have three files one is the parent and the other two are children, I want to pass the data between the children files.
Paerent
import React, { useState } from "react";
import ShoppingPageOne from "./ShoppingPageOne";
import ShoppingPageTwo from "./ShoppingPageSecond";
function ShoppingPageContainer() {
const [pageone_Open, setPageone_Open] = useState(true);
const [pagetwo_Open, setPagetwo_Open] = useState(false);
const page_showHandler = () => {
setPageone_Open(!pageone_Open);
setPagetwo_Open(!pagetwo_Open);
};
return (
<div className="Shopping_Container">
<div className="Shopping_Box">
<h2>Online food shop</h2>
<div className="Shopping_Page_Container">
<ShoppingPageOne showOne={pageone_Open} next_ClickHandler={page_showHandler} />
<ShoppingPageTwo showTwo={pagetwo_Open} Reset_Data={page_showHandler} />
</div>
</div>
</div>
);
}
export default ShoppingPageContainer;
Child one:
import React, { useState, useEffect } from "react";
import Data from '../shoppingData/Ingredients';
const ShoppingPageOne = (props) => {
//element displays
const [pageone_show, setPageone_show] = useState("pageOne");
//stores quantities of items as JSON objects
const [Quantities, setQuantities] = useState({});
const [QuantiesProps, setQuantitiesProps] = useState(null)
useEffect(() => {
//sets info text using Json
if (props.showOne) {
setPageone_show("pageOne");
} else {
setPageone_show("pageOne hide");
}
}, [props.showOne]);
return (
<div className={"Shopping_Content " + pageone_show}>
{Data.map((Ingredients) => {
//updates Quanties Hook
const handleChange = (event) => {
setQuantities({
...Quantities,
[Ingredients.Name]: {
...(Quantities[Ingredients.Name] ?? {}),
quantities: event.target.value
}
});
};
return (<div className="Shopping_input" key={Ingredients.Name}>
<p>{Ingredients.Name} £{Ingredients.Price}</p>
<input onChange={handleChange.bind(this)} min="0" type="number"></input>
</div>)
})}
<div className="Shopping_Buttons">
<p onClick={props.next_ClickHandler}>Buy Now!</p>
</div>
</div>
);
};
export default ShoppingPageOne;
Child Two
import React, { useState, useEffect } from "react";
import Data from '../shoppingData/Ingredients';
const ShoppingPageSecond = (props) => {
//element displays
const [pagetwo_show, setPagetwo_show] = useState("pageTwo hide");
useEffect(() => {
//resets info text
if (props.showTwo) {
setPagetwo_show("pageTwo");
} else {
setPagetwo_show("pageTwo hide");
}
}, [props.showTwo]);
return (
<div className={"Shopping_Content " + pagetwo_show}>
<div className="Shopping_Buttons">
<p onClick={props.Reset_Data}>Shop Again</p>
</div>
</div>
);
};
export default ShoppingPageSecond;import React, { useState, useEffect } from "react";
import Data from '../shoppingData/Ingredients';
const ShoppingPageSecond = (props) => {
//element displays
const [pagetwo_show, setPagetwo_show] = useState("pageTwo hide");
useEffect(() => {
//resets info text
if (props.showTwo) {
setPagetwo_show("pageTwo");
} else {
setPagetwo_show("pageTwo hide");
}
}, [props.showTwo]);
return (
<div className={"Shopping_Content " + pagetwo_show}>
<div className="Shopping_Buttons">
<p onClick={props.Reset_Data}>Shop Again</p>
</div>
</div>
);
};
export default ShoppingPageSecond;
I simply want to pass the state contained in Quantities hook from Child One to Child Two when "Buy Now!" button is clicked.
What is the best approach to do doing this?
From my understand, I don't pass props between two children under the same parent. Instead, the parent holds the data, and pass the data and mutation function to children as props.
import React, { useState } from 'react';
const PageOne = ({ value, setValue }) => {
const PageOneFunction = () => {
setValue({
pageOneData: value.pageOneData + 1,
pageTwoData: value.pageTwoData + 1,
});
};
return (
<div>
<h4>Page One</h4>
<div>{value.pageOneData}</div>
<button onClick={PageOneFunction}>
Increase page one and page two value
</button>
</div>
);
};
const PageTwo = ({ value, setValue }) => {
const pageTwoFunction = () => {
setValue({
pageOneData: 0,
pageTwoData: 0,
});
};
return (
<div>
<h4>Page Two</h4>
<div>{value.pageTwoData}</div>
<button onClick={pageTwoFunction}>Reset</button>
</div>
);
};
const PageContainer = () => {
const [data, setData] = useState({
pageOneData: 0,
pageTwoData: 0,
});
return (
<div className="bg-white">
<PageOne value={data} setValue={setData} />
<PageTwo value={data} setValue={setData} />
</div>
);
};
export default PageContainer;
Related
I'm currently trying to figure out how to pass an array from one useState object to another across two different components. In my first useState I have an array called imagesOriginal, which gets filled with file paths dynamically to various different images like in the following:
[
"https://source.unsplash.com/WLUHO9A_xik/900x900",
"https://source.unsplash.com/R4K8S77qtwI/900x900",
"https://source.unsplash.com/jJGc21mEh8Q/900x900"
]
In my App.js, I construct it like so.
import React, { useCallback, useState } from 'react';
import ShowImage from './ShowImage.jsx';
import DropBox from './DropBox.js';
function App() {
const [imagesOriginal, setImages] = useState([]);
const onDrop = useCallback((acceptedFiles) => {
acceptedFiles.map((file, index) => {
const reader = new FileReader();
reader.onload = function (e) {
setImages((prevState) => [
...prevState,
{ id: index, src: e.target.result },
]);
};
reader.readAsDataURL(file);
return file;
});
}, []);
return (
<div className="App">
<div class="parent">
<div>
<h3>Originals</h3>
<DropBox onDrop={onDrop} />
<ShowImage images={imagesOriginal}/>
</div>
</div>
</div>
);
}
export default App;
The main issue comese in the ShowImage.jsx, where I want to pass that array of images to another useState, as I need to use both the array and the setItems to sort the array with a new order.
import React, { useState } from 'react';
import {
DndContext,
closestCorners,
MouseSensor,
TouchSensor,
DragOverlay,
useSensor,
useSensors,
} from '#dnd-kit/core';
import "./ShowImage.css"
import {arrayMove, SortableContext} from '#dnd-kit/sortable';
import {SortablePhoto} from './SortablePhoto.jsx';
const ShowImage = ({images}) => {
const [items, setItems] = useState([]);
setItems([...images]);
const [activeId, setActiveId] = useState(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
return(
<div class="scroll">
<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext items={items} strategy={() => {}}>
<div columns={1}
style={{
display: "grid",
gridAutoRows: `100px`,
gridGap: 10
}}
>
{items.map((url, index) => (
<SortablePhoto key={url.src} url={url.src} index={index}/>
))}
</div>
</SortableContext>
</DndContext>
</div>
);
function handleDragStart(event) {
setActiveId(event.active.id);
}
function handleDragOver(event) {
const {active, over} = event;
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
}
function handleDragEnd(event) {
setActiveId(null);
}
function handleDragCancel() {
setActiveId(null);
}
};
export default ShowImage;
I've tried using the line setItems([...images]); to try and pass the new items in, and also const [items, setItems] = useState(images);, but It never seems to update the items array. I'm probably doing something really stupid, but any help would be greatly appreciated.
You can create a function in your App component that wraps your setItems state modifier and pass this function as a prop to your nested ShowImage component where you could use it to manipulate the state. This way you won't need to maintain 2 different states.
// App.js
function App() {
const [imagesOriginal, setImages] = useState([]);
const setImagesWrapper = useCallback(val => {
setImages(val);
}, [setImages]);
//...
return (
<div className="App">
<div class="parent">
<div>
<h3>Originals</h3>
<DropBox onDrop={onDrop} />
<ShowImage
images={imagesOriginal}
setImages={setImagesWrapper}
/>
</div>
</div>
</div>
);
}
export default App;
// ShowImage.js
const ShowImage = ({ images, setImages }) => {
const [activeId, setActiveId] = useState(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
// ...
};
export default ShowImage;
I' m new to React and I'm building a simple React app that displays all the nations of the world on the screen and a small search bar that shows the data of the searched nation.
Here an image of the site
But I don't know how to show the country you want to click in the scrollbar.
Here the app.js code:
import React, { Component } from 'react';
import './App.css';
import NavBar from '../Components/NavBar';
import SideBar from './SideBar';
import CountryList from '../Components/SideBarComponents/CountryList';
import Scroll from '../Components/SideBarComponents/Scroll';
import Main from './Main';
import SearchCountry from '../Components/MainComponents/SearchCountry';
import SearchedCountry from '../Components/MainComponents/SearchedCountry';
import Datas from '../Components/MainComponents/Datas';
class App extends Component {
constructor() {
super();
this.state = {
nations: [],
searchField: '',
button: false
}
}
onSearchChange = (event) => {
this.setState({searchField: event.target.value});
console.log(this.state.searchField)
}
onClickChange = () => {
this.setState(prevsState => ({
button: true
}))
}
render() {
const {nations, searchField, button, searchMemory} = this.state;
const searchedNation = nations.filter(nation => {
if(button) {
return nation.name.toLowerCase().includes(searchField.toLowerCase())
}
});
return (
<div>
<div>
<NavBar/>
</div>
<Main>
<div className='backgr-img'>
<SearchCountry searchChange={this.onSearchChange} clickChange={this.onClickChange}/>
<SearchedCountry nations={searchedNation}/>
</div>
<Datas nations={searchedNation}/>
</Main>
<SideBar>
<Scroll className='scroll'>
<CountryList nations={nations} clickFunc/>
</Scroll>
</SideBar>
</div>
);
}
componentDidMount() {
fetch('https://restcountries.eu/rest/v2/all')
.then(response => response.json())
.then(x => this.setState({nations: x}));
}
componentDidUpdate() {
this.state.button = false;
}
}
export default App;
The countryList:
import React from 'react';
import Images from './Images';
const CountryList = ({nations, clickFunc}) => {
return (
<div className='container' style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(115px, 3fr))'}}>
{
nations.map((country, i) => {
return (
<Images
key={country.numericCode}
name={country.name}
flag={country.flag}
clickChange={clickFunc}
/>
);
})
}
</div>
)
}
export default CountryList;
And the images.js:
import React from 'react';
import './images.css'
const Images = ({name, capital, region, population, flag, numericCode, clickChange}) => {
return (
<div className='hover bg-navy pa2 ma1 tc w10' onClick={clickChange = () => name}>
<img alt='flag' src={flag} />
<div>
<h6 className='ma0 white'>{name}</h6>
{capital}
{region}
{population}
{numericCode}
</div>
</div>
);
}
export default Images;
I had thought of using the onClick event on the single nation that was going to return the name of the clicked nation. After that I would have entered the name in the searchField and set the button to true in order to run the searchedNation function.
I thank anyone who gives me an answer in advance.
To keep the actual structure, you can try using onClickChange in Images:
onClickChange = (newName = null) => {
if(newName) {
this.setState(prevsState => ({
searchField: newName
}))
}
// old code continues
this.setState(prevsState => ({
button: true
}))
}
then in onClick of Images you call:
onClick={() => {clickChange(name)}}
Or you can try as well use react hooks (but this will require some refactoring) cause you'll need to change a property from a parent component.
With that you can use useState hook to change the value from parent component (from Images to App):
const [searchField, setSearchField] = useState('');
Then you pass setSearchField to images as props and changes the searchField value when Images is clicked:
onClick={() => {
clickChange()
setSearchField(name)
}}
So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example
I'm new to React. I'm attempting to add an onClick event to a div element that will remove a className from another element. These elements are part of a loop in a map. I am attempting to use the useRef hook for this. I specifically don't want to toggle classNames, I want to remove it, and that's it. Then add it back with another onclick event from another element. This requirement is specific to my application. My current code removes the className from the last element, not the one I am targeting. Thanks in advance for any help!
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [kitchenItems, setkitchenItems] = useState([]);
useEffect(() => {
axios.get("./data.json").then((res) => {
setkitchenItems(res.data.kitchen);
});
}, []);
const navRef = React.useRef(null);
const onRemoveClick = (e) => {
navRef.current.classList.remove("red");
};
return (
<main>
{kitchenItems.map((item, index) => (
<div key={index} className="item">
<div onClick={onRemoveClick}>
<h2>{item.name}</h2>
<p ref={navRef} className="red">
{item.text}
</p>
</div>
</div>
))}
</main>
);
}
Here it is in CodeSandbox:
https://codesandbox.io/s/lively-moon-d7mm3
Save the indicator of whether an item should have the class or not into the kitchenItems state. Remove the ref.
useEffect(() => {
axios.get("./data.json").then((res) => {
setkitchenItems(res.data.kitchen.map(item => ({ ...item, red: true })));
});
}, []);
const onRemoveClick = (i) => {
setkitchenItems(
kitchenItems.map((item, j) => j !== i ? item : ({ ...item, red: false }))
);
};
<div onClick={() => onRemoveClick(i)}>
<h2>{item.name}</h2>
<p className={item.red ? 'red' : ''}>
The way you are approaching this is the way it's done with jQuery, that you change the dom elements. In React you would instead update the state and let the component render. I have added a new property on your objects but you may have another list or some other logic if you wish.
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [kitchenItems, setkitchenItems] = useState([]);
useEffect(() => {
axios.get("./data.json").then((res) => {
setkitchenItems(res.data.kitchen);
});
}, []);
const onRemoveClick = (index) => {
debugger;
const tmpItems = [...kitchenItems];
tmpItems[index].isRed = !tmpItems[index].isRed;
setkitchenItems(tmpItems);
};
return (
<main>
{kitchenItems.map((item, index) => (
<div key={index} className="item">
<div onClick={() => onRemoveClick(index)}>
<h2>{item.name}</h2>
<p className={item.isRed ? "red" : ""}>{item.text}</p>
</div>
</div>
))}
</main>
);
}
In CodeSandbox: https://codesandbox.io/s/keen-kirch-55whe?file=/src/App.js
Why not just use pureJS?
const onRemoveClick = (e) => {
var target = e.target;
if (target) {
if (target.classList && target.classList.contains("red")) {
target.classList.remove("red");
} else {
target.classList.add("red");
}
}
};
I would like to update the parent state from child component, which renders each object of the array of objects. The main goal of the child component is to update the original value from the array of objects.
I've the following code
Parent:
import { useState } from 'react';
import ExpenseItem from './expenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} {...expense} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
child:
import { useState, useRef } from 'react';
function ExpenseItem({ description, date, credit, debit }) {
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Once way, is to pass the parent state property (expenses) and the function that updates it (setExpenses) to the child Component via the props:
Parent:
import React from 'react';
import ReactDOM from 'react-dom';
import { useState } from 'react';
import ExpenseItem from './ExpenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
Checking: { expenses[0].description } | { expenses[1].description }
<hr/>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
Child:
import React from 'react';
import { useState, useRef } from 'react';
function ExpenseItem( props ) {
let { description, date, credit, debit } = props.expenses[props.index];
const setExpenses = props.setExpenses;
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
const expenses = [ ...props.expenses ]; // Get a copy of the expenses array
// Replace the current expense item
expenses.splice( props.index, 1, {
description: value, date, credit, debit
});
// Update the parent state
setExpenses( expenses );
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Working demo
This can get really complicated as you move along, so the best option is to look for some sort of State Management solution, like using the Context API.
Also, take a look at this interesting post that talks about using the map index value as a key value: Index as a key is an anti-pattern