Here's the code
render () {
if (this.props && this.props.list) {
return (
<div className="List_select">
{!this.state.editing
&& <div><button className="List_delete" onClick={(ev) => {
ev.preventDefault();
this.props.handleDelete(this.props.list);
}} />
<NavLink to={{
pathname: '/list/' + this.props.userId + '|' + this.props.list
}}>
{this.props.list}
</NavLink>
<button className="List_edit" onClick={(ev) => {
ev.preventDefault();
this.setState({editing: true})
}} /></div>
}
{this.state.editing
&& <form onSubmit={(ev) => {
ev.preventDefault();
this.props.handleEdit(this.props.list, this.state.listName);
this.setState({editing: false})
}}><input type="text" name="listName" className="List_input" value={this.props.list} onChange={(ev) => {
this.setState({listName: ev.target.value});
}} />
<button type="submit" value="submit" name="submit" className="StandardButton-1">Submit</button>
</form>
}
</div>
)
} else {
return ""
}
}
The code is not letting the user fill out the text field. Does anyone know what I'm doing wrong? I can edit the text fields at all. I have the onChange trigger and doing all the good stuff to handle text fields correct in the code.
You are giving it value but in onChange your changing another value.
Have the same value in both :)
<input type="text" name="listName" className="List_input"
value={this.state.listName}
onChange={(ev) => {this.setState({listName: ev.target.value}) }}
/>
value={this.props.list} onChange={(ev) => {
this.setState({listName: ev.target.value});
the listName and list must match, and you have to use this.state.list
You have to assign the text box value from state not props, because props is readonly. So you have to replace this.props.list with this.state.listName
<input
type="text"
name="listName"
className="List_input"
value={this.state.listName}
onChange={ev => {
this.setState({ listName: ev.target.value });
}}
/>
Related
I have a basic react hook form with field array. With append the form fields replicates as expected. For each array, I want the user to choose some field to enter without entering the other. I am using radio button and useState to achieve this. However, when i change the selection in an array, the selections in the other arrays changes as well. Please how do i correct this ? Or is there a better way to achieve this functionality. Thanks in advance for your help. The code is found below. I also have codeSandbox: https://codesandbox.io/s/usefieldarray-react-hook-form-2yp3vb?file=/src/App.js:0-3753
export default function App() {
const { handleSubmit, control } = useForm({
defaultValues: {
Detail: [
{
userName: {},
officeAddress: {},
homeAddress: {}
}
]
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "Detail"
});
const [checked, setChecked] = useState();
// onChange function for the address forms
const changeAddressForm = (e) => {
setChecked(e.target.value);
};
const onSubmit = async (data) => {};
return (
<div className="App">
<h1>Selecting a different form in each field array</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<ul>
{fields.map((field, index) => {
return (
<li
key={field.id}
className="w3-border w3-border-green w3-padding"
>
<div>
<div className="w3-padding-large">
<label>Username</label>
<Controller
name={`Detail.${index}.userName`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
<div>
<Radio.Group onChange={changeAddressForm} value={checked}>
<Radio value={1}>Office address</Radio>
<Radio value={2}>Home address</Radio>
</Radio.Group>
</div>
<div className="w3-padding-large">
{checked === 1 && (
<div>
<label>Office address</label>
<Controller
name={`Detail.${index}.officeAddress`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
)}
</div>
<div className="w3-padding-large">
{checked === 2 && (
<div>
<label>Home address</label>
<Controller
name={`Detail.${index}.homeAddress`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
)}
</div>
</div>
</li>
);
})}
</ul>
<section>
<button
type="button"
onClick={() =>
append({
userName: {},
homeAddress: {},
officeAddress: {}
})
}
>
Append
</button>
</section>
</form>
</div>
);
}
I have a login form functional component built using react-hooks comprising of userid, password and rememberMe checkbox.
For some reason, the checkbox is not working as in, when I click on it, it does tick or untick. Which is basically its state is not changing.
userid, password and rememberMe checkbox needs to be sent to the backend. That's why I have to couple it with inputs. If it was an independent checkbox than it would have been easy.
I have handleChange() for userid, password and I have handleChechbox() for handling checkbox.
Below is the complete code.
const Login = (props) => {
const [inputs, setInputs] = useState({
userid: "",
password: "",
rememberPassword: false,
});
const [submitted, setSubmitted] = useState(false);
const { userid, password, rememberPassword } = inputs;
// reset login status
useEffect(() => {
dispatch(loginActions.logout());
}, []);
function handleChange(e) {
const { name, value } = e.target;
setInputs((inputs) => ({ ...inputs, [name]: value }));
}
function handleChechbox(e) {
const { name, value } = e.target;
console.log("eeeeee check", e.target.type);
console.log("eeeeee check", e.target.checked);
console.log("eeeeee check inputs", inputs);
console.log("eeeeee check inputs remember", inputs.rememberPassword);
if (e.target.type === "checkbox" && !e.target.checked) {
setInputs((inputs) => ({ ...inputs, [name]: "" }));
} else {
setInputs((inputs) => ({ ...inputs, [name]: value }));
}
}
function handleSubmit(e) {
e.preventDefault();
setSubmitted(true);
if (inputs) {
// get return url from location state or default to home page
const { from } = location.state || {
from: { pathname: "/admin/summary" },
};
dispatch(loginActions.login(inputs, from));
// props.history.push("/admin/summary");
}
}
return (
<div className="Login">
<div className="login-form-container">
<div className="content">
<Form className="login-form" onSubmit={handleSubmit}>
<InputGroup>
<InputGroupAddon
className="input-group-addon"
addonType="prepend"
>
<i className="fa fa-user"></i>
</InputGroupAddon>
<input
autoFocus
type="email"
aria-label="Username"
aria-describedby="Username"
aria-invalid="false"
placeholder="Username or Email"
name="userid"
value={userid}
onChange={(event) => handleChange(event)}
className={
"form-control" + (submitted && !userid ? " is-invalid" : "")
}
/>
{submitted && !userid && (
<div className="invalid-feedback">
Username or Email is required
</div>
)}
</InputGroup>
<InputGroup>
<InputGroupAddon
className="input-group-addon"
addonType="prepend"
>
<i className="fa fa-lock"></i>
</InputGroupAddon>
<input
type="password"
name="password"
placeholder="Password"
aria-label="password"
aria-describedby="password"
value={password}
onChange={(event) => handleChange(event)}
className={
"form-control" + (submitted && !password ? " is-invalid" : "")
}
/>
{submitted && !password && (
<div className="invalid-feedback">Password is required</div>
)}
</InputGroup>
<div className="form-actions">
<br />
<div className="form-check">
<input
type="checkbox"
className="form-check-input"
id="rememberPassword"
name="checkbox"
checked={rememberPassword}
onChange={(event) => handleChechbox(event)}
// required
/>
<label className="form-check-label" for="rememberPassword">
Remember me
</label>
</div>
</div>
</Form>
</div>
</div>
</div>
);
};
You're reading the name attribute which is 'checkbox' not 'rememberPassword'. I'd imagine a simple console log of inputs would reveal you're setting the wrong property name.
Anyway your input is controlled, you don't need to read DOM values since you know that the value is and what it should change to.
function handleRememberMeChange(e) {
setInputs((inputs) => ({ ...inputs, rememberPassword: !inputs.rememberPassword }));
}
If you want it to work generically from an attribute then use id or change name to "rememberPassword". And use e.target.checked not e.target.value.
Also it's fine to do onChange={handleChechbox} instead of onChange={(event) => handleChechbox(event)} it's the same thing.
I have a login component that has an email and password to log in. After a successful, I use this.props.history.push('/userComponent) to route a another component. At the component where it is routed should display the name of the person/user. I am trying to send as a prop but I end up getting undefined as the user component (the component to which it is routed) is not rendered. Please check the below.
export default class Login extends Component {
constructor(props) {
super(props);
this.x = '';
}
onSubmit = () => {
fetch('http://localhost:5000/api/account/')
.then(res => res.json())
.then(data => {
for (let index = 0; index < data.length; index++) {
if (userName === data[index].email && passWord === data[index].password && data[index].role === "Admin") {
console.log("login is successful");
this.x = true;
console.log('.......state after update: ' + this.x);
this.props.history.push('/userA');
return (
<div>
<UserA somePropName={data[index].userName} />
</div>
);
}
else if (userName === data[index].email && passWord === data[index].password) {
console.log("login is successful");
this.x = false;
console.log("x....... " + this.x);
this.props.history.push('/userB');
return (
<UserB somePropName={data[index].userName} />
);
}
else {
this.props.history.push('/errorPage');
}
}
});
}
render() {
return (
<div>
<div class="container">
<label for="uname"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="uname" required />
<label for="psw"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="psw" required />
<button type="submit" onClick={this.onSubmit}>Login </button>
<label>
<input type="checkbox" checked="checked" name="remember" /> Remember me
</label>
</div>
<div class="container" style="background-color:#f1f1f1">
<button type="button" class="cancelbtn">Cancel</button>
<span class="psw">Forgot password?</span>
</div>
</div>
);
}
}
The way you can pass properties to other component while navigation
this.props.history.push({
pathname: '/userB',
state: { title: 'Hello world' }
})
using Link
<Link to={{
pathname: '/userB',
state: { title: 'Hello...' }
}}>Click</Link>
Access like this in navigated component
this.props.location.state.title
You should render conditionally UserA or UserB in render(). Place the user related fields in the state. Something like
render() {
return (
<div>
<div class="container">
<label for="uname"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="uname" required />
<label for="psw"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="psw" required />
<button type="submit" onClick={this.onSubmit}>Login </button>
<label>
<input type="checkbox" checked="checked" name="remember" /> Remember me
</label>
</div>
<div class="container" style="background-color:#f1f1f1">
<button type="button" class="cancelbtn">Cancel</button>
<span class="psw">Forgot password?</span>
</div>
{this.state.isLoggedIn && this.state.isAdmin && <UserA somePropName={this.state.userName} />}
{this.state.isLoggedIn && !this.state.isAdmin && <UserB somePropName={this.state.userName} />}
</div>);
}
For the onSubmit func
onSubmit = () => {
fetch('http://localhost:5000/api/account/')
.then(res => res.json())
.then(data => {
this.props.history.push('/xxx');
this.setState({
isAdmin: data[index].role === "Admin"
isLoggedIn: userName === data[index].email && passWord === data[index].password,
userName: data[index].userName
})
})
}
I have a modal form with a form that uses Formik. Here are two pictures that show two states of that form that can be toggled with a switch.Initially I fill text into fields which can be added dynamically and stored as an array with .
The second picture shows how I toggled to textarea. There you can also add text with commas that will be turned into an array.
Is there any way to fill data in input fields from the first screen, toggle into textarea and access already inputted data.
I understand formik keeps that state somewhere. But at the moment these fields have a separate state.
Here is my component:
class ModalForm extends React.Component {
constructor(props) {
super(props);
this.state = {
disabled: true,
};
}
onChange = () => {
this.setState({
disabled: !this.state.disabled,
});
};
render() {
var {
visible = false,
onCancel,
onRequest,
submitting,
setSubscriberType,
editing,
subscriptionTypeString,
tested,
selectedGates,
} = this.props;
const { gateId } = selectedGates.length && selectedGates[0];
const handleSubmit = values => {
console.log(values);
onRequest && onRequest({ gateId, ...values });
};
const { disabled } = this.state;
return (
<Modal
footer={null}
closable
title="Список абонентов для выбранного гейта"
visible={visible}
onCancel={onCancel}
onOk={handleSubmit}
destroyOnClose
width="600px"
>
<StyledDescription>
<Switch onChange={this.onChange} />
<StyledLabel>массовый ввод</StyledLabel>
</StyledDescription>
<Formik
initialValues={{ abonents: [''] }}
onSubmit={handleSubmit}
render={({ values, handleChange }) => (
<Form>
{disabled ? (
<FieldArray
name="abonents"
render={arrayHelpers => {
return (
<div>
{values.abonents.map((value, index) => (
<div key={index}>
<MyTextInput
placeholder="Абонент ID"
name={`abonents.${index}`}
value={value}
onChange={handleChange}
/>
<Button
shape="circle"
icon="delete"
onClick={() => {
arrayHelpers.remove(index);
}}
/>
</div>
))}
<Button type="dashed" onClick={() => arrayHelpers.push('')}>
<Icon type="plus" />Добавить абонента
</Button>
</div>
);
}}
/>
) : (
<StyledField
placeholder="Введите ID абонентов через запятую"
name="message"
component="textarea"
/>
)}
<Footer>
<Button type="primary" htmlType="submit">
Запросить
</Button>
</Footer>
</Form>
)}
/>
</Modal>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
Pretty easy, formik stores values inside values.abonents, hence you can use it inside textarea
let { Formik, Form, Field, ErrorMessage, FieldArray } = window.Formik;
function App () {
const [disabled, setDisabled] = React.useState(false) // some boilerplate code
function submit (values) {
console.log('submit', values)
}
return (
<Formik
initialValues={{ abonents: [] }}
onSubmit={submit}
render={({ values, handleChange, setFieldValue }) => (
<Form>
<FieldArray
name='abonents'
render={arrayHelpers => {
if (!disabled) {
return (
<textarea onChange={(e) => {
e.preventDefault()
setFieldValue('abonents', e.target.value.split(', '))
}} value={values.abonents.join(', ')}></textarea>
)
}
return (
<div>
{
values.abonents.map((value, index) => (
<div key={index}>
<input
placeholder='Абонент ID'
name={`abonents.${index}`}
value={value}
onChange={handleChange}
/>
<button onClick={(e) => {
e.preventDefault()
arrayHelpers.remove(index)
}}>
-
</button>
</div>
))
}
<button onClick={(e) => {
e.preventDefault()
arrayHelpers.push('')
}}>
+
</button>
</div>
)
}}
/>
<button type='submit'>Submit</button>
<button onClick={e => {
e.preventDefault()
setDisabled(!disabled)
}}>toggle</button>
</Form>
)}
/>
)
}
ReactDOM.render(<App />, document.querySelector('#root'))
<script src="https://unpkg.com/react#16.9.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/formik/dist/formik.umd.production.js"></script>
<div id='root'></div>
I found a solution. You have got to give the same name to the input and textarea, so whe you add text in input will automatically change text in textarea
<FieldArray
name="abonents"
render={arrayHelpers => {
and
<StyledField
placeholder="Введите ID абонентов через запятую"
name="abonents"
component="textarea"
/>
These two fields have same name so they are rendered interchangeably but have common text inside them
I do not quite understand yet how react is working.
As far as I noticed I am supposed to handle detection of a change of form fields (for example angular and knockout detect fields changes with observables).
I have the following snippet in react:
constructor(props) {
super(props);
this.state = {
products: [],
branchId: document.getElementById('branchId').value
};
this.updateproduct = this.updateproduct.bind(this);
this.deleteproduct = this.deleteproduct.bind(this);
}
render() {
return (
<Container>
{
this.state.products.map(product => (
<div key={product.id}>
<div className="col-xs-12 col-sm-3">
<div className="user-label">Title</div>
<input type="text" name="title" className="form-control" defaultValue={product.title} onChange={this.handleChange} />
</div>
<div className="col-xs-12 col-sm-3">
<div className="user-label">Upload image</div>
<input type="text" name="image" className="form-control" defaultValue={product.image} onChange={this.handleChange} />
<img src={product.image} height="100" width="auto" style={{ marginTop: '5px' }} />
</div>
<div className="col-xs-12 col-sm-5">
<div className="user-label">Description</div>
<textarea name="description" className="form-control" rows="7" defaultValue={product.description} onChange={this.handleChange}>
</textarea>
</div>
<div className="col-xs-12 col-sm-1">
<div className="user-label"> </div>
<span className="btn btn-danger pull-right" data-btntype="delete" onClick={() => this.deleteproduct(product.id)}>
×
</span>
<span className="btn btn-success pull-right" data-btntype="update" onClick={() => this.updateproduct(this)} style={{ marginTop: '2px' }}>
✓
</span>
</div>
<div className="clearfix"></div>
</div>
))
}
</Container>
)
}
and don't quite know how to write a function that detect change of every field?
Also how can I handle the form update?
The above snippet is part of a bigger form (that's why form tag is not there) and I need to handle only the product object update above (not the entire form).
UPDATE
I applied the following solution based on #Kejt answer - with just a typo/ two corrections:
In the input:
onChange={e => this.handleChange(product.id, 'name', e.target.value) // Note 'this' must be added to the function whenever it's called
And then the handleChange method:
handleChange = (id, propertyName, value) => {
this.setState(
state => ({ // mapped 'products' need to be 'this.state.products'
products: this.state.products.map(product => product.id === id ? {...product, [propertyName]: value} : product)
})
)
}
Plus I had to add #babel/plugin-proposal-class-properties plugin to make the syntax working.
And in the class contructor added the usual declaration:
this.handleChange = this.handleChange.bind(this);
you already have onChange listener assigned to input onChange={this.handleChange}
you need to add handleChange method to your component
handleChange = event => {
console.log(event.target.value)
}
you can rebuild this method with adding product id and input name
onChange={e => handleChange(product.id, 'name', e.target.value)
and then in handleChange method
handleChange = (id, propertyName, value) => {
this.setState(
state => ({
products: products.map(product => product.id === id ? {...product, [propertyName]: value} : product)
})
}