JSX button element form submissions - reactjs

I have two Buttons in my component. the 'submit note' button refreshes the page (submits the form?) though the function handler for it only says to console.log. The "add note" button does not refresh the page. Both buttons are a part of the form, and both have arrow functions attached to them. Why isn't the "add note" function also refreshing the page? Is it because any component that changes state cannot also submit?
const Button = ({handleClick, text}) => {
return (
<button onClick={handleClick}>{text}</button>
)
}
export default Button; //component in separate file. added here for clarity
const NoteContainer = () => {
const [click, setClick] = useState(false)
const handleClick = () => setClick(true)
const submitNote = () => console.log('note submitted')
if (click === true) {
return (
<div>
<Note />
<Button handleClick={submitNote} text="Submit Note" />
</div>
)
}
return (
<div>
<Button handleClick={handleClick} text='Add Note'/>
</div>
)
}
export default NoteContainer;

Related

React: how to use spread operator in a function that toggles states?

I made an example of my question here:
EXAMPLE
I'm mapping an array of objects that have a button that toggles on click, but when clicking on the button every object is changed.
This is the code
export default function App() {
const [toggleButton, setToggleButton] = useState(true);
// SHOW AND HIDE FUNCTION
const handleClick = () => {
setToggleButton(!toggleButton);
};
return (
<div className="App">
<h1>SONGS</h1>
<div className="container">
{/* MAPPING THE ARRAY */}
{songs.map((song) => {
return (
<div className="song-container" key={song.id}>
<h4>{song.name}</h4>
{/* ON CLICK EVENT: SHOW AND HIDE BUTTONS */}
{toggleButton ? (
<button onClick={handleClick}>PLAY</button>
) : (
<button onClick={handleClick}>STOP</button>
)}
</div>
);
})}
</div>
</div>
);
}
I know I should be using spread operator, but I couldn't get it work as I spected.
Help please!
Of course every object will change because you need to keep track of toggled state for each button. Here is one way to do it:
import { useState } from "react";
import "./styles.css";
const songs = [
{
name: "Song A",
id: "s1"
},
{
name: "Song B",
id: "s2"
},
{
name: "Song C",
id: "s3"
}
];
export default function App() {
const [toggled, setToggled] = useState([]);
const handleClick = (id) => {
setToggled(
toggled.indexOf(id) === -1
? [...toggled, id]
: toggled.filter((x) => x !== id)
);
};
return (
<div className="App">
<h1>SONGS</h1>
<div className="container">
{songs.map((song) => {
return (
<div className="song-container" key={song.id}>
<h4>{song.name}</h4>
{toggled.indexOf(song.id) === -1 ? (
<button onClick={() => handleClick(song.id)}>PLAY</button>
) : (
<button onClick={() => handleClick(song.id)}>STOP</button>
)}
</div>
);
})}
</div>
</div>
);
}
There are many ways to do it. Here, if an id is in the array it means that button was toggled.
You can also keep ids of toggled buttons in object for faster lookup.
One way of handling this requirement is to hold local data into states within the Component itself.
I have created a new Button Component and manages the toggling effect there only. I have lifted the state and handleClick method to Button component where it makes more sense.
const Button = () => {
const [toggleButton, setToggleButton] = useState(true);
const click = () => {
setToggleButton((prevValue) => !prevValue);
};
return <button onClick={click}>{toggleButton ? "Play" : "Stop"}</button>;
};
Working Example - Codesandbox Link

How to pass state from a button to another component

