React: Change field's value from button - reactjs

I have this fairly simple application that has multiple inputs, and only has state for the current field, and the current field's input. When you click on an input, it sets the "current field" and "current input" accordingly, not resetting on blur.
Is there any way to implement having a button that resets the current field's value, as shown below?
import React, { useState } from 'react'
const FieldTest = () => {
const [currentField, setCurrentField] = useState(1)
const [currentInput, setCurrentInput] = useState('A')
const fields = [...Array(4)].map((current, i) => (
<input
key={i}
type='text'
onFocus={e => {
setCurrentField(i + 1)
setCurrentInput(e.target.value)
}}
onChange={e => setCurrentInput(e.target.value)}
defaultValue={String.fromCharCode(64 + i + 1)}
/>
))
return (
<>
<h1>Field Test</h1>
<p>Current Field: {currentField}</p>
<p>Current Input: {currentInput}</p>
{fields}
<button
onClick={e => {
/*Is there any way to implement this by only changing this onClick?*/
}}
>
Reset Current Field's Input
</button>
</>
)
}
export default FieldTest
This could easily be accomplished by refactoring this into a state for each input, or a ref for each input, but if there's any way I could not implement that (as it makes adding variable amounts of inputs a lot easier) then let me know.
Or if there were a completely different way to implement this that also allowed for variable amounts of inputs, let me know!
Thanks!

You can use keep one ref and change its .current value based on the element that is focused.
import React, { useState, useRef } from "react";
export default function App() {
const [currentField, setCurrentField] = useState(1);
const [currentInput, setCurrentInput] = useState('A');
const curInputRef = useRef(null);
const fields = [...Array(4)].map((current, i) => {
return (
<input
key={i}
type="text"
onFocus={(e) => {
setCurrentField(i + 1);
setCurrentInput(e.target.value);
curInputRef.current = e.target; //make the ref point to the focused input
}}
onChange={(e) => setCurrentInput(e.target.value)}
defaultValue={String.fromCharCode(64 + i + 1)}
/>
);
});
return (
<>
<h1>Field Test</h1>
<p>Current Field: {currentField}</p>
<p>Current Input: {currentInput}</p>
{fields}
<button
onClick={(e) => {
if (!curInputRef.current) return;
curInputRef.current.value = ''; //change the value of the focused input to be ''
/*Is there any way to implement this by only changing this onClick?*/
}}
>
Reset Current Field's Input
</button>
</>
);
}
The script in action:
https://codesandbox.io/s/admiring-saha-2uo2f?file=/src/App.js

Related

Use array of strings in React Hook Form

