onClick method called automatically react - reactjs

I have a react component named <Filters /> which renders a component <PriceFilter />. The <PriceFilter /> component renders a <Filter /> component and some other JSX.
<Filters /> component:
const Filters = ({ price, setPrice }) => {
return (
<div>
<PriceFilter price={price} setPrice={setPrice} />
</div>
);
};
<PriceFilter /> component:
const PriceFilter = ({price, setPrice}) => {
const [show, setShow] = useState(false);
const toggleShow = () => setShow(!show);
return (
<div>
<Filter show={show} toggleShow={toggleShow}>
Price
</Filter>
{show && (
<FilterContainer>
<div>
<div onClick={() => setPrice('$')}>
<span>Inexpensive</span>
</div>
<div onClick={() => setPrice('$$')}>
<span>$$</span>
</div>
<div onClick={() => setPrice('$$$')}>
<span>$$$</span>
</div>
<div onClick={() => setPrice('$$$$')}>
<span>$$$$</span>
</div>
</div>
</FilterContainer>
)}
</div>
);
};
<Filter /> component:
const Filter = ({children, show, toggleShow}) => {
return (
<div>
<span>{children}</span>
{show ? <KeyboardArrowUpIcon onClick={toggleShow} /> : <KeyboardArrowDownIcon onClick={toggleShow} />}
</div>
);
};
Clicking on any of the options (Inexpensive/$$/$$$/$$$$) in the <PriceFilter /> component not only sets the state to the value passed ('$'/'$$'/'$$$'/'$$$$'), but it is also triggering the toggleShow function for some reason. Can someone please provide a solution to this so that the toggleShow function isn't called when clicking on the option.

The code works with minimal setup. See what is going wrong in higher level component Test where Filters is being rendered.
export const Test = () => {
const [price, setPrice] = useState('inexpensive')
return(
<Filters price={price} setPrice={setPrice} />
)
}
const Filters = ({ price, setPrice }) => {
return (
<div>
<PriceFilter price={price} setPrice={setPrice} />
</div>
);
};
const PriceFilter = ({price, setPrice}) => {
const [show, setShow] = useState(false);
const toggleShow = () => {
setShow(!show);
}
return (
<div>
<Filter show={show} toggleShow={toggleShow}>
{price}
</Filter>
{show && (
<div>
<div>
<div onClick={() => setPrice('$')}>
<span>Inexpensive</span>
</div>
<div onClick={() => setPrice('$$')}>
<span>$$</span>
</div>
<div onClick={() => setPrice('$$$')}>
<span>$$$</span>
</div>
<div onClick={() => setPrice('$$$$')}>
<span>$$$$</span>
</div>
</div>
</div>
)}
</div>
);
};
const Filter = ({children, show, toggleShow}) => {
return (
<div>
<span>{children}</span>
{show ? <div onClick={toggleShow}>up</div> : <div onClick={toggleShow}>down</div>}
</div>
);
};

It's working!! I didn't get why but there was a problem in the parent component. I had created a local component in the parent and rendered it there. I just extracted that local component in an independent component and it works now.

Related

Warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form>. How to solve this console warning?

const PaymentDetailsTabs = () => {
const [modalShow, setModalShow] = useState(false);
return (
<>
<div className="Payment-details-tab">
<div>
<p>There are no any payment methods yet</p>
<RedButton onClick={() => setModalShow(true)} title="Add Method" />
</div>
<div>
<AddPaymentPopup
show={modalShow}
onHide={() => setModalShow(false)}
/>
</div>
</div>
</>
)
}

Is there a way to populate an input field from a list of buttons in React?