When I click on a button, I am adding a product to the cart but I also want to set the state of a side drawer to true so it appears. This is working but trying to pass that state to the component so that when I click on close is giving me trouble. Here are basically all the moving parts:
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
<CartDrawer toggleOpen={isOpen} />
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
This is working fine. I click on add to cart, it adds to cart and my modal shows up as expected.
The modal is basically component and I am receiving toggleOpen as props. Here is the CartDrawer component
const CartDrawer = (props) => {
const [isOpen, setIsOpen] = useState(false);
const closeNavHandler = () => {
setIsOpen(false);
};
return (
<div
id="mySidenav"
className={props.toggleOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={closeNavHandler}
>
×
</a>
</div>
);
};
export default CartDrawer;
I know this is wrong but I can't figure out how to update the state here correctly to close it.
Just control everything from the parent. The cartDrawer only needs to receive a ìsOpen prop to know its state. Don't write another state in it.
A component like this should be stupid. It receives informations, and display them. Don't spill the logic all over your components. Just have a single source of truth.
// Main
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
<CartDrawer isOpen={isOpen} onClose={()=> setIsOpen(false)}/>
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
// CartDrawer
const CartDrawer = ({isOpen, onClose}) => {
return (
<div
id="mySidenav"
className={isOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={onClose}
>
×
</a>
</div>
);
};
export default CartDrawer;
I think that's what you want?
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
const toggleOpenDrawer = (val) => {
setIsOpen(val);
}
<CartDrawer toggleOpenDrawer={toggleOpenDrawer} toggleOpen={isOpen} />
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
const CartDrawer = (props) => {
const { toggleOpenDrawer } = props
return (
<div
id="mySidenav"
className={props.toggleOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={toggleOpenDrawer(!props.toggleOpen)}
>
×
</a>
</div>
);
};
export default CartDrawer;

How to create a confirmation delete popup in React?

I am creating a Todo App, and trying to create a confirmation delete popup which is going to be visible when the user wants to delete a todo.
In my todo.js component I have created an onClick callback, handleDelete, in my delete button, that callback will set the popup to true making it visible, the problem is that in my handleDelete I pass the Id as argument, so I can track which todo has been clicked and filter it to show the new data updating the todos state, but I only want to do update the data when the user have clicked in the confirm button that is in the popup.
App Component:
function App() {
const [inputValue, setInputValue] = useState("");
const [todos, setToDos] = useState([]);
const [noToDo, setNoToDo] = useState(false);
const [popup, setPopup] = useState(false);
const handleOnSubmit = (e) => {
e.preventDefault();
setNoToDo(false);
const ide = nanoid();
const date = new Date().toISOString().slice(0, 10);
const newToDo = { task: inputValue, id: ide, date: date };
setToDos([...todos, newToDo]);
setInputValue("");
};
const handleDelete = (id) => {
setPopup(true);
let filteredData = todos.filter((todo) => todo.id !== id);
{
/*
filteredData is the new data, but I only want to update
todos with filteredData when the user has clicked on the confirm
button in the modal component, which execute(handleDeleteTrue)*/
}
};
const handleDeleteTrue = () => {
setPopup(false);
setToDos(filteredData);
};
const handleEdit = (id, task) => {
setInputValue(task);
const EditedData = todos.filter((edited) => edited.id !== id);
setToDos(EditedData);
};
return (
<div className="App">
<div className="app_one">
<h1>To do app</h1>
<form action="" className="form" onSubmit={handleOnSubmit}>
<input
type="text"
placeholder="Go to the park..."
onChange={(e) => setInputValue(e.target.value)}
value={inputValue}
/>
<button type="submit">ADD TO DO</button>
</form>
</div>
{noToDo && <FirstLoad />}
{todos.map((todo) => {
return (
<div key={todo.id} className="result">
<Todo
{...todo}
handleDelete={handleDelete}
handleEdit={handleEdit}
/>
</div>
);
})}
{popup && <Popup handleDeleteTrue={handleDeleteTrue} />}
</div>
);
}
export default App;
Todo Component:
const Todo = ({ handleDelete, handleEdit, task, id, date }) => {
return (
<>
<div className="result_text">
<h3>{task}</h3>
<p className="result_textP">{date}</p>
</div>
<div>
<button onClick={() => handleEdit(id, task)} className="button green">
Edit
</button>
<button onClick={() => handleDelete(id)} className="button">
delete
</button>
</div>
</>
);
};
export default Todo;
Modal Component:
function Popup({ handleDeleteTrue }) {
return (
<div className="modal">
<div className="modal_box">
<p>You sure you wanna delete?</p>
<button className="modal_buttonCancel">Cancel</button>
<button onClick={handleDeleteTrue} className="modal_buttoDelete">
Confirm
</button>
</div>
</div>
);
}
export default Popup;
I tried to declare filteredData as global variable, outside my App component, so when I execute handleDelete it initializes that variable with the filtered data, and only when the user click the confirm button on the popup it executes a new function, handleDeleteTrue, which updates the data to filteredData.
It works, but declaring variables outside my component is not a good practice, so is there a better approach?
The issue in your current code is that, you are losing the id that should be deleted, so you need to store it in a ref or state.
Here is a solution that stores the id in state along with the boolean flag that shows/hides the Confirmation Box:
const [popup, setPopup] = useState({
show: false, // initial values set to false and null
id: null,
});
Modify the delete-handlers as:
// This will show the Cofirmation Box
const handleDelete = (id) => {
setPopup({
show: true,
id,
});
};
// This will perform the deletion and hide the Confirmation Box
const handleDeleteTrue = () => {
if (popup.show && popup.id) {
let filteredData = todos.filter((todo) => todo.id !== popup.id);
setToDos(filteredData);
setPopup({
show: false,
id: null,
});
}
};
// This will just hide the Confirmation Box when user clicks "No"/"Cancel"
const handleDeleteFalse = () => {
setPopup({
show: false,
id: null,
});
};
And, in the JSX, pass the handlers to Popup:
{popup.show && (
<Popup
handleDeleteTrue={handleDeleteTrue}
handleDeleteFalse={handleDeleteFalse}
/>
)}

how do control the state for multiple component with one function

I have one simple app that include 3 identical button and when I click the button, onClick event should trigger to display one span. for now, I have use one one state to control span show or not and once I click any one of button all span show. How can I implement the code, so when I click the button, only the correspond span display
import "./styles.css";
import React, { useState } from "react";
const Popup = (props) => {
return <span {...props}>xxx</span>;
};
export default function App() {
const [isOpen, setIsOpen] = useState(true);
const handleOnClick = () => {
setIsOpen(!isOpen);
};
return (
<div className="App">
<button onClick={handleOnClick}> Show popup1</button>
<Popup hidden={isOpen} />
<button onClick={handleOnClick}> Show popup2</button>
<Popup hidden={isOpen} />
<button onClick={handleOnClick}> Show popup3</button>
<Popup hidden={isOpen} />
</div>
);
}
codesandbox:
https://codesandbox.io/s/cocky-fermi-je8lr?file=/src/App.tsx
You should rethink how the components are used.
Since there is a repeating logic and interface, it should be separated to a different component.
const Popup = (props) => {
return <span {...props}>xxx</span>;
};
interface Props {
buttonText: string
popupProps?: any
}
const PopupFC: React.FC<Props> = (props) => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(!isOpen)}>{props.buttonText}</button>
<Popup hidden={isOpen} {...props.popupProps} />
</>
)
}
export default function App() {
const [isOpen, setIsOpen] = useState(true);
const handleOnClick = () => {
setIsOpen(!isOpen);
};
return (
<div className="App">
<PopupFC buttonText="Show popup1" />
<PopupFC buttonText="Show popup2" />
<PopupFC buttonText="Show popup3" />
</div>
);
}
If each Popup needs its own isOpen state, it would not be possible to achieve with a single boolean state.
Perhaps converting both the button and the span to a single component and letting each Popup component handle its own isOpen:
import "./styles.css";
import React, { useState } from "react";
const Popup = (props) => {
const [isOpen, setIsOpen] = useState(true);
const handleOnClick = () => {
setIsOpen(!isOpen);
};
return (
<>
<button onClick={handleOnClick}>{props.children}</button>
{isOpen && <span {...props}>xxx</span>}
</>
);
};
export default function App() {
return (
<div className="App">
<Popup>Show popup 1</Popup>
<Popup>Show popup 2</Popup>
<Popup>Show popup 3</Popup>
</div>
);
}
That happens simply because you are using the same state "isOpen" for all buttons,
once you click any one of them it reflects all buttons because it's the same value.
you could solve this using Custom Hook since you repeat the logic or you could separate them into small components
Based on your comment, you only want one popup to be open at a time. That was not clear in your original question so the other answers don't address this.
Right now you are just storing a value of isOpen that is true or false. That is not enough information. How do you know which popup is open?
If you want to show just one at a time, you can instead store the number or name (any sort of unique id) for the popup which is currently open.
We make the Popup a "controlled component" where instead of managing its own internal isOpen state, it receives and updates that information via props.
The App component is responsible for managing which popup is open and passing the right props to each Popup component. Since we are doing the same thing for multiple popups, I moved that logic into a renderPopup helper function.
Popup
interface PopupProps {
isOpen: boolean;
open: () => void;
close: () => void;
label: string;
}
const Popup = ({ isOpen, open, close, label }: PopupProps) => {
return (
<>
<button onClick={open}> Show {label}</button>
{isOpen && (
<div>
<h1>{label}</h1>
<span>xxx</span>
<button onClick={close}>Close</button>
</div>
)}
</>
);
};
App
export default function App() {
// store the label of the popup which is open,
// or `null` if all are closed
const [openId, setOpenId] = useState<string | null>(null);
const renderPopup = (label: string) => {
return (
<Popup
label={label}
isOpen={openId === label} // check if this popup is the one that's open
open={() => setOpenId(label)} // open by setting the `openId` to this label
close={() => setOpenId(null)} // calling `close` closes all
/>
);
};
return (
<div className="App">
{renderPopup("Popup 1")}
{renderPopup("Popup 2")}
{renderPopup("Popup 3")}
</div>
);
}
Code Sandbox

