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

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.

Related

react.js react-select onselected value change another select value and submit multiple items to array

I have stomped on a problem that i don't know how to resolve.
I have a react-select input that passes a selected value to another select input.
When a user clicks on submit button then i have to display an array of every selector input that the user has selected as a list of items and then the form should reset all select values.
For this, i have tried submitting to an array but it only shows one item from all selectors and form doesn't reset its values.
Here is a sandbox link
https://codesandbox.io/s/awesome-carson-i99g8p?file=/src/App.js
How can I archive this I have tried everything but I could not figure out how can i archive this functionality.
Ok. First tricky thing of react-select is, the value you assign to the component must be an object with label and valueproperties, not just the value. Meaning, when handling change events, you should set the state using the full event object.
This is the code mostly fixed:
import React, { useState, useEffect } from "react";
import Select from "react-select";
const options = [
{ value: "0", label: "0" },
{ value: "1", label: "1" },
{ value: "2", label: "2" }
];
const options2 = [
{ value: "Before Due Date", label: "Before Due Date" },
{ value: "After Due Date", label: "After Due Date" }
];
const App = (props) => {
const [numOfDays, setNumOfDays] = useState('');
const [beforeDueDate, setBeforeDueDate] = useState('');
const [items, setItems] = useState([]);
const [reminders, setReminders] = useState([]);
const submit = (event) => {
event.preventDefault(); // <-- prevent form submit action
const obj = {};
obj.id = reminders.length + 1;
obj.numOfDays = numOfDays.value;
obj.beforeDueDate = beforeDueDate.value;
setReminders((items) => [...items, obj]); // <-- update arr state
setBeforeDueDate("");
setNumOfDays("");
};
function numOfDaysHandle(event) {
// const numOfDays = event.value;
setNumOfDays(event);
setItems((items) => [...items, items]);
}
function beforeDueDateHandle(event) {
// const value = event.value;
setBeforeDueDate(event);
}
const removeReminder = (id) => {
setReminders(reminders.filter((item) => item.id !== id));
};
return (
<>
<form>
<div>
{reminders.map((item, index) => (
<div key={index}>
<div>
<span>
{item.numOfDays} days {item.beforeDueDate}
</span>
<button onClick={() => removeReminder(item.id)}>
removeItem
</button>
</div>
</div>
))}
</div>
<div>
<Select
options={options}
value={numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
//onChange={numOfDaysHandle}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
/>
</div>
{items.map((item, index) => (
<div key={index}>
<Select
options={options}
value={item.numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={item.beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
// onClick={() => setItems((items) => [...items, items])}
/>
</div>
))}
<button
onClick={submit}
//disabled={!numOfDays}
>
Set Reminder
</button>
</form>
</>
);
};
export default App;
Please see if you can move forward now, I could not understand what you want exactly with the array of <Select /> elements.

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>

Show/Hide div onClick

I currently have my toggle action in place but the only help I need is that, I would like to close the div as well, like an toggle action. The one that I've currently done is that once I click on another div element the previous one that has been clicked closes, but I'd rather prefer that I have an toggle action on closing and opening on the div element being clicked, without needing to click on another just to close the previous div, I've only grabbed the parts that are needed in the code, just to prevent on copying and pasting the whole file, just to save time on reading.
Code Snippet
const [posts, setPosts] = useState([]);
const [commentState, commentChange] = useState({
activeObject: null
});
const toggleComment = (index) => {
commentChange({...commentState, activeObject: posts[index]})
}
const toggleActiveStyles = (index) => {
if(posts[index] === commentState.activeObject) {
return "dashboard__commentContent toggle";
} else {
return "dashboard__commentContent";
}
}
return error ? (
<span>{error}</span>
) : (
{posts.map((post, i) => (
<button onClick={() => toggleComment(i)} >toggle</button>
<div className={toggleActiveStyles(i)}>
<h1>{post.title}</h1>
</div>
)}
Here is a working codesandbox that you can manipulate to fit to your needs.
Explanation
You would want to keep track of toggled divs and make sure to adjust your class based on that. You can filter out or add to the toggled divs state variable, and do whatever you want while rendering.
Code
import { useState } from "react";
import "./styles.css";
const DATA = ["1", "2", "3", "4"];
export default function App() {
const [closedDivs, setClosedDivs] = useState([]);
const toggleDiv = (i) => {
setClosedDivs((divs) =>
divs.includes(i) ? divs.filter((d) => d !== i) : [...divs, i]
);
};
return (
<div className="App">
{DATA.map((d, i) => (
<div
className={`${closedDivs.includes(i) ? "close" : ""} box`}
onClick={() => toggleDiv(i)}
>
<p> {d} </p>
</div>
))}
</div>
);
}

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.

Only 1 true state intro array React js

What's up ?
I'm trying to reproduce the sliding button effect from frontity home page with ReactJS (NextJS).
Sliding buttons from Frontity
I managed to create the sliding button effect BUT I'm struggling with state management.
I have all my objects mapped with a "isActive : true/false" element and I would to create a function that put "isActive : true" on the clicked button BUT put "isActive: false" on all the other buttons.
I don't know the syntax / method for that kind of stuff.
Please, take a look at my codesandbox for more clarity (using react hooks):
https://codesandbox.io/s/busy-shirley-lgx96
Thank you very much people :)
UPDATE: As pointed out above by Drew Reese, even more cleaner/easier is to have just one activeIndex state:
const TabButtons = () => {
const [activeIndex, setActiveIndex] = useState(0);
const handleButtonClick = (index) => {
setActiveIndex(index);
};
return (
<>
<ButtonsWrapper>
{TabButtonsItems.map((item, index) => (
<div key={item.id}>
<TabButtonItem
label={item.label}
ItemOrderlist={item.id}
isActive={index === activeIndex}
onClick={() => handleButtonClick(index)}
/>
</div>
))}
<SlidingButton transformxbutton={activeIndex}></SlidingButton>
</ButtonsWrapper>
</>
);
};
I have made a slight modification of your TabButtons:
const TabButtons = () => {
const [buttonProps, setButtonProps] = useState(TabButtonsItems);
// //////////// STATE OF SLIDING BUTTON (TRANSLATE X ) ////////////
const [slidingbtn, setSlidingButton] = useState(0);
// //////////// HANDLE CLIK BUTTON ////////////
const HandleButtonState = (item, index) => {
setButtonProps((current) =>
current.map((i) => ({
...i,
isActive: item.id === i.id
}))
);
setSlidingButton(index);
};
return (
<>
<ButtonsWrapper>
{buttonProps.map((item, index) => (
<div key={item.id}>
<TabButtonItem
label={item.label}
ItemOrderlist={item.id}
isActive={item.isActive}
onClick={() => HandleButtonState(item, index)}
/>
</div>
))}
<SlidingButton transformxbutton={slidingbtn}></SlidingButton>
</ButtonsWrapper>
</>
);
};
When we click on a button, we set its isActive state to true and all the rest buttons to isActive: false. We also should use state, since we also declared it. Changing state will force component to re-render, also we are not mutating anything, but recreating state for buttons.

Resources