Application:
Search bar with two text input fields (input1, input2)
Three buttons: SearchX, SearchY, Clear results
Both the searches can take input1 and input2 as parameters to give two different results.
There's a result component which takes both the inputs, action and renders the search component depending on the action.
function TestComponent() {
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
const [action, setAction] = useState(null);
const onInput1Change = evt => setInput1(evt.target.value);
const onInput2Change = evt => setInput2(evt.target.value);
return (
<div>
<input type="text" value={input1} onChange={onInput1Change} />
<input type="text" value={input2} onChange={onInput2Change} />
<button type="button" onClick={() => setAction('SearchX')}>
SearchX
</button>
<button type="button" onClick={() => setAction('SearchY')}>
SearchY
</button>
<button type="button" onClick={() => setAction('Clear results')}>
Clear results
</button>
<ResultComponent input1={input1} input2={input2} action={action} />
</div>
);
}
function ResultComponent({ input1, input2, action }) {
if (action === 'SearchX') {
return <SearchX input1={input1} input2={input2} />;
}
if (action === 'SearchY') {
return <SearchY input1={input1} input2={input2} />;
}
if (action === 'Clear results') {
return null;
}
return null;
}
function SearchX({ input1, input2 }) {
const [result, setResult] = useState(null);
useEffect(() => {
// Fetch and process X-way to get the result. Using timeout to simulate that
const id = window.setTimeout(() => setResult(`Search X result with inputs: ${input1}, ${input2}`), 3000);
return () => window.clearInterval(id);
}, [input1, input2]);
return <div>{result}</div>;
}
function SearchY({ input1, input2 }) {
const [result, setResult] = useState(null);
useEffect(() => {
// Fetch and process Y-way to get the result. Using timeout to simulate that
const id = window.setTimeout(() => setResult(`Search Y result with inputs: ${input1}, ${input2}`), 3000);
return () => window.clearInterval(id);
}, [input1, input2]);
return <div>{result}</div>;
}
ReactDOM.render(<TestComponent />, document.getElementById('root'));
Problem:
We want the search to initiate only when a button is clicked. With below code, after the first search result, as soon as you change your input, the result component expectedly re-renders thereby initiating search again without button click
Steps to reproduce the problem:
Enter "input1" in first text box
Enter "input2" in second text box
Hit on "SearchX"
After 3 seconds you should see something like "Search X result with inputs: input1, input2"
Change any of the input boxes. Need not press enter.
After 3 seconds, the result would change without button click
Possible option:
Planning to use React.memo hook to compare action prop before updating the result component. Action prop can only change on button clicks and hence can solve the problem.
Question:
Is there any other way (any other hooks etc.) to solve the problem?
Or is there any other process/design that I can follow to avoid memo ?
You could, upon input interaction, reset the action back to null. This will clear out the current result and not trigger a "search".
function TestComponent() {
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
const [action, setAction] = useState(null);
const onInput1Change = evt => {
setInput1(evt.target.value);
setAction(null);
};
const onInput2Change = evt => {
setInput2(evt.target.value)
setAction(null);
};
return (
<div>
<input type="text" value={input1} onChange={onInput1Change} />
<input type="text" value={input2} onChange={onInput2Change} />
<button type="button" onClick={() => setAction('SearchX')}>
SearchX
</button>
<button type="button" onClick={() => setAction('SearchY')}>
SearchY
</button>
<button type="button" onClick={() => setAction(null)}>
Clear results
</button>
<ResultComponent input1={input1} input2={input2} action={action} />
</div>
);
}
EDIT Use html5 forms to save input and set action upon submit. When inputs are interacted with the inputs in state aren't updated until form is submitted.
function TestComponent() {
const [input1, setInput1] = useState("");
const [input2, setInput2] = useState("");
const [action, setAction] = useState(null);
return (
<div>
<form
id="searchX"
onSubmit={e => {
e.preventDefault();
setInput1(e.target.inputX.value);
setAction("SearchX");
}}
/>
<form
id="searchY"
onSubmit={e => {
e.preventDefault();
setInput2(e.target.inputY.value);
setAction("SearchY");
}}
/>
<input id="inputX" form="searchX" type="text" />
<input id="inputY" form="searchY" type="text" />
<input form="searchX" type="submit" value="SearchX" />
<input form="searchY" type="submit" value="SearchY" />
<button type="button" onClick={() => setAction(null)}>
Clear results
</button>
<ResultComponent input1={input1} input2={input2} action={action} />
</div>
);
}
Also, setting the "clear results" button action back to null saves a conditional check in ResultComponent, which simplifies to:
function ResultComponent({ input1, input2, action }) {
if (action === 'SearchX') {
return <SearchX input1={input1} input2={input2} />;
}
if (action === 'SearchY') {
return <SearchY input1={input1} input2={input2} />;
}
return null;
}
You can use refs to inputs and only update state on button click.
export default function TestComponent() {
const [input1, setInput1] = useState("");
const [input2, setInput2] = useState("");
const [action, setAction] = useState(null);
const input1Ref = useRef(null);
const input2Ref = useRef(null);
const onButtonClick = () => {
if (input1Ref.current) {
setInput1(input1Ref.current.value);
}
if (input2Ref.current) {
setInput2(input2Ref.current.value);
}
};
const onSearchXClick = () => {
onButtonClick();
setAction("SearchX");
};
const onSearchYClick = () => {
onButtonClick();
setAction("SearchX");
};
return (
<div>
<input ref={input1Ref} type="text" />
<input ref={input2Ref} type="text" />
<button type="button" onClick={onSearchXClick}>
SearchX
</button>
<button type="button" onClick={onSearchYClick}>
SearchY
</button>
<button type="button" onClick={() => setAction("Clear results")}>
Clear results
</button>
<ResultComponent input1={input1} input2={input2} action={action} />
</div>
);
}
Related
Code:
export default function App() {
const [name,setName] = useState("");
var myArray = [];
const handleAdd = () => {
myArray = [...myArray,name]
setName("")
}
return (
<div className="App">
<input placeholder="type a name" onChange={(e) => setName(e.target.value)}/>
<button onClick={handleAdd}>add</button>
<button onClick={() => console.log(myArray)}>test</button>
{myArray.map((n) => {
return <h2>{n}</h2>
})}
</div>
);
}
OnClick it isn't adding the name to the array.
this is how you "push" to an array with useState
const [array, setArray] = useState([])
setArray(previous => [...previuous, newItem])
You should use a state for your array and set that state to see the changes reflected:
export default function App() {
const [name, setName] = useState('');
const [myArray, setMyArray] = useState([]);
const handleAdd = () => {
setMyArray([...myArray, name]);
setName('');
};
return (
<div className="App">
<input
placeholder="type a name"
onChange={(e) => setName(e.target.value)}
/>
<button onClick={handleAdd}>add</button>
<button onClick={() => console.log(myArray)}>test</button>
{myArray.map((n) => {
return <h2>{n}</h2>;
})}
</div>
);
}
We can also set the state of myArr to be an empty array initially, making it easier to manipulate the subsequent state of that array. The onClick event handler does not fire the handleAdd function, for some reason, it only resets the form and does not provide any state. To submit the form and materialize the state, we can also use the onSubmit event handler instead of onClick. In the same way, we can use the name state as a value/prop for the name input, which will be used by the onChange handler.
import React, { useState } from 'react'
const App = () => {
const [name, setName] = useState('')
const [myArr, setMyArr] = useState([])
const submit = (event) => {
event.preventDefault()
setMyArr(myArr.concat(name))
setName('')
}
//console.log(myArr)
return (
<div className="App">
<form onSubmit={submit}>
<div>
<label htmlFor="name">Name</label>
<input
placeholder="type a name"
type="text"
value={name}
onChange={({ target }) => setName(target.value)}
/>
</div>
<div>
<button type="submit">Add</button>
</div>
</form>
<div>
{myArr.map((arr, index) => (
<div key={index}>
<p>{arr}</p>
</div>
))}
</div>
</div>
)
}
export default App
I have a proclivity of inserting items on an array using concat.
import React, { useState } from 'react'
// ...
const App = () => {
// ...
const [myArr, setMyArr] = useState([])
// somewhere on your event handler e.g. Submit handler
setMyArr(myArr.concat(name))
// ...
}
I have a text in <input> have value "first" by using useState().
const [text, setText] = React.useState("");
const setStateOnClick = () => {
setText("second");
};
const onchangeText = (e) => {
const value = e.target.value;
setText(value);
};
return (
<div>
<input value={text} onChange={(e) => onchangeText(e)} />
<button onClick={setStateOnClick}>set new state</button>
</div>
);
Begin, in <input> is: "first"
After I setText.
I can't "ctrl Z" to take back value("first") of begin text.
I try to catch the value by "onChange()" and put it in a stack, but it save every letters so performance will be decreased.
Is there a way to save the state of "ctrl Z" ? Thank you.
SOVLED.
export default function StateTextFields() {
const [text, setText] = React.useState("");
const [textArr, setTextArr] = React.useState([]);
const typingTimeoutRef = React.useRef(null);
const setStateOnClick = () => {
setText("second state");
};
const setStateUndo = () => {
if (textArr.length > 0) {
const NewArr = [...textArr];
setText(NewArr[NewArr.length - 1]);
NewArr.pop();
setTextArr(NewArr);
}
};
const onchangeText = (e) => {
const value = e.target.value;
setText(value);
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
}
typingTimeoutRef.current = setTimeout(() => {
const NewArr = [...textArr];
NewArr.push(value);
setTextArr(NewArr);
}, 300);
};
return (
<div>
<input value={text} onChange={(e) => onchangeText(e)} />
<button onClick={setStateOnClick}>second</button>
<button onClick={setStateUndo}>Undo</button>
</div>
);
}
Provided below is a working-example which shows that Ctrl+Z may be used to undo text typed in the input (unless the button is clicked).
Code Snippet
const {useState} = React;
const Thingy = () => {
const [text, setText] = useState("");
const setStateOnClick = () => {
setText("second");
};
const onchangeText = (e) => {
const value = e.target.value;
setText(value);
};
return (
<div>
<input value={text} onChange={(e) => onchangeText(e)} />
<br/>
<br/>
value in real-time: {text} <br/>
Can use Ctrl+Z to undo entered text
<br/>
<br/>
<button onClick={setStateOnClick}>set new state</button>
<br />
Cannot Ctrl+Z after above button is clicked !
</div>
);
};
ReactDOM.render(
<div>
<Thingy />
</div>,
document.getElementById("rd")
);
<div id="rd" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
EDIT
Based on updated question, a new button "Undo" has been added.
The original text typed by the user is saved when user clicks the first button ("Confirm text").
This info is also stored on an array pastItems.
When there are entries in pastItems the "Undo" button is enabled and allows user to recall values one-by-one till no values remain.
const {useState} = React;
const Thingy = () => {
const [text, setText] = useState("");
const [pastItems, setPastItems] = useState([]);
const setStateOnClick = () => {
setPastItems(prev => ([...prev, text]));
setText('');
// setText(prev => (`${prev} - set to state`));
};
const undoOnClick = () => {
const prevText = pastItems.pop();
setPastItems([...pastItems]);
setText(prevText);
};
const onchangeText = (e) => {
const value = e.target.value;
setText(value);
};
return (
<div>
<label for="userInput">Type some text here: </label>
<input id="userInput" value={text} onChange={(e) => onchangeText(e)} />
<br/>
<br/>
value in real-time: {text} <br/>
prev values in real-time: {pastItems.join(', ')} <br/>
Can use Ctrl+Z to undo entered text
<br/>
<br/>
<button onClick={setStateOnClick}>Confirm text</button>
<button onClick={undoOnClick} disabled={pastItems.length === 0}>Undo</button>
<br /><br />
Can "Undo" after 'Confirm' button is clicked !
</div>
);
};
ReactDOM.render(
<div>
<Thingy />
</div>,
document.getElementById("rd")
);
<div id="rd" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
I am trying to create a button that add more input and remove input and when it remove the input it also clear all the data inside that input but the problem is when I remove that input but the data still stay. How can I fix that ?
Here is my code base:
const [inputs, setInputs] = useState(teamData.rules);
useEffect(() => {
setInputs(teamData.rules);
}, [teamData]);
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule-${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...inputs];
list.splice(index, 1);
setInputs(list);
};
{inputs.map((data, index) => (
<div className="agreement-form-grid" key={index}>
<button
className="agreement-remove-button"
onClick={() => handleRemoveClick(index)}
>
<Remove />
</button>
<input
type="text"
defaultValue={teamData.rules[index]}
name={`rule_${index}`}
placeholder={`Rule ${index + 1}`}
onChange={handleChange}
/>
</div>
))}
{inputs.length !== 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
Update question add handleChange function:
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
}
Define another function in your parent component to clear the data like below,
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState
}
});
}
In the child component, in the onClick of the remove button call this function as well
<button
className="agreement-remove-button"
onClick={() => { handleRemoveClick(index); clearInput(`rule_${index}`)}}
>
<Remove />
</button>
Before saving you can ignore empty inputs
Hi i am working on a React application where there are four inputs.when a user add an input the element will be added to the wrapper.In the following code add operation works fine but remove operation is not working properly ,it is not removing the corresponding element.Another problem the values on the inputs fields not present when the component re-renders.so experts guide me how i can achieve removing the corresponding row when the remove button is clicked and the input values should not be reset when the component re-renders
So when I refresh the page and click to remove an input it will clear all other input data. How can I fix this problem ?
Update added full component to question:
const Agreement = (props) => {
const { agreement, editable, teamData, teamId, fetchTeamData } = props;
const [editing, setEditing] = useState(false);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [showErrors, setShowErrors] = useState(false);
const [errorsArr, setErrorsArr] = useState();
const initialFormState = {
rule_0: teamData.rule_0,
rule_1: teamData.rule_1,
rule_2: teamData.rule_2,
rule_3: teamData.rule_3,
creator: teamData.User.public_user_id,
};
const [updateTeamData, setUpdateTeamData] = useState(initialFormState);
const [inputs, setInputs] = useState(teamData.rules);
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
};
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule-${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...inputs];
list.splice(index, 1);
setInputs(list);
};
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState,
};
});
};
const handleSubmit = async (event) => {
event.preventDefault();
setEditing(false);
// Send update request
console.log(updateTeamData);
const res = await axios.put(`/api/v1/teams/team/${teamId}`, updateTeamData);
// If no validation errors were found
// Validation errors don't throw errors, it returns an array to display.
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Call update profiles on parent
fetchTeamData();
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
const handleCancel = () => {
setEditing(false);
};
useEffect(() => {
if (agreement === "default") {
setTitle(defaultTitle);
setInputs(teamData.rules);
} else {
setTitle(agreement.title ?? "");
}
}, [agreement, teamData]);
return (
<div className="team-agreement-container">
{!editing && (
<>
<h4 className="team-agreement-rules-title">{title}</h4>
{editable && (
<div className="team-agreement-rules">
<EditOutlined
className="team-agreement-rules-edit-icon"
onClick={() => setEditing(true)}
/>
</div>
)}
<p className="team-agreement-rules-description">{description}</p>
{teamData.rules.map((rule, index) => (
<div className="team-agreement-rule-item" key={`rule-${index}`}>
{rule ? (
<div>
<h4 className="team-agreement-rule-item-title">
{`Rule #${index + 1}`}
</h4>
<p className="team-agreement-rule-item-description">
- {rule}
</p>
</div>
) : (
""
)}
</div>
))}
</>
)}
{/* Edit rules form */}
{editing && (
<div className="team-agreement-form">
{showErrors && <ModalErrorHandler errorsArr={errorsArr} />}
<h1>Rules</h1>
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={`${data}-${idx}`}>
<button
type="button"
className="agreement-remove-button"
onClick={() => {
handleRemoveClick(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
type="text"
placeholder={`Rule ${idx + 1}`}
defaultValue={teamData.rules[idx]}
name={`rule_${idx}`}
onChange={handleChange}
/>
</div>
);
})}
{inputs.length < 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
<div className="div-button">
<button className="save-button" onClick={handleSubmit}>
Save
</button>
<button className="cancel-button" onClick={handleCancel}>
Cancel
</button>
</div>
</div>
)}
</div>
);
};
export default Agreement;
Hi i am working on a React application where there are four options.when a user select an option corresponding input element will be added to the wrapper.In the following code add operation works fine but remove operation is not working properly ,it is not removing the corresponding element.Another problem the values on the inputs fields not present when the component re-renders.so experts guide me how i can acheive removing the corresponding row when the remove button is clicked and the input values should not be reset when the component re-renders.
But when I submit the input it will appear my data perfectly and when i restart the page and just click into edit and hit submit with the defaultValue it just clear all the data and send back to my backend with undefined value like this: [ undefined, undefined, undefined, undefined ]
Here is my full component:
const Agreement = (props) => {
const { agreement, editable, teamData, teamId, fetchTeamData } = props;
const [editing, setEditing] = useState(false);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [showErrors, setShowErrors] = useState(false);
const [errorsArr, setErrorsArr] = useState();
const initialFormState = {
rule_0: teamData.rules.rule_0,
rule_1: teamData.rules.rule_1,
rule_2: teamData.rules.rule_2,
rule_3: teamData.rules.rule_3,
creator: teamData.User.public_user_id,
};
const [updateTeamData, setUpdateTeamData] = useState(initialFormState);
const [inputs, setInputs] = useState(teamData.rules);
const handleChange = (event) => {
const { name, value } = event.target;
// Update state
setUpdateTeamData((prevState) => ({
...prevState,
[name]: value,
}));
};
// Add more input
const addInputs = () => {
setInputs([...inputs, { name: `rule_${inputs.length + 1}` }]);
};
// handle click event of the Remove button
const removeInputs = (index) => {
const list = [...inputs];
list.splice(index, 1);
setInputs(list);
};
const clearInput = (dataName) => {
setUpdateTeamData((prevState) => {
delete prevState[dataName];
return {
...prevState,
};
});
};
const handleSubmit = async (event) => {
event.preventDefault();
setEditing(false);
// Send update request
const res = await axios.put(`/api/v1/teams/team/${teamId}`, updateTeamData);
// If no validation errors were found
// Validation errors don't throw errors, it returns an array to display.
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Call update profiles on parent
fetchTeamData();
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
const handleCancel = () => {
setEditing(false);
};
useEffect(() => {
if (agreement === "default") {
setTitle(defaultTitle);
setInputs(teamData.rules);
} else {
setTitle(agreement.title ?? "");
}
}, [agreement, teamData]);
console.log("teamData.rules", teamData.rules);
console.log("inputs", inputs);
return (
<div className="team-agreement-container">
{!editing && (
<>
<h4 className="team-agreement-rules-title">{title}</h4>
{editable && (
<div className="team-agreement-rules">
<EditOutlined
className="team-agreement-rules-edit-icon"
onClick={() => setEditing(true)}
/>
</div>
)}
<p className="team-agreement-rules-description">{description}</p>
{teamData.rules.map((rule, index) => (
<div className="team-agreement-rule-item" key={`rule-${index}`}>
{rule ? (
<div>
<h4 className="team-agreement-rule-item-title">
{`Rule #${index + 1}`}
</h4>
<p className="team-agreement-rule-item-description">
- {rule}
</p>
</div>
) : (
""
)}
</div>
))}
</>
)}
{/* Edit rules form */}
{editing && (
<div className="team-agreement-form">
{showErrors && <ModalErrorHandler errorsArr={errorsArr} />}
<h1>Rules</h1>
{inputs.map((data, idx) => {
return (
<div className="agreement-form-grid" key={`${data}-${idx}`}>
<button
type="button"
className="agreement-remove-button"
onClick={() => {
removeInputs(idx);
clearInput(`rule_${idx}`);
}}
>
<Remove />
</button>
<input
name={`rule_${idx}`}
onChange={handleChange}
value={teamData.rules[idx]}
/>
</div>
);
})}
{inputs.length < 4 && (
<div className="team-agreement-add-rule">
<button type="submit" onClick={addInputs}>
<Add />
</button>
</div>
)}
<div className="div-button">
<button className="save-button" onClick={handleSubmit}>
Save
</button>
<button className="cancel-button" onClick={handleCancel}>
Cancel
</button>
</div>
</div>
)}
</div>
);
};
export default Agreement;
How can I fix this error?
My thought is the problem is around [inputs, setInputs]
Try this
<input
//..
onChange={(event) => handleChange(event.target.value)}
//..
/>
then in your "handleChange" function
const handleChange = (event) => {
const { name, value } = event;
//....
};