Confusion with shared variables in React

The "Bad button" only works once, since it's not controlling the same "value" as the "Good button". What am I missing here?
const Test = () => {
const [value, setValue] = useState(0)
const [view, setView] = useState()
const add = () => setValue(value + 1)
const badButton = () => {
return (
<div>
<button onClick={add}>Bad button</button>
</div>
)
}
return (
<div>
{value}
<button onClick={add}>Good button</button>
{view}
<button onClick={() => setView(badButton)}>show bad button</button>
</div>
)
}
Thanks for replying, I'm going to use the flag method as suggested. But I would still like to know why the two buttons don't work the same way in this original case.
Check this sandbox
I feel it's the right way to handle such a usecase. Instead of storing component itself in state. I replaced it with a boolean. By default it will be false and badButton will be hidden, on click of showBadButton, i'm setting the view state true and bad button will come into picture. Actually Its a good buton now. Check it out.
I would use view as a flag to show/hide BadButton component, I have created a demo that showcase the following code snippet:
import React, { useState } from 'react';
import { render } from 'react-dom';
import './style.css';
const Test = () => {
const [value, setValue] = useState(0)
const [view, setView] = useState(false)
const add = () => setValue(value + 1)
const BadButton = () => {
return (
<button onClick={add}>Bad button</button>
)
}
return (
<>
{value}
<button onClick={add}>Good button</button>
{view ? BadButton() : null}
<button onClick={() => setView(!view)}>
{view ? 'hide' : 'show'} bad button
</button>
</>
)
}
render(<Test />, document.getElementById('root'));
Welcome to StackOverflow

Resources