This may be a simple question and I'm just missing something, but I haven't found an answer yet.
I would like to use useState with an object storing both onChange and onClick events.
With onChange, the attributes of event.target.name and event.target.value are provided from the virtual DOM. That makes it easy to identify which parameter the event should be targeting.
However, with onClick there is no attribution of the element from either of these event.target parameters.
How can one identify which tag is generating an onClick event in order to tell the handler which parameter it should affect in an object of several boolean parameters?
Here's an example I created in an effort to be both as simple and complete as possible:
import React from 'react';
const initialState = {
switches: {
menuOpen: false,
modalOpen: false,
},
fields: {
username: '',
password: '',
},
};
export default function Header(props) {
const [changeStateObj, setChangeStateObj] = useState(initialState.fields);
const [toggleStateObj, setToggleStateObj] = useState(initialState.switches);
useEffect(() => {
console.log(changeStateObj, toggleStateObj);
}, [changeStateObj, toggleStateObj]);
const changeHandler = (event) => {
let name = event.target.name; // these provide values
let value = event.target.value;
setChangeStateObj((values) => ({ ...values, [name]: value })); // and therefore, this works
};
const clickHandler = (event) => {
let name = event.target.name // nothing here
setToggleStateObj((prevState) => ({ [name]: !prevState.name })); // no dice
};
return (
<div>
<label htmlFor="username">
User Name
<input type="text" name="username" onChange={changeHandler} />
</label>
<label htmlFor="password">
Password
<input type="password" name="password" onChange={changeHandler} />
</label>
<button name="modalOpen" onClick={clickHandler}>
Open modal
</button>
<button name="menuOpen" onClick={clickHandler}>
Open menu
</button>
</div>
);
}
const handleClickModalOpen = () => clickHandler({target: {name : 'modalOpen'}});
<button name="modalOpen" onClick={handleClickModalOpen}>
Open modal
</button>
Second approach, with your existing code, try use event.currentTarget.name rather than event.target.name. The currentTarget should tell you which exact element you've attached the event handler.
Related
My app gets initialized with a json payload of 'original values' for attributes in a task form. There is a shared state between various components through a context manager submissionState that also gets imported to this form component. This shared state is a copy of the original values json but includes any edits made to attributes in the payload. I would like to include individual 'reset' buttons for each input of the form which would update the shared state to the original value in the json payload. The original values get passed into the parent form component as props and the edited value state gets called from within the parent component as well.
const FormInput = ({ fieldName, fieldValue, onChange, originalValue }) => {
const handleReset = e => {
//????
}
return (
<div>
<label htmlFor={fieldName}>
{fieldName}
</label>
<br />
<input type="text"
name={fieldName}
value={fieldValue}
onChange={onChange}/>
<button id="reset" onClick={handleReset}>↻</button>
<br />
<div>{originalValue}</div>
</div>
);
};
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
function handleChange(evt) {
const value = evt.target.value;
setSubmission({
...submissionState,
[evt.target.name]: value,
});
}
const taskFields = ["name", "address", "address_extended", "postcode", "locality", "region", "website"];
return (
<div>
<form>
{taskFields.map((field) => {
<FormInput
key={field}
fieldName={field}
fieldValue={submissionState[field]}
onChange={handleChange}
originalValue={payload[field]}
/>
})
}
</form>
</div>
);
};
export default TaskForm;
What I would like to do is include logic in the reset button function so that any edits which were made in a form input (from state) get reverted to the original value (stateless), which comes from the payload props: payload[field].
The form input is controlled through a global shared state submissionState, so the reset button logic can either modify the shared state itself with something like:
const handleReset = (submissionState,setSubmissionState) => {
setSubmission({
...submissionState,
fieldName: originalValue,
});
but I would need to pass the submissionState and setSubmission down through to the child component. It would be better if I can somehow update the value attribute in the input, which in-turn should potentially update the shared state? And the logic can just be something like this (assuming I can somehow access the input's value state in the reset button)
const handleReset = (?) => {
/*psuedo code:
setInputValueState(originalValue)
*/
}
I would highly recommend using react-hook-form if it's an option. I've implemented it across several projects and it has never let me down. If it's just not possible to use a library, then keep in mind that React is usually unidirectional. Don't try to work around it, since it works that way by design for most cases you can encounter. Otherwise…
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
const upsertSubmission = (upsert) =>
setSubmission({
...submissionState,
...upsert,
});
const handleChange = ({ target }) => {
upsertSubmission({
[target.name]: target.value,
});
};
const reset =
(originalValue) =>
({ target }) => {
upsertSubmission({
[target.name]: originalValue,
});
};
/* Also something like this. RHF will handle most of this for you!
* const reset = (originalValue, fieldName) =>
* upsertSubmission({[fieldName]: originalValue})
*/
const taskFields = [];
return (
<div>
<form>
{taskFields.map((field) => (
<FormInput
key={field}
fieldName={field}
onChange={handleChange}
reset={reset(originalValue)}
value={submissionState[field]}
/>
))}
</form>
</div>
);
};
I new to React. I have two input fields which are filled when the pages loads via axios but when I want to change the Values I am unable to change them even I can not type anything on them.
function MainView() {
const [InputFields, setInputFields] = useState({
name: "",
fullURL: "",
});
const changeHandler = (e) => {
setInputFields({
...InputFields,
[e.target.name]: e.target.value,
});
};
const [links, setLinks] = useState([]);
const getLinks = () => {
axios.get('../sanctum/csrf-cookie').then(response => {
axios.get("/userlinks/getdata").then((res) => {
console.log(res);
setLinks(res.data);
}).catch((err) => {
console.log(err);
});
});
};
useEffect(() => {
getLinks();
}, []);
return (<div>
{links.map((link) => {
return (<div className="card" key={link.id}>
<form>
<input className="form-control" value={link.name} type="text" name="name" placeholder="name" onChange={changeHandler} />
<input className="form-control" value={link.fullURL} type="text" name="fullURL" placeholder="fullURL" onChange={changeHandler} />
</form>
</div>);
})}
</div>
);
}
export default MainView;
This happens because in your onChange method, you are changing InputFields variable, where as your getLinks method changes links variable, which is being rendered on the screen.
If you want to set an initial value, and then allow the user to change it, change your input to :
<input className="form-control" defaultValue={link.name}
value={InputFields.name} type="text" name="name" placeholder="name" onChange={changeHandler} />
Likewise change for your other input, if you do not want the user to change the value later on, it's often better to add disable in the input to avoid confusing people. 🙂
I know that this has been done so that you can create a minimal reproducible example for us, but I would have directly called setInputFields in the axios.get section to avoid this problem in the first place, however, if not possible, use the defaultValue and value as I've shown above.
I want to test my Log In Component, which consists of two input fields, whose values are determined by a single react state that is an object with two parameters. However, when I try the test only the first letter appears in the value of the selected input and not the rest of the word. I determined my use of ...prev when updating the state to be the issue. If I only use a single input field with one state it works fine!
Here is my component:
import {useState} from 'react';
export function Login () {
//Login Credentials
const [loginCredentials, setLoginCredentials] = useState({ name: '' });
const handleChange = ({target}) => {
setLoginCredentials({[target.name]: target.value});
}
return (
<div className="login-container">
<h1>Log In</h1>
<div className="login-label-input">
<label htmlFor="name">Account Name
<input
type="name"
id="name"
name="name"
onChange={handleChange}
value={loginCredentials.name}
/>
</label>
<label htmlFor="name">Password
<input
type="password"
id="password"
name="password"
onChange={handleChange}
value={loginCredentials.password}
/>
</label>
</div>
State name: {loginCredentials.name}. State password: {loginCredentials.password}.
</div>
)
}
This works but if I include the password state:
export function Login () {
//Login Credentials
const [loginCredentials, setLoginCredentials] = useState({ name: '', password: '' });
const handleChange = ({target}) => {
setLoginCredentials((prev) => ({...prev, [target.name]: target.value}));
}
...
it does not pass the test. I does not throw an error but simply only adds the first letter of the string I am testing with:
test("log in with empty name input returns error message", async () => {
render(
<Login />
);
const nameField = screen.getByLabelText(/account name/i);
userEvent.type(nameField, 'test');
await waitFor(() => expect(nameField).toHaveValue('test'));
});
with the error:
expect(element).toHaveValue(test)
Expected the element to have value:
test
Received:
t
Is using ...prev bad or is this is a bug or what is going on?
It seems like you have to assign the new value to a different variable, I am not sure why this is necessary for the test but not in the app itself.
const handleChange = ({target}) => {
const newValue = target.value
setLoginCredentials((prev) => ({ ...prev, [target.name]: newValue }));
}
I have an array of tags that take an input and update when the user presses enter. For some reason, the user needs to press enter twice before anything happens.
const [ tags, setTags ] = useState([]);
const addTag = (inputEvent) => {
if (inputEvent.key === 'Enter') {
setTags([ ...tags, inputEvent.target.value ]);
inputEvent.target.value = '';
}
};
return(
<input
type="text"
placeholder="Press enter"
onKeyUp={(inputEvent) => addTag(inputEvent)}
/>
)
Need more context to be sure. But i would guess this is a classic stale state problem. Instead of setTags([ ...tags, inputEvent.target.value ]), try use the callback function signature:
setTags(tags => [ ...tags, inputEvent.target.value ])
Use the ref to access the current tag from your input field.
Use form so that it is easier to add the tags and listen to Enter submit.
On submit, update your tags, and reset your form using the ref.
import React from "react";
export default function App() {
const [tags, setTags] = React.useState([]);
const inputRef = React.useRef(null);
const formRef = React.useRef(null);
const addTag = (e) => {
e.preventDefault();
setTags([...tags, inputRef.current.value]);
formRef.current.reset();
};
return (
<div>
<p>{tags.join(",")}</p>
<form onSubmit={addTag} ref={formRef}>
<input type="text" ref={inputRef} placeholder="Press enter" />
</form>
</div>
);
}
Note :- It is advisable in React to use ref to access DOM elements and manipulating them.
You could check a working example here -> https://codesandbox.io/s/wispy-microservice-3j455?file=/src/App.js:0-469
I am new to React and learning it on my own, I am trying to implement a simple form where the user can provide a name and it will then be store is the state. Once he stop typing and clink on send the the name is store and the fields is
in the input is reset not the state.
This is what i tried and i get
an error saying that cannot read property "then"
changeFun = (e) => {
this.setState({name: e.target.value})
}
submitFun = (e) => {
e.preventDefault()
this.setState({ name: e.target.value})
}
render() {
return (
<input type = "text" value={this.state.name}/>
<button
onSubmit = {(e) =>
this.submitFun(e).then(
() => reset()
)
onchange ={this.changeFun}}>
SEND
</button>
)
}
submitFun is not returning a promise. So you can't use .then after it.
submitFun = (e) => {
e.preventDefault()
// this.setState({name: e.target.value}) should not be here
// because e.target is <button/>
this.setState({name: ''}) // This will reset the input value
}
<button onClick = {this.submitFun} onchange ={this.changeFun}>SEND</button>
In addition you need to use onClick instead of onSubmit for <button> tag.
onSubmit will be used for <form> tag.
what are you trying to achieve is called Controlled Component! more info here.
the base of Controlled Component is basically you have a property in your state and a form element (i.e and input element). then you chain that input value to your state by a function, and that function is going to run on onChange event, to update the state on every change.
something like this:
class App extends React.Component {
constructor(props) {
super()
this.state = {
inputValue: ""
}
}
handleChange = e => {
const _tempValue = e.target.value
e.preventDefault()
this.setState({inputValue: _tempValue})
}
handleSubmit = e => {
const {inputValue} = this.state
e.preventDefault()
// here is your data
// save it to redux or do what ever you want to
console.log(inputValue)
// last thing here is gonna be to reset state after submition
this.setState({inputValue: ""})
}
render() {
const {inputValue} = this.state
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
value={inputValue}
onChange={this.handleChange}
placeholder="type something"
/>
<input type="submit" />
</form>
<p>{inputValue}</p>
</div>
)
}
}
this is a basic implementation of what you want to do, its here: https://codesandbox.io/s/m39w10olnp
on the example that you provide though , you using then that is basically used when that you returning a promise from a function, something like this:
export const giveMeArray= () => {
return new Promise( (res,rej) => {
setTimeout(() => {
res(Object.assign([], myArray))
}, delay);
})
}
so as you can see there is no need to use then here, check my simple example to implement in a better way!