I am building a React application and I need to populate an input field from a list of predefined buttons (the text of the btn should be displayed on the input if the user clicks on the btn).
For example: let's suppose the user sees 3 btns with the following text for each: 'Banana', 'Apple','Orange'.
If the user clicks on the Banana btn, the world 'Banana' must appear on the input field.
Here is the code of App.js :
function App() {
const [suggestiveListBorder, setSuggestiveListBorder] = useState("");
return (
<div className="App">
<Navbar />
<ListOfExercises
suggestiveListBorder={suggestiveListBorder}
></ListOfExercises>
<div className="pages">
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/chest"
element={
<ChestWorkouts
setSuggestiveListBorder={setSuggestiveListBorder}
/>
}
/>
<Route path="/hamstrings" element={<HamstringsWorkouts />} />
the code of ListOfExercises.js
export function ListOfExercises({ suggestiveListBorder }) {
const currentLocation = useLocation();
let currentLocat = currentLocation.pathname;
const chestExos = exercisesData.exercises.chest_Exercises;
return (
<>
{currentLocat === "/chest" ? (
<ChestExosList
suggestiveListBorder={suggestiveListBorder}
chestExos={chestExos}
/>
) : currentLocat === "/hamstrings" ? (
<span>hamstrings</span>
) : null}
</>
);
}
the code of ChestExosList.js
export function ChestExosList({ chestExos, suggestiveListBorder }) {
const chestValues = Object.values(chestExos);
return (
<div
className="chest-exos-list-container"
style={{
border: suggestiveListBorder,
}}
>
<div>
<p className="chest-exos-paragraph">
Have no idea about the exercise you want to do ? Here are some
suggestions :
</p>
</div>
<Stack direction={"row"} spacing={2} sx={{ marginBottom: "30px" }}>
{chestValues.map(
(elem, index) =>
index < 8 &&
index >= 0 && (
<Button type="secondary" key={index}>
{elem}
</Button>
)
)}
</Stack>
the code of Chest page
const Chest = ({ setSuggestiveListBorder }) => {
const { workouts, dispatch } = useWorkoutsContext();
useEffect(() => {
const fetchWorkouts = async () => {
const response = await fetch("/api/workouts");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_WORKOUTS", payload: json });
}
};
fetchWorkouts();
}, [dispatch]);
const antIcon = (
<LoadingOutlined
style={{
fontSize: 50,
}}
/>
);
return (
<div className="chest-page-container">
<div className="chest-page-workouts">
{!workouts && <Spin spinning={true} indicator={antIcon}></Spin>}
{workouts &&
workouts.map((workout) => (
<WorkoutDetails workout={workout} key={workout._id} />
))}
</div>
<WorkoutForm setSuggestiveListBorder={setSuggestiveListBorder} />
</div>
);
};
and a snippet of the code of WorkoutForm.js :
return (
<form className="chest-workouts-form" onSubmit={handleSubmit}>
<div className="chest-workouts-form-inner">
<h3>Add a New Workout</h3>
<label>Excercise Title : </label>
<input
onMouseOver={() => {
setSuggestiveListBorder("1.5px solid #1aac83");
}}
type="text"
onChange={(e) => setTitle(e.target.value)}
value={title}
// value=""
className={emptyFields.includes("title") ? "form-msg-error" : ""}
/>
...
thank you all.
Yes, you can. You can add a different onClick event on each button (see first button in example) or a more generic one if you're planning on having many buttons (second button in example)
const Component = () => {
const [inputValue, setInputValue] = useState("");
const firstButtonText = "First button";
return (
<>
<button onClick={() => setInputValue(firstButtonText)}>{firstButtonText}</button
<button onClick={(e) => setInputValue(e.target.innerHTML)}>Second button</button>
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
</>
)
}
Create state for you input value :
const [inputValue,setInputValue]=useState('');
Give this value to your input:
<input
onMouseOver={() => {
setSuggestiveListBorder("1.5px solid #1aac83");
}}
type="text"
onChange={(e) => setTitle(e.target.value)}
value={inputValue}
className={emptyFields.includes("title") ? "form-msg-error" : ""}
/>
On your button click, you set this value:
<button onClick={()=> setInputValue("button one"}>...
Do the same for your other buttons

share hooks across components

I am trying to understand how to share hooks state across components. But it doesn't seem to be sharing. Am I doing something wrong here?
Home.js
export default function Home() {
const [search, setSearch]= useState('');
return (
<div>
<Input search={search} handleChange={setSearch} />
<Products search={search} handleChange={setSearch} />
</div>
)
}
Input.js
export default function Input({search, setSearch}) {
const handleChange = (e) => {
setSearch(e.target.value)
}
return (
<div className='App'>
<input
placeholder='search...'
value={search}
onChange={handleChange}
/>
{search}
</div>
)
}
Live Example:
const { useState } = React;
/*export default*/ function Home() {
const [search, setSearch]= useState('');
return (
<div>
<Input search={search} handleChange={setSearch} />
<Products search={search} handleChange={setSearch} />
</div>
)
}
/*export default*/ function Input({search, setSearch}) {
const handleChange = (e) => {
setSearch(e.target.value)
}
return (
<div className='App'>
<input
placeholder='search...'
value={search}
onChange={handleChange}
/>
{search}
</div>
)
}
const Products = ({search}) => {
return <div>Product: {search}</div>;
};
ReactDOM.createRoot(document.getElementById("root"))
.render(<Home />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.development.js"></script>
You pass handleSearch as prop in your Home component but Input is expecting setSearch, so just change this line in your Home
return (
<div>
<Input search={search} setSearch={setSearch} /> // change here
<Products search={search} handleChange={setSearch} />
</div>
)

How to pass state properties from component to component

I am currently learning React and I am trying to create a basic todo list app. I am facing an issue in the understanding of how passing data from component to component.
I need that when I add a task in the modal of my home component it gets added in the "pub" state of my public task component in order for the task to be rendered.
I joined the code of both components,
Hope someone can help me :)
function PublicTask (){
const [pub,setPub] = useState([{id: 1, value : "test"},{id: 2, value : "test2"}]);
function ToDoPublicItem() {
const pubT = pub.map(value =>{
return(
<div className= 'pubTask-item'>
<li>{value.value}</li>
</div>
)
});
return(
<div>
<div>
{pubT}
</div>
</div>
);
}
return(
<div className= 'item-container'>
<h2 style={{color:'white'}}>Public Tasks</h2>
<ToDoPublicItem/>
</div>
);
}
export default PublicTask;
function Home() {
const [show,setShow] = useState(false);
const [pubTask,setPubTask] = useState([]);
function openModal() {
setShow(true);
}
function Modal(){
const[textTodo, setTextTodo] = useState('')
const addItem = () => {
const itemTopush = textTodo;
pubTask.push(itemTopush);
}
return(
<div className='modal'>
<div className = 'modal-title'>
<h2>ADD A TODO</h2>
<hr></hr>
</div>
<div className= 'modal-body'>
<input type='text' onChange = {(e) => setTextTodo(e.target.value)}/>
<input type="checkbox" id="pub" value ='public'/>
<label Htmlfor="pub">Public</label>
<input type="checkbox" id="priv" value= 'private '/>
<label Htmlfor="riv">Private</label>
<hr></hr>
<Button id='button-add' size='large' style={{backgroundColor : 'white'}} onClick={()=> addItem()}>ADD</Button>
<hr></hr>
<Button id='button-close' size='large' style={{backgroundColor : '#af4c4c'}} onClick= {()=> setShow(false)} >CLOSE</Button>
</div>
</div>
)
}
return(
<div>
<h1 style={{textAlign:'center'}}>You are logged in !</h1>
<div>
<button id='button-logout' onClick = {() => firebaseApp.auth().signOut()}>Logout</button>
</div>
<div>
<Fab color="primary" aria-label="add" size = 'large' onClick = {() => openModal()}>
<Add/>
</Fab>
{show ? <Modal/> : <div></div>}
</div>
<div>
<Router>
<div className='pub-container'>
<Link to='/publicTasks'>Public Tasks </Link>
</div>
<div className='ongo-container'>
<Link to='/onGoingTasks'>On Going Tasks </Link>
</div>
<div className='finish-container'>
<Link to='/finishedTasks'>Finished Tasks </Link>
</div>
<Route path='/publicTasks' component = {PublicTask}/>
<Route path='/onGoingTasks' component = {OngoingTask}/>
<Route path='/finishedTasks' component = {FinishedTask}/>
</Router>
</div>
</div>
);
}
export default Home;
You can share data between react components like this:
const [value, setValue] = useState("test"); // data that you want to share
return (
<Parent data={value}> // pass data to parent in your child component
);
<h1>{this.props.data}</h1> // do something with the data in the parent component

React change state next component

I must create upload files unique component for input type file, for it i use react-dropzone library with drag and drop and use this component 4 time in page. Initial Initially, besides the first component, other components must be disabled, but after loading the file, the next component must change state to disable = {true}.How do I properly implement the logic using useState?
sandbox
UploadFile.js
const UploadFile = ({ visible }) => {
const [active, setActive] = useState(false);
useEffect(() => {
if (!visible) {
setActive(true);
}
}, [visible]);
console.log(visible);
const { getRootProps, getInputProps, open, acceptedFiles } = useDropzone({
noClick: true,
noKeyboard: true,
disabled: active
});
const files = acceptedFiles.map((file) => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
return (
<>
<div className="file-input">
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<p>Drag drop some files here</p>
<button type="button" onClick={open} disabled={active}>
Open File Dialog
</button>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</div>
</>
);
};
App.js
export default function App() {
return (
<div className="App">
<div className="upload-file">
<h4>Please select file</h4>
<div>
<p>Images</p>
<UploadFile visible />
<UploadFile visible={false} />
</div>
<div>
<p>Documents</p>
<UploadFile visible={false} />
<UploadFile visible={false} />
</div>
<br />
<button type="submit" disabled={true}>
SEND
</button>
</div>
</div>
);
}
you can use only one state currentStep as reference at your parent.
import React, { useCallback, useState } from "react";
import UploadFile from "./UploadFile";
import "./styles.css";
export default function App() {
const [currentStep, setCurrentStep] = useState(1)
const nextStep = useCallback(() => {setCurrentStep(crr => crr + 1)}, [])
return (
<div className="App">
<div className="upload-file">
<h4>Please select file</h4>
<div>
<p>Images</p>
<UploadFile nextStep={nextStep} currentStep={currentStep} step={1} />
<UploadFile nextStep={nextStep} currentStep={currentStep} step={2} />
</div>
<div>
<p>Documents</p>
<UploadFile nextStep={nextStep} currentStep={currentStep} step={3} />
<UploadFile nextStep={nextStep} currentStep={currentStep} step={4} />
</div>
<br />
<button type="submit" disabled={true}>
SEND
</button>
</div>
</div>
);
}
at Upload File you don't need setState, only compare step with currentStep to check if is enabled. you also need to check step === currentStep once drop is accepted, otherwise if you make multilple changes to the same field, would keep opening other fields.
import React from "react";
import { useDropzone } from "react-dropzone";
const UploadFile = ({ nextStep, step, currentStep }) => {
const onDropAccepted = () => {
if(step === currentStep) nextStep()
}
const { getRootProps, getInputProps, open, acceptedFiles } = useDropzone({
noClick: true,
noKeyboard: true,
disabled: step > currentStep,
onDropAccepted
});
const files = acceptedFiles.map((file) => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
return (
<>
<div className="file-input">
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<p>Drag drop some files here</p>
<button type="button" onClick={open} disabled={step > currentStep}>
Open File Dialog
</button>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</div>
</>
);
};
export default UploadFile;
you can use react context to create the relationship between each component, and after the first file drop , you can go to the next index of upload component
UploadFile.js
import React, { useEffect, useState, useContext } from "react";
import { useDropzone } from "react-dropzone";
import { DragContext } from "./App";
const UploadFile = ({ index = 0 }) => {
const { currentIndex, setCurrentIndex } = useContext(DragContext); // use context
const [active, setActive] = useState(false);
useEffect(() => {
if (index !== currentIndex) { // compare to active
setActive(true);
} else {
setActive(false);
}
}, [currentIndex, index]);
console.log(active);
const { getRootProps, getInputProps, open, acceptedFiles } = useDropzone({
noClick: true,
noKeyboard: true,
disabled: active,
onDropAccepted: () => {
setCurrentIndex(currentIndex + 1);
}
});
const files = acceptedFiles.map((file) => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));
return (
<>
<div className="file-input">
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<p>Drag drop some files here</p>
<button type="button" onClick={open} disabled={active}>
Open File Dialog
</button>
</div>
<aside>
<h4>Files</h4>
<ul>{files}</ul>
</aside>
</div>
</>
);
};
export default UploadFile;
App.js
import React, { createContext, useState } from "react";
import UploadFile from "./UploadFile";
import "./styles.css";
export const DragContext = createContext({});
export default function App() {
const [currentIndex, setCurrentIndex] = useState(0); // compare with index
return (
<div className="App">
<DragContext.Provider value={{ currentIndex, setCurrentIndex }}>
<div className="upload-file">
<h4>Please select file</h4>
<div>
<p>Images</p>
<UploadFile index={0} />
<UploadFile index={1} />
</div>
<div>
<p>Documents</p>
<UploadFile index={2} />
<UploadFile index={3} />
</div>
<br />
<button type="submit" disabled={true}>
SEND
</button>
</div>
</DragContext.Provider>
</div>
);
}

Resources