In a form that I am making the material that is being created in the form should have multiple width options that can be added. This means that I will have a text input where the user can add an option, and when this option is added, it should be added to the React Hook Form widthOptions array, without using the regular react state. How would one do this? How do you add an item to the total React Hook Form state, I only see options for just one input field corresponding to a property.
This is how i would do it using the regular React state
import { TrashIcon } from "#heroicons/react/24/outline";
import React, { useRef, useState } from "react";
const Test = () => {
const [widthOptions, setWidthOptions] = useState<string[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
const removeWidthOption = (widthOption: string) => {
setWidthOptions(widthOptions.filter((option) => option !== widthOption));
};
const addWidthOption = (widthOption: string) => {
setWidthOptions([...widthOptions, widthOption]);
};
const editWidthOptions = (widthOption: string, index: number) => {
const newWidthOptions = [...widthOptions];
newWidthOptions[index] = widthOption;
setWidthOptions(newWidthOptions);
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={() => addWidthOption(inputRef?.current?.value)}>
Add Width Option
</button>
{widthOptions.map((option, index) => (
<div className="flex">
<input
type="text"
value={option}
onChange={() => editWidthOptions(option, index)}
/>
<button type="button" onClick={() => removeWidthOption(option)}>
<TrashIcon className="w-5 h-5 mb-3 text-gray-500" />
</button>
</div>
))}
</div>
);
};
export default Test;
You can just the controller component for this as for all other fields.
Since you have not shared any of you code here is a generic multi-select
<Controller
name={name}
render={({ field: { value, onChange, ref } }) => {
return (
// You can use whatever component you want here, the you get the value from the form and use onChange to update the value as you would with a regular state
<Test
widthOptions={value}
setWidthOptions={onChange}
/>
);
}}
/>;
https://react-hook-form.com/api/usecontroller/controller/
And in you Test component remove the state and get the props instead
const Test = ({widthOptions, setWidthOptions}) => {
const inputRef = useRef<HTMLInputElement>(null);
.
.
.

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.

Lifting state up and accessing values of a dynamically created input text field

In my Component, I am opening up a Modal and passing a form like this:-
<Modal
show={showGroupModal}
onCancel={closeGroupModalHandler}
header="Please add Group Name"
contentClass="player-admin__modal-content"
>
<div>
<form onSubmit={addGroupSubmitHandler}>
<NonFormikInput
type="text"
placeholder={formatValue ? "Enter Group Name" : "Enter Team Name"}
nogat={nogat}
getTextValue={getTextValueHandler}
/>
<Button type="submit" text="Submit"></Button>
<Button type="button" text="Cancel" onClick={closeGroupModalHandler}>
{" "}
</Button>
</form>
</div>
</Modal>
The value {nogat} is a variable value being fetched from a MONGODB collection. This value is passed to a child component, where I am using .map() to create number of input boxes based on number passed via nogat, like this:-
const NonFormikInput = (props) => {
const [title, setTitle] = useState('')
const changeHandler = (event) => {
setTitle(event.target.value)
props.getTextValue(title)
}
const newValues = [...Array(props.nogat)].map((_,i) => i + 1)
const newValues1 = newValues.map(number => {
return (
<input
key={number}
id={number}
className="non-formikinput__text"
type={props.type}
placeholder={props.placeholder}
onChange={changeHandler}
value={title}
/>
)
})
return (
newValues1
)
}
The onChange function in this component should ideally lift the state up via this statement
props.getTextValue(title)
It is doing this correctly and I can see the value in my parent component also correctly.
My Problem:- In the child component the input text field is wired in such a way that whatever I type on any one input field is getting reflected in all the subsequent text boxes.
I figured that this is probably because I wired the value prop as a state field title and this is now getting spread across all the input fields coming from the .map function.
Is there a way to segregate the input values so that I am uniquely able to capture values from every field
instead of storing a string in the state variable store an object that can have the title of each input with key as its number. Pass the number to the changeHandler along with the event to set the titles. Now in the parent element, you can access the text of any input by its number.
const NonFormikInput = (props) => {
const [titles, setTitles] = useState({})
const changeHandler = (event, number) => {
setTitles({...titles, [number]: event.target.value});
props.getTextValue(titles)
}
const newValues = [...Array(props.nogat)].map((_,i) => i + 1)
const newValues1 = newValues.map(number => {
return (
<input
key={number}
id={number}
className="non-formikinput__text"
type={props.type}
placeholder={props.placeholder}
onChange={(event) => changeHandler(event, number)}
value={number in titles ? titles[number] : ""}
/>
)
})
return (
newValues1
)
}

Is this the correct way to set state using react's useState hook

I was looking into React hooks and wasn't sure how useState works with arrays. So I made a working example with 3 controlled inputs, fed by an array. It works but I was wondering if this is the correct/optimal way of doing this.
Working example on codepen
const { useState } = React;
const InputAndButton = ({ tag, index, handleTagChange }) => (
<div>
<input value={tag} onChange={(e) => handleTagChange(e.target.value, index)} />
<button type="button" onClick={() => handleTagChange('', index)}>×</button>
</div>
)
function Tags(){
const [ tags, setTags ] = useState(["", "", ""]);
const handleTagChange = (value, index) => {
// make copy of state
const tagsCopy = [...tags];
// change the [i] value to the new one given
tagsCopy[index] = value;
// set copy back as state
setTags(tagsCopy);
}
return(
<form>
tags
{tags.map((tag, i) => <InputAndButton tag={tag} index={i} handleTagChange={handleTagChange} key={i} />)}
</form>
)
}
ReactDOM.render(<Tags />, document.getElementById("app"));
Can anybody give me some feedback here?

How can I re write this code in the correct "react" way

I am trying to change the innerhtml of an element in react, my solution is obviously not ideal since i'm manipulating the DOM directly. How can I write this in the correct way? Would it be better to useState or useRef? Basically, if plus is selected then turn into minus and if minus is selected then turn into plus. Each button is unique and there are multiple buttons
function App() {
const [tags, setTags] = useState([]);
const displayScores = (e) => {
if (e.target.innerHTML == "+") {
e.target.innerHTML = "-";
} else {
e.target.innerHTML = "+";
}
};
return (
<>
{tags.filter((t) => t.studentEmail == students.email).map((tag, index) => {
return (
<p key={index} className="student-tags">
{tag.value}
</p>
);
})}
<input
onKeyDown={addTag.bind(this, students.email)}
className="student-tag"
type="text"
placeholder="Add a tag"
/>
</div>
</div>
<button onClick={displayScores} className="expand-btn">+</button>
</div>
);
})}
</>
)
}
export default App;
I don't think you need to access the DOM for this at all. You could use some React state (boolean) that determines if scores are displayed, and then render a "+" or "-" in the button based off of that boolean.
// Your state
const [scoresDisplayed, setScoresDisplayed] = useState(false);
// Function to update state
const displayScores = () => {
// Toggle the boolean state
setScoresDisplayed(state => !state);
};
// Conditionally render button text
<button onClick={displayScores} className="expand-btn">
{scoresDisplayed ? "-" : "+"}
</button>
in React you should not work with something like inner html,there much better ways like below :
function App() {
const [tags, setTags] = useState([]);
const [buttonLabel, setButtonLabel] = useState("+");
const toggleButtonLabel = () => {
setButtonLabel(buttonLabel === "+" ? "-" : "+");
};
return (
<>
{tags
.filter((t) => t.studentEmail == students.email)
.map((tag, index) => {
return (
<p key={index} className="student-tags">
{tag.value}
</p>
);
})}
<input
onKeyDown={addTag.bind(this, students.email)}
className="student-tag"
type="text"
placeholder="Add a tag"
/>
<button onClick={toggleButtonLabel} className="expand-btn">
{buttonLabel}
</button>
</>
);
}
export default App;

Resources