Passing OnChange Function using React for drop down - reactjs

I have a payment component and custom dropdown component. I'm trying to pass down a function called handlePaymentImageChange from the parent (payment) to child (dropdown) so as to control the image change. However, it does not work well as I expect. What I'm trying to do is displaying the image based on the selection of the dropdown. In my case, if the value = 'Visa' -> render visa image only.
Details: https://codesandbox.io/s/serene-noether-s8pqc?file=/src/components/Payment/Payment.js
In my Payment.js
function Payment() {
const [paymentImage, setPaymentImage] = useState({
id: 0,
value: ""
});
const handlePaymentImageChange = (e) => {
const { name, value } = e.target;
setPaymentImage({
...paymentImage,
[name]: value
});
};
return (
<div className="payment-container">
<Dropdown
title="Select payment"
items={items}
multiSelect={false}
handlePaymentImageChange={handlePaymentImageChange}
/>
{/* render specifed image based on the selected choice */}
//REST RENDER CODE...
// for example, value = Visa -> render visa image only
</div>
);
In my Dropdown.js
import React, { useState } from "react";
import "./Dropdown.css";
function Dropdown({
title,
items = [],
multiSelect = false,
handlePaymentImageChange
}) {
const [open, setOpen] = useState(false);
const [selection, setSelection] = useState([]);
const [selectedValue, setSelectedValue] = useState(title);
//REST DROPDOWN TOGGLE FUNCTION
...
return (
<div className="dropdown-container">
// pass the item.value to change the Payment state, then render the correct image
{open && (
<ul className="dropdown-list">
{items.map((item) => (
<li
className="dropdown-list-item"
key={item.id}
onChange={() => handlePaymentImageChange(item.value)}
>
<button
type="button"
onClick={() => handleOnClick(item)}
value={item.value}
>
<span>{item.value}</span>
<span>{isItemInSelection(item) && "Selected"}</span>
</button>
</li>
))}
</ul>
)
}
</div>
);
}
export default Dropdown;
Any solution?

There are multiple issue,
In Dropdown component you should add eventListener for onClick not onChange.
Inside handlePaymentImageChange method you are using e.target.value for the value. But in your case e itself is the value. So you should write,
setPaymentImage({
...paymentImage,
value: e
});
When you are rendering the image there is no check. So check if value is "Visa" and render visa image and so on.
I have updated the code here please check.

Related

How to add ref to a conditional rendering component and make it work in React

I'm trying to build a table component and make one of its cells editable.
I need this cell to be clickable, and if clicked, an input component would replace the button, and it would get focused automatically so that users can decide the text of this cell.
Now in the first rendering, button would be rendered, which leads to the binding ref of Input failing.
Here is my simplified code:
import { Input, InputRef, Button } from 'antd'
import { useRef, useState, useEffect } from 'react'
export default function App() {
const [showInput, setIsShowInput] = useState(false)
const inputRef = useRef<InputRef>(null)
useEffect(() => {
console.log(inputRef.current)
}, [inputRef, showInput])
return (
<div className="App">
{showInput ? <Input ref={inputRef} onBlur={() => {
setIsShowInput(false)
}} /> :
<Button onClick={() => {
setIsShowInput(true)
if (showInput) inputRef.current?.focus()
}}>Edit me</Button>}
</div>
);
}
How can I make the binding of ref takes effect in the first rendering, so when I click the button, Input would get focused.
Or is there any other way to achieve this?
The easiest way to achieve this is to watch the showInput value. If the value is true then call the focus method, otherwise do nothing as the Input component will be unmounted from the App.
export default function App() {
const [showInput, setIsShowInput] = useState(false)
const inputRef = useRef(null)
useEffect(() => {
if (!showInput) return;
inputRef.current.focus()
}, [showInput])
return (
<div className="App">
{showInput ? <Input ref={inputRef} onBlur={() => {
setIsShowInput(false)
}} /> :
<Button onClick={() => setIsShowInput(true)}>Edit me</Button>}
</div>
);
}

Persisting state change with localStorage for each input

So I have two radio button images, one checked and one not. I am trying to persist the change of state to view the corresponding image on button click for each of the inputs.
Please help.
Here's my code:
import React, { useState, useEffect } from 'react';
const Option = (props) => {
const img1 = <img alt='' src='/radio-active.png' className='radio__img' />;
const img2 = <img alt='' src='/radio-inactive.png' className='radio__img' />;
const [state, setState] = useState(false);
const handleStateChange = () => {
state === true ? setState(false) : setState(true);
};
useEffect(() => {
setState(JSON.parse(window.localStorage.getItem('state')));
}, []);
useEffect(() => {
window.localStorage.setItem('state', state);
}, [state]);
return (
<div className='option'>
<div className='radio'>
<button className='radio__button' onClick={handleStateChange}>
{state ? img1 : img2}
</button>
<p className='option__text radio__text'>{props.optionText}</p>
</div>
<button
className='button button--link'
onClick={(e) => {
props.handleDeleteOption(props.optionText);
}}
>
remove
</button>
</div>
);
};
export default Option;
All of your Option components are saving the state using the same key ("state"). You'll want each Option to have its own saved state. For each Option, add a new "optionName" property that is the key you want to use when saving the option's value to local storage.
// Change these:
window.localStorage.setItem('state', state);
setState(JSON.parse(window.localStorage.getItem('state')));
// To these:
window.localStorage.setItem(props.optionName, state);
setState(JSON.parse(window.localStorage.getItem(props.optionName)));

How to select a single list element when using map in React to alter the state value

I'm super new to React and building my first ever app which is a url shortening app. Each shortened url has a button next to it whose text is set to 'copy' initially and once the user click on it the link is copied to the clipboard and the button text changes to 'copied'. Everything is working fine except if I have multiple shortened url's and I click on one of the buttons next to any particular url, it still only copies that url to clipboard but the button text changes to copied on all of them.
If anyone can please enlighten me how to single out those buttons individually that'll be of great help. I've tried using the id but maybe I'm not doing that correctly?
P.S - this is first time I'm posting on here so apologies upfront if I missed any crucial bits.
import {useState} from 'react'
import axios from 'axios'
import { v4 as uuidv4 } from 'uuid';
function Main() {
const [name, setName] = useState('')
const [list, setList] = useState(initialList);
const handleSubmit = (e) => {
e.preventDefault();
}
const handleAdd = async () => {
const res = await axios.get(`https://api.shrtco.de/v2/shorten?url=${name}`)
const {data: {result: {full_short_link: shortLink}}} = res
const newList = list.concat({name:shortLink, id: uuidv4()});
setList(newList);
setName('');
}
const [buttonText, setButtonText] = useState("Copy");
return (
<form onSubmit={handleSubmit}>
<input type="text"
value= {name}
onChange={(e) => setName(e.target.value)}
placeholder='Shorten a link here'
onClick = {()=> setButtonText('copy')}
/>
<button onClick = {handleAdd}>Shorten it!</button>
</form>
<ul>
{list.map((item, index) => (
<li key={item.id}>{item.name}<button
onClick = {() => { navigator.clipboard.writeText(item.name); setButtonText("Copied")}} >
{buttonText}
</button></li>))}
</ul>
export default Main``
It’s because you are using one state variable for all of your buttons, you need a variable to keep track of state for each individual button. You should refactor the code within your map function into its own component, and declare the buttonText state within that component. That way each button has its’ own state.
Eg (sorry for the capitalisations in my code):
MyButton.js
Const MyButton = ({item}) => {
const [buttonText, setButtonText] = useState(‘Copy’)
Return (
<li key={item.id}>{item.name}
<button
onClick = {() => {
navigator.clipboard.writeText(item.name);
setButtonText("Copied")}
}
>
{buttonText}
</button>
</li>
)
Export default MyButton
Form:
// ……
<ul>
{list.map((item, index) => <MyButton key={item.id} item={item} />)}
</ul>

Drop down for a single element in a loop based on an id in react

I'm new to react and have an app that displays some data. I am using a map function to build one component multiple times. When a button is clicked inside of an element more data should be displayed but only in the clicked element. Currently, when I click a button in one element can toggle the display of the additional data for all element as well as store the unique id of the clicked element in a state. I am pretty sure that I need to filter the results and I have seen similar examples but I can't say that I fully understand them. Any tips or more beginner-friendly tutorials are greatly appreciated.
import React, { useState, useEffect } from 'react';
import '../style/skeleton.css'
import '../style/style.css'
export default function Body( student ) {
const [active, setActive] = useState({
activeStudent: null,
});
const [display, setDisplay] = useState(true)
useEffect(() => {
if (display === false) {
setDisplay(true)
} else {
setDisplay(false)
}
}, [active])
const handleClick = (id) => setActive({ activeStudent: id});
return (
<div>
{student.student.map((data) => {
const id = data.id;
return (
<div key={data.id} className="row border">
<div className="two-thirds column">
<h3>{data.firstName} {data.lastName}</h3>
{ display ?
<button onClick={() => handleClick(id)}>-</button>
:
<button onClick={() => handleClick(id)}>+</button> }
{ display ? <div>
<p>{data.addional} additonal data</p>
</div> : null }
</div>
</div>
)
})}
</div>
);
}
Change your code from:
{ display ? <div><p>{data.addional} additonal data</p></div> : null }
To:
{ active.activeStudent === id ? <div><p>{data.addional} additonal data</p></div> : null }

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}
/>
)}

Resources