React toggle visibility of clicked element - reactjs

I have a simple list of items. When I click on one item I want the text inside span dissapear, but in the rest I want to make them visible. Now when I click on any all spans dissapear. Here is a simple demo:
link: https://codepen.io/rosy654/pen/VwaZJNb

You only have a single value called visible in the state of your Example component and your handleClick funciton updates that value independently of the element clicked. (You're using the same function and same state value for both spans.)
This can easily be solved in two ways:
Creating a new component (example: SpanItem) which only contains the span and the state of that span. You're Example component doesn't need any state in that case and can just render the SpanItem component multiple times.
const SpanItem = ({ label }) => {
const [visible, setVisible] = useState(true);
const handleClick = () => {
setVisible(!visible);
}
return <li onClick={handleClick}>{label} <span className={!visible && 'hide'}>Visible</span></li>
}
const Example = () => (
<div>
<ul>
<SpanItem label="First Item">
<SpanItem label="Second Item">
</ul>
</div>
);
You could refactor your usage of the state in the Example component and save one visible value per item:
const Example = () => {
const defaultVisibillity = true;
const [visible, setVisible] = useState({});
const isVisible = (id) => visible[id] || defaultVisibillity;
const handleClick = (id) => () => {
setVisible({
...visible
[id]: !isVisible(id)
})
}
return (
<div>
<ul>
<li id={1} onClick={handleClick(1)}>First Item <span className={!isVisible(1) && 'hide'}>Visible</span></li> <li id={2} onClick={handleClick(2)}>Second Item <span className={!isVisible(2) && 'hide'}>Visible</span></li>
</ul>
</div>
);
}

You are using same visible flag/state for both the span items. Hence either both will be hidden or shown at the same time when user clicks any one item,
Either use different states like visible1/visible2 and use them respectively
Or keep the ids to span element and change code as below inside handleClick
const el = document.getElementById(e.currentTarget.id);
el.style.visibility = "hidden"; // or "visible" accordingly

Related

onClick handler is applying on all elements after map

I have the following data:
export const Bookings = ({ booking, selectedBookings }) => {
const [selected, setSelected] = React.useState(false)
const handleSelect = (data) => {
selectedBookings(data)
}
{booking.map(data => (
<div
key={nanoid()}
className={selected ? styles.selectedDescription : styles.NonSelectedDescription}
onClick={() => {
handleSelect(data.name); // when I add this it does not trigger the style toggling
setSelected(!selected) // if I keep only this handler it works but for all childrens
}}
>
{description}
</div>
In this code I want to send data to parent component and apply a specific styles when the booking is clicked, BUT I have 2 issues:
1- when I remove the handleSelect(data.name) the styles apply on whole childrens and not one single chidren.
2- when I add the handleSelect(data.name) handler the styles does not work at all, but the handler send the data to the parent
So is there any problem here?
You need to store different selected boolean for all bookings;
Try something like below:-
Change selected state to object:-
const [selected, setSelected] = React.useState({})
Change handleSelect like below:-
const handleSelect = (dataName) => {
setSelected({...selected, selected[dataName]: !selected[dataName]});
}
Change className in div like below:-
{booking.map(data => (
<div
key={nanoid()}
className={selected[data.name] ? styles.selectedDescription : styles.NonSelectedDescription}
onClick={() => { handleSelect(data.name); }}
>
{description}
</div>
))}
Note:- All booking name should be unique, If they are not unique you need to pass unique value in handleSelect function.

Passing OnChange Function using React for drop down

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.

How can I set focus on a dynamically added field with React

I have a basic task list app that gives users the ability to add items to the task list. When the "Add Item" button is clicked I will insert a new row to the bottom of the list. The row contains an empty text field, where the user can enter their task name. I want to set the focus on this field as soon as it's push()ed into the array. I know how to set the focus using a ref if the field already exists, but I can't seem to figure it out for a dynamically added field. How can I do this?
Here is my code:
const tasks = [array_of_task_objects];
const [state, setState] = React.useState({tasks: tasks});
const newTask = {title: ''};
const addTask = () => {
let newTasks = [...state.tasks];
newTasks.push(newTask);
setState({...state, tasks: newTasks});
// Now, set focus in the input field...(how?)
};
Elsewhere:
<button onClick={addTask}>Add Task</button>
<ul>
{
state.tasks.map(task => {
return(
<li><input value={task.title}></li>
);
})
}
</ul>
One way to do this is to have a ref that's always referring to the last textbox and then running an effect that sets focus on that last element when tasks are updated. This is a shell of an example that should basically get you there:
export default function App() {
const [tasks, setTasks] = useState([newTask]);
const lastRef = useRef(null);
useEffect(() => {
if (lastRef.current)
lastRef.current.focus();
}, [tasks]);
return (
<div className="App">
{tasks.map((task, i) => (
<>
<input key={i} ref={i === tasks.length - 1 ? lastRef : undefined} />
<br />
</>
))}
<button
onClick={() => {
setTasks(tasks => [...tasks, newTask]);
}}
>
Add
</button>
</div>
);
}
You can make the task input focus itself when it is rendered the first time.
const Task = ({value}) => {
const ref = useRef(null);
useEffect(() => if (ref.current) {ref.current.focus()}, [ref.current])
return <li><input ref={ref} value={value} /></li>
}
This will work if you are only mounting one at a time. If you have multiple inputs rendered in an initial state for example you could introduce a shouldTakeFocus prop. Then you limit the effect to only run when shouldTakeFocus is true.

Change css class of a component by onClick on Icon in React using useState hook

I am new to React and I was hoping someone could help me with this issue. I am trying to render some images called 'cards' from an array based on the same data I've received from Axios. I basically need to render an array of card props which have an <i> tag with some font-awesome class attached to them. When I click on the "fa-search-plus" font-awesome icon, I want the parent of this icon <div> to trigger the onClick such that the css property of the sibling <img> of this <div> can be changed. For some reason with the following code, this does not seem to happen. Any fix is appreciated. Thanks!
const GameCards = (cards) => {
const [cardimgclass, setCardimgclass] = useState(true);
const onClick = (e) => {
e.preventDefault();
setCardimgclass(!cardimgclass);
};
const loadCardsByCategory = (cards) => {
var allCards = [];
if (cards)
cards.forEach((item, i) => {
allCards.push(
<div key={item._id} className="card-container">
<img
className={cardimgclass ? "card-reg" : "card-big"}
src={item.src}
alt="No file"
/>
<div onClick={(e) => onClick(e)}>
{" "}
<i className="fas fa-search-plus"></i>
</div>
</div>
);
});
return allCards;
};
const loadCards = (cards) => {
return (
<Fragment>
<div className="cardgallery">{loadCardsByCategory(cards)}</div>
</Fragment>
)
};
const loadCardsUsingMemo = useMemo(() => loadCards(cards), [cards]);
return <Fragment>{loadCardsUsingMemo}</Fragment>;
};
It looks like your primary problem is that you are not destructuring your props object:
const GameCards = cards => {
You need to change that to:
const GameCards = ({ cards }) => {
Also, remove the useMemo stuff. It is not helping you here. Here is a slimmed down version of your code. I'm changing the background-color property with the class, but the concept is the same. Hope that helps!
EDIT: Also note, as in the example your logic is currently changing all of the elements. If you want to only modify the class for one element you could use the method of passing the index (or better yet, the ID!). Here is an example:
const [cardimgclass, setCardimgclass] = useState();
...
const onClick = (e, item) => setCardimgclass(item._id)
...
<div onClick={e => onClick(e, item)} />

Can't control Downshift selectedItem prop from parent component using controlled props

I wrote a reusable autocomplete component using Downshift. The component encapsulates a lot of Relay logic for fetching data from my GraphQL endpoint. I have a use case where the parent component needs to receive the selectedItem, display the name property of the selectedItem, then clear out the selectedItem of the autocomplete (think selecting and displaying multiple tags).
Problem is I can't seem to control the selectedItem of the autocomplete from the parent component. I'm sending down the selectedItem as null to the autocomplete yet the inputValue/selectedItem remains the chosen item.
Sample code with Relay stripped out for simplicity:
https://codesandbox.io/embed/spring-fire-um1xh
Steps to reproduce
Type the word "Item" into the textbox
Click on one of the three results that are displayed
Actual outcome
Title of parent component and inputValue/selectedItem of autocomplete are both set to value of item chosen
Desired Outcome
Title of parent component set to value of item chosen
inputValue/selectedItem of autocomplete cleared out
Try below code. This code needs alot of refactor but it works anyway as desired. So, i just added inputValue props on Downshift as per doc.
----- index.js -------
function App() {
const [title, setTitle] = useState("Select an Item");
const [selectedItem, setSelectedItem] = useState(null);
const [inputValue, setInputValue] = useState(""); //added these two to control input of inputbox
const onItemSelected = selectedItem => {
setTitle(selectedItem.name);
setSelectedItem(null);
setInputValue("") // after selection clear input
};
return (
<div className="App">
<h1>{title}</h1>
<Autocomplete
selectedItem={selectedItem}
inputValue={inputValue} // passed down inputValue and setInputValue
setInputValue={setInputValue}
onItemSelected={onItemSelected}
/>
</div>
);
}
----Autocomplete.js--------
function Autocomplete({ selectedItem, onItemSelected, inputValue, setInputValue }) { // get those props
const [items, setItems] = useState([]);
const onInputValueChanged = e => {
setInputValue(e.currentTarget.value) //change a input on inputchange
const items = data.filter(f => f.name.includes(e.currentTarget.value));
setItems(items);
};
return (
<Downshift
inputValue={inputValue} //added
selectedItem={selectedItem}
onChange={onItemSelected}
itemToString={i => (i ? i.name : "")}
>
{({ getInputProps, getItemProps, getMenuProps, isOpen }) => (
<div>
<input {...getInputProps({ onChange: onInputValueChanged })} />
<ul {...getMenuProps()}>
{isOpen &&
items.map(item => (
<li {...getItemProps({ item, key: item.value })}>
{item.name}
</li>
))}
</ul>
</div>
)}
</Downshift>
);
}

Resources