State is not passing correctly with redux - reactjs

Hey i have two form that i want to keep there state seperatly, i added redux and added reducer to keep the state change, but my problem is when i change on form state it's add to the other form state his state also, i want to keep each state seperately
like you see in picture the replace property have been added to the newProjectForm when it only need to be at the roof form.
I have the same reducer on forms because i only need to keep track only onInputChange.
This are my two form:
First Form:
import React from 'react'
import {Form, FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar,
Toggle } from 'rsuite';
import InputPicker from '../input-picker';
import './form.css'
import {onInputChanged} from '../../redux/actions/form.actions';
import { connect } from 'react-redux';
//onChange = {props.onInputChanged} formValue = {props.newProjectForm}
const options = ["yes","no"];
function NewProjectForm(props){
return (
<Form className='form' layout="horizontal" >
<h1 className='title'>New Project</h1>
<br/>
<FormGroup>
<ControlLabel>Address</ControlLabel>
<FormControl name="address" />
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Affilate</ControlLabel>
<InputPicker name="affilate" data ={options} onChange={props.onInputChanged} style={{width:'300px'}}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Size</ControlLabel>
<FormControl name="size" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Bedroom Amount</ControlLabel>
<FormControl name="bedrooms" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Bathrooms amount</ControlLabel>
<FormControl name="bathrooms" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Stories</ControlLabel>
<FormControl name="stories" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Has Gas</ControlLabel>
<Toggle name="gas"/>
</FormGroup>
<FormGroup>
<ButtonToolbar>
<Button appearance="primary">Save</Button>
<Button appearance="default">Discard</Button>
</ButtonToolbar>
</FormGroup>
</Form>
);
}
const mapStateToProps = (state) => {
return {
newProjectForm: state.newProjectForm
}
};
const mapDispatchToProps = (dispatch) => {
return {
onInputChanged: (event) => dispatch(onInputChanged(event))
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(NewProjectForm);
Second Form:
import React from 'react'
import {Form, FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar} from 'rsuite';
import './form.css'
import {onInputChanged} from '../../redux/actions/form.actions';
import { connect } from 'react-redux';
import InputPicker from '../input-picker';
function RoofForm(props){
//replace all-roof type description color
const options = ["yes","no"];
// console.log(props);
//onChange={props.onInputChanged}
return (
<Form className='form' layout="horizontal" onChange = {props.onInputChanged} formValue = {props.roofForm}>
<h1 className='title'>Roof</h1>
<br/>
<FormGroup>
<ControlLabel>Replace ?</ControlLabel>
<InputPicker name="replace" style={{ width: 300 }} data={options} onChange={props.onInputChanged}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>All Roof?</ControlLabel>
<InputPicker name="all-roof" style={{ width: 300 }} data={options} onChange={props.onInputChanged}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
{props.roofForm['all-roof'] === 'yes' && (<div><FormGroup>
<ControlLabel>Type</ControlLabel>
<InputPicker name="type" style={{ width: 300 }} />
</FormGroup>
<FormGroup>
<ControlLabel>Description</ControlLabel>
<InputPicker name="description" style={{ width: 300 }} />
</FormGroup>
{props.roofForm.type === 'shingles' && <FormGroup>
<ControlLabel>Color</ControlLabel>
<InputPicker name="color" style={{ width: 300 }} />
</FormGroup>}
</div>)
}
<FormGroup>
<ControlLabel>Rain diverter</ControlLabel>
<FormControl name="rain-diverter" type="number" style={{ width: 300 }} />
</FormGroup>
<FormGroup>
<ControlLabel>Drip edge</ControlLabel>
<FormControl name="drip-edge" type="number" style={{ width: 300 }}/>
</FormGroup>
<FormGroup>
<ButtonToolbar>
<Button appearance="primary">Save</Button>
<Button appearance="default">Discard</Button>
</ButtonToolbar>
</FormGroup>
</Form>
);
}
const mapStateToProps = (state) => {
return {
roofForm: state.roofForm
}
};
const mapDispatchToProps = (dispatch) => {
return {
onInputChanged: (event) => dispatch(onInputChanged(event))
}
};
export default connect(
mapStateToProps,
mapDispatchToProps)
(RoofForm);
and this is my redux setup:
Form Actions:
export const ON_INPUT_CHANGED = 'ON_INPUT_CHANGED';
export function onInputChanged(event) {
console.log(event);
return(
{
type: ON_INPUT_CHANGED,
payload: event
}
)
}
and this is my reducer:
import {ON_INPUT_CHANGED} from '../actions/form.actions';
function formReducerWrapper(defaultState){
return (state=defaultState, action) => {
switch(action.type){
case ON_INPUT_CHANGED:
state = {
...state,
...action.payload
}
break;
default:
return state;
}
return state;
}
}
const newProjectDefault = {
affilate: {label:"", value: ""}
}
const roofDefault ={
replace:{label:"",value:""},
"all-roof":{label:"",value:""},
type:{label:"",value:""},
description: {label:"",value:""},
color:{label:"",value:""}
}
export const newProjectReducer = formReducerWrapper(newProjectDefault);
export const roofReducer = formReducerWrapper(roofDefault);
Thanks in Advance:)

It looks like you are trying to merge the input change event directly into redux state. That is not how it works.
You can access the HTML input element that changed from the change event using event.target and then read the latest value event.target.value.
If you want the form data to stay separate, then you need to dispatch different information for each form. For example:
dispatch({ type: 'input-changed', form: 'newProject', field: 'affiliate', value: event.target.value });
In your reducers, it should skip any events for a different form.
if (event.form !== 'newProject') return state;
If it becomes tedious there are helper packages like redux-form to help structure the application to avoid repetitive code. It also works fine to store form data in local state instead of putting it into redux.

Related

Why do i get error 400 on my graphQL mutation create user

I'm working with Reactjs and GraphQL integration. i got a problem when i'm doing mutation for new user.
Scenario :
Creating user using Modals bootstrap. when successful create new user, it shows alert or information success.
Code :
Here's my ModalCreate component code.
import React, { useState, useEffect } from 'react';
import { Button, Modal, Form } from "react-bootstrap";
const ModalCreate = (props) => {
// state for check input component
const [value, setValue] = useState({
username: props.username || '',
email: props.email || '',
fullname: props.full_name || '',
password: props.password || '',
phone: props.phone || '',
address: props.address || '',
groupid: props.group_id,
});
const onChange = event => {
setValue({
...value,
[event.target.name]: event.target.value
})
}
useEffect(() => {
if (props.show) {
document.body.classList.add("modal-open");
}
return () => {
if (document.body.classList.contains("modal-open")) {
document.body.classList.remove("modal-open");
}
};
}, [props.show]);
return (
<Modal show={props.show}>
<Modal.Header>
<Modal.Title> <span>FORMULIR AKUN PENGGUNA</span> </Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={e => {
e.preventDefault();
props.action({
variables: {
...value
}
})
}}>
<Form.Group className="mb-3">
<Form.Label>Role Akun</Form.Label>
<Form.Select aria-label="pilih user role" value={value.groupid} onChange={onChange}>
<option value="superadmin">Super Admin</option>
<option value="admin">Admin</option>
<option value="admin_rj ">Admin RJ</option>
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Username</Form.Label>
<Form.Control name="username" value={value.username} onChange={onChange}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Nama Lengkap</Form.Label>
<Form.Control name="fullname" value={value.fullname} onChange={onChange}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Email</Form.Label>
<Form.Control type="email" name="email" value={value.email} onChange={onChange}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Password</Form.Label>
<Form.Control type="password" name="password" value={value.password} onChange={onChange}/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Phone</Form.Label>
<Form.Control type="text" name="phone" value={value.phone} onChange={onChange}/>
</Form.Group>
<Button variant="secondary" type='submit'>
Simpan
</Button>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={props.onClose}>
Keluar
</Button>
</Modal.Footer>
</Modal>
);
};
export default ModalCreate;
and action/performing mutation in page call index.js :
import React, { useState } from 'react';
import { useQuery, useMutation } from '#apollo/client';
import { Container, Card, Button, InputGroup, FormControl, Form, Spinner } from 'react-bootstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faSearch } from '#fortawesome/fontawesome-free-solid';
import CardInfo from '../../../component/PengaturanPengguna/CardInfo';
import TableUserInfo from '../../../component/PengaturanPengguna/Table';
import { Loading } from '../../../component/Common';
import ModalCreate from '../../../component/PengaturanPengguna/Modals/ModalCreate';
import { GET_ALL_USERS, GET_USER_BY_ID } from '../../../gql/query';
import { REGISTER_USER } from '../../../gql/mutation';
const SearchInput = () => {
return (
<InputGroup className="mb-3">
<InputGroup.Text>
<FontAwesomeIcon icon={faSearch} />
</InputGroup.Text>
<FormControl
type="text"
placeholder="Search..."
/>
</InputGroup>
)
}
const PengaturanPengguna = (props) => {
// refetch and query data
const { data: usersdata, loading: usersloading, error: userserror } = useQuery(GET_ALL_USERS);
const { refetch, loading } = useQuery(GET_ALL_USERS);
// show modals
const [showModal, setShowModal] = useState(false);
// mutation new register user
const [registerUser, { loading: registerloading, error: registererror }] = useMutation(REGISTER_USER, {
refetchQueries: [{ query: GET_USER_BY_ID }, { query: GET_ALL_USERS }],
onCompleted: data => {
console.log(data)
},
onError: err => {
console.error(err);
}
}) ;
const handleRefreshClick = () => {
refetch();
}
const handleShowModal = () => setShowModal(true);
const handleCloseModal = () => setShowModal(false);
if (usersloading || registerloading) return <Loading/>
if (userserror || registererror) return <p>Error!</p>
return (
<Container>
<CardInfo/>
<Card>
<Card.Title>
<span className='base-md text-regular mt-2 std-txt-primary-200'>Data Pengguna Dashboard</span>
</Card.Title>
<Card.Body>
<div className='d-flex justify-content-between'>
<Form inline>
<SearchInput/>
<Button variant='secondary' onClick={handleRefreshClick} disabled={loading}>{loading ? ( <Spinner
as="span"
animation="border"
size="sm"
role="status"
aria-hidden="true"/> ) : 'Muat Ulang'}</Button>
</Form>
<div>
<Button variant='success' onClick={() => { setShowModal(true) }}>Daftar Akun</Button>
</div>
</div>
<TableUserInfo users={usersdata}/>
</Card.Body>
</Card>
{
showModal ? <ModalCreate show={handleShowModal} onClose={handleCloseModal} action={registerUser} /> : null
}
</Container>
)
}
export default PengaturanPengguna;
and here's my mutation :
const REGISTER_USER = gql`
mutation($input: RegisterInput!) {
register(input: $input) {
username
email
full_name
phone
address
group_id
}
}
`;
Error :
I got this error
Also, Network Status Tabs :
I've been try any solution but it still not working, any help will be appreciated, thank you
If you are getting validation error from apollo for required fields then check the form fields may be name attribute is missing and value is not storing inside your state.

onChange event doesn't fire on first input

I'm new to React and I've got this registration (parent component) where it has an inventory (child component) and I'm changing the state of the inventory once the user inputs the quantity of the item that they hold so I can register them
But onChange seems to be delayed, I input 4 water bottles for example, and it console logs me the default value, only when I input the amount for another item like food it displays me the 4 water bottles and 0 food :(
This is what I'm working with...
Child component:
import React from "react";
import { Col, Row, FormGroup, Label, Input } from "reactstrap";
import waterIcon from "./../assets/water.png";
import soupIcon from "./../assets/food.png";
import medsIcon from "./../assets/aid.png";
import weaponIcon from "./../assets/gun.png";
function Inventory({ onSubmitInventory, currentInventory }) {
const onChangeWater = (e) => {
const newInventory = { ...currentInventory, water: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeSoup = (e) => {
const newInventory = { ...currentInventory, soup: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeMeds = (e) => {
const newInventory = { ...currentInventory, meds: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeWeapon = (e) => {
const newInventory = { ...currentInventory, weapon: e.target.value };
onSubmitInventory(newInventory);
};
return (
<FormGroup>
<Row className="justify-content-center text-center border-top pt-3">
<Col xs="3">
<img src={waterIcon} alt="Water" />
<Label for="inventoryWater">Fiji Water:</Label>
<Input
type="number"
name="inventoryWater"
id="inventoryWater"
placeholder="Enter amount..."
onChange={onChangeWater}
/>
</Col>
<Col xs="3">
<img src={soupIcon} alt="Soup" />
<Label for="inventorySoup">Campbell Soup:</Label>
<Input
type="number"
name="inventorySoup"
id="inventorySoup"
placeholder="Enter amount..."
onChange={onChangeSoup}
/>
</Col>
<Col xs="3">
<img src={medsIcon} alt="Aid" />
<Label for="inventoryMeds">First Aid Pouch:</Label>
<Input
type="number"
name="inventoryMeds"
id="inventoryMeds"
placeholder="Enter amount..."
onChange={onChangeMeds}
/>
</Col>
<Col xs="3">
<img
className="d-block"
style={{ margin: "0 auto" }}
src={weaponIcon}
alt="Gun"
/>
<Label for="inventoryWeapon">AK47:</Label>
<Input
type="number"
name="inventoryWeapon"
id="inventoryWeapon"
placeholder="Enter amount..."
onChange={onChangeWeapon}
/>
</Col>
</Row>
</FormGroup>
);
}
export default Inventory;
Parent component:
import React, { useState } from "react";
import { Col, Row, Button, Form, Label, Input } from "reactstrap";
import { useForm } from "react-hook-form";
import Inventory from "./Inventory";
import MapContainer from "./MapContainer";
function Register() {
const [lonlat, setLonlat] = useState("");
const onMarkerChange = (lonlat) => {
setLonlat(lonlat);
};
const [inventory, setInventory] = useState({
water: 0,
soup: 0,
meds: 0,
weapon: 0,
});
const onSubmitInventory = (newInventory) => {
setInventory(newInventory);
console.log(inventory);
};
const { register, handleSubmit, errors } = useForm();
const onSubmit = (data) => {
console.log(inventory);
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: data.personName }),
};
console.log(data);
};
return (
<Form
id="registerForm"
name="registerForm"
onSubmit={handleSubmit(onSubmit)}
>
<h4>New Person</h4>
<Row className="border-top pt-4">
<Col xs="8">
<Label for="personName">Your name:</Label>
<Input
className="gray-input"
type="text"
name="name"
id="personName"
placeholder="Enter your name here..."
innerRef={register}
/>
</Col>
<Col xs="2" className="text-center">
<Label for="personAge">Age:</Label>
<Input
className="gray-input"
type="number"
name="age"
id="personAge"
placeholder="Enter age..."
innerRef={register}
/>
</Col>
<Col xs="2" className="text-center">
<Label for="personGender">Gender:</Label>
<Input
type="select"
name="gender"
id="personGender"
innerRef={register}
>
<option defaultValue disabled>
-
</option>
<option>F</option>
<option>M</option>
</Input>
</Col>
</Row>
<Row>
<Col xs="12">
<Input
hidden
id="personLatLon"
name="personLatLon"
type="text"
defaultValue={lonlat}
innerRef={register}
/>
<MapContainer onMarkerChange={onMarkerChange} />
</Col>
</Row>
<h4>Inventory</h4>
<Inventory
onSubmitInventory={onSubmitInventory}
currentInventory={inventory}
/>
<Button
outline
color="secondary"
className="mt-2"
type="submit"
form="registerForm"
>
Submit
</Button>
</Form>
);
}
export default Register;
EDIT: Updated code with tips from answers, still facing the same problem :(
The only problem I see with your code is that you are trying to pass a second argument to setInventory. I don't believe this works with hooks like it did with class components. When I attempt to type that in typescript it throws an instant error.
Just pass the actual data you are trying to send and then call onSubmitInventory(inventory) for example:
const onChangeWeapon = (e) => {
const newInventory = {...inventory, weapon: e.target.value};
setInventory(newInventory);
onSubmitInventory(newInventory);
};
If you have to wait for the next render to call onSubmitInventory, it should be a useEffect in the parent. My next question would be why the parent needs the state and the child also has it as a state? Possibly it should be lifted up. But I'm not sure of that without seeing the parent.
Edit: Is your problem just with the console.log? If you run
const [state, setState] = useState(true);
// ...
setState(false);
console.log(state); // outputs true.
The value of state does not change until the next render. Calling setState will cause a new render, but until that happens, the old value is there. Add a console.log of state in the parent just in the body like here
const [inventory, setInventory] = useState({
water: 0,
soup: 0,
meds: 0,
weapon: 0,
});
console.log('I just rendered, inventory: ', inventory);
You'll see it updates!
The fact that the value of state does not change can bite you in this case:
const [state, setState] = useState(true); // closes over this state!
// ...
setState(!state); // closes over the state value above
setState(!state); // closes over the same state value above
// state will be false on next render
So I use this form if there is any possibility I call setState twice before a render:
const [state, setState] = useState(true);
// ...
setState(state => !state);
setState(state => !state);
// state will be true on next render
Inside your parent component's onSubmitInventory you have named the argument inventory but that already exists in the parent's scope. I suggest renaming that newInventory to be clear about which you are referencing.
Also, it seems like you are keeping track of inventory in both the parent and child's state. Keep it in the parent and pass it down to the child as a prop/props.

React - Material-UI - How to use Select with multiple values inside react-hook-form

I am trying to use UI-Material Select with multiple options inside react-hook-form without success.
I had this working before trying to do it with multiple options
<form onSubmit={handleSubmit(onSubmit)}>
<Row className="mb-2">
<Col sm={6}>
<FormControl className="select-language">
<InputLabel> {t('Languages')}</InputLabel>
<Controller
as={
<Select>
{Config.languages.map(m => <MenuItem key={m.code} value={m.text}> {t(m.text)} </MenuItem>)}
</Select>
}
defaultValue={user.language}
name="language"
control={control}
>
</Controller>
</FormControl>
</Col>
</Row>
</form>
I tried to add multiple to the Select element which lead me to another error.
I tried also to keep only the Select element without the Controller wrapper, but then I can't get the language value in onSubmit
Very simple codeSandBox that shows that I can't get value from Select when submitting the form: https://codesandbox.io/s/react-hook-form-example-s7h5p?file=/src/index.js
I would appreciate any help
Thanks
If anyone looking for a easy solution, this might come in handy. Multiple select options become very easy now with Select Component. If you look at the Select component, you just have to set the default value to an array and pass the "multiple" prop.
import {
Button,
FormControl,
InputLabel,
MenuItem,
Select
} from "#mui/material";
import { Controller, useForm } from "react-hook-form";
const FCWidth = {
width: "20rem"
};
export default function App() {
const { control, handleSubmit } = useForm();
const formSubmitHandler = (formData) => {
console.log(formData);
};
const ages = ["10", "20", "30"];
return (
<div className="App">
<form onSubmit={handleSubmit(formSubmitHandler)}>
<Controller
name="age"
control={control}
type="text"
defaultValue={[]}
render={({ field }) => (
<FormControl sx={FCWidth}>
<InputLabel id="age">Age</InputLabel>
<Select
{...field}
labelId="age"
label="age"
multiple
defaultValue={[]}
>
{ages.map((age) => (
<MenuItem value={age} key={age}>
{age}
</MenuItem>
))}
</Select>
</FormControl>
)}
/>
<FormControl sx={FCWidth}>
<Button
type="submit"
variant="contained"
fullWidth
sx={{ marginTop: ".75rem", fontWeight: "bold" }}
>
Submit
</Button>
</FormControl>
</form>
</div>
);
}
Here is the code sandbox link https://codesandbox.io/s/select-multiple-option-with-mui-and-react-hook-form-2kv2o
I know that this old but someone may need it later, maybe this one would work for you,
just check the render Controller props
import React from "react";
import {
TextField,
ListItemText,
Checkbox,
MenuItem,
makeStyles
} from "#material-ui/core";
import { useForm, Controller } from "react-hook-form";
const useStyles = makeStyles({
root: {
width: "200px"
},
center: {
textAlign: "center"
}
});
export default function SelectTextField() {
const classes = useStyles();
const { handleSubmit, control } = useForm();
const nums = [1, 2, 3, 4];
const onSubmit = (e) => {
console.log(e);
};
return (
<form className={classes.center} onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="selectedNumbers"
defaultValue={[]}
render={({ onChange, value }) => {
console.log(value);
return (
<TextField
classes={{ root: classes.root }}
select
id="Numbers"
variant="outlined"
label="Numbers"
SelectProps={{
multiple: true,
value: value,
renderValue: (selected) => selected.join(", "),
onChange: onChange
}}
>
{nums.map((n) => (
<MenuItem key={n} value={n}>
<Checkbox checked={value.includes(n)} />
<ListItemText primary={n} />
</MenuItem>
))}
</TextField>
);
}}
/>
<div>
<button type="submit">Submit</button>
</div>
</form>
);
}
also, you can and play with it here
https://codesandbox.io/s/multi-select-input-u0cr3?file=/demo.js
also can check this on the docs
https://react-hook-form.com/get-started#ReactNative
First of all, i think your version is on v3, so you may want to upgrade:
https://codesandbox.io/s/react-hook-form-example-5lrij?file=/src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { useForm, Controller } from "react-hook-form";
import { Select, MenuItem } from "#material-ui/core";
import "./styles.css";
const languages = [
{
code: "en-US",
text: "English"
},
{
code: "zu",
text: "Another Language"
}
];
export default function App() {
const { register, handleSubmit, control } = useForm();
const onSubmit = data => console.log(data);
return (
<div className="container">
<div className="col-sm-12">
<h3>Client Profile</h3>
</div>
<div className="col-sm-12">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-group">
<input
className="form-control"
type="text"
placeholder="Name"
name="Name"
ref={register}
/>
</div>
<div className="form-group">
<Controller
name="Language"
control={control}
as={
<Select>
{languages.map(m => (
<MenuItem key={m.code} value={m.text}>
{" "}
{m.text}{" "}
</MenuItem>
))}
</Select>
}
defaultValue={languages[0].text}
/>
</div>
<div className="form-group">
<input className="btn btn-primary" type="submit" />
</div>
</form>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
here is the fixed version by using Controller

React - Infinite loop when changing state from onChange

I have a component with a form inside. The fields of the form rely on the state of the component that is updated within the onChange methods. Upon clicking the submit button, the component sends the form fields to the parent component, then updates the store. When the onChange method is called, the error "Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."
I can't find the reason why a loop occurs inside the "MemberForm" component.
By debugging, however, I understood that at the change of state (not to the onChange function) the parent component "AggiungiSocio" is also rendered. However, the status of the "MemberForm" component is in no way related to the "AggiungiSocio" component.
What is causing this unexpected rendering? How can I avoid it?
The strange thing is that I also use the "MemberForm" component in the "ModificaSocio" component where I pass the form fields as properties. In this component "ModificaSocio" everything works correctly.
To recap
AddSocio -> MemberForm doesn't work
EditSocio -> MemberForm works
AggiungiSocio
import React, { Component, Fragment } from "react";
import {
Row,
Card,
CardBody,
CardTitle,
CardSubtitle,
} from "reactstrap";
import IntlMessages from "../../../helpers/IntlMessages";
import { Colxx } from "../../../components/common/CustomBootstrap";
import MemberForm from "../../../containers/form/MemberForm";
import {injectIntl} from "react-intl";
import {connect} from "react-redux";
import {addMemberDetail} from "../../../redux/memberDetail/actions";
import {Socio} from "../../../models/socio";
class AggiungiSocio extends Component {
constructor(props) {
super(props);
}
aggiungiSocio = (socio) => {
console.log(socio);
this.props.addMemberDetail(socio);
};
render() {
console.log("render aggiungi socio");
const {
loading
} = this.props.memberDetail;
return (
<Fragment>
<Row className="mb-4">
<Colxx xxs="12">
<Card>
<CardBody>
<CardTitle>
<IntlMessages id="pages.member.add-member"/>
</CardTitle>
<CardSubtitle>
<IntlMessages id="pages.member.add-member-description"/>
</CardSubtitle>
{loading ? (
<MemberForm onSubmit={this.aggiungiSocio}/>
) : (
<div className="loading"/>
)
}
</CardBody>
</Card>
</Colxx>
</Row>
</Fragment>
)
}
}
const mapStateToProps = ({memberDetail}) => {
return {
memberDetail
};
};
export default injectIntl(
connect(
mapStateToProps,
{
addMemberDetail
}
)(AggiungiSocio)
);
ModificaSocio
import React, { Component, Fragment } from "react";
import {
Row,
Card,
CardBody,
CardTitle
} from "reactstrap";
import IntlMessages from "../../../helpers/IntlMessages";
import { Colxx } from "../../../components/common/CustomBootstrap";
import "react-datepicker/dist/react-datepicker.css";
import MemberForm from "../../../containers/form/MemberForm";
import {injectIntl} from "react-intl";
import {connect} from "react-redux";
import {getMemberDetail, editMemberDetail} from "../../../redux/memberDetail/actions";
class ModificaSocio extends Component {
constructor(props) {
super(props);
this.state = {
};
}
modificaSocio = (socio) => {
this.props.editMemberDetail(socio);
}
render() {
const {
member,
loading
} = this.props.memberDetail;
return (
<Fragment>
<Row className="mb-4">
<Colxx xxs="12">
<Card>
<CardBody>
<CardTitle>
<IntlMessages id="pages.member.edit-member"/>
</CardTitle>
{loading ? (
<MemberForm member={member} onSubmit={this.modificaSocio}/>
): (
<div className="loading" />
)
}
</CardBody>
</Card>
</Colxx>
</Row>
</Fragment>
)
}
}
const mapStateToProps = ({memberDetail}) => {
return {
memberDetail
};
};
export default injectIntl(
connect(
mapStateToProps,
{
editMemberDetail,
getMemberDetail
}
)(ModificaSocio)
);
MemberForm
import React, { Component } from "react";
import {
Input,
FormGroup,
Label,
Button,
Form
} from "reactstrap";
import IntlMessages from "../../helpers/IntlMessages";
import DatePicker from "react-datepicker";
import "react-tagsinput/react-tagsinput.css";
import "react-datepicker/dist/react-datepicker.css";
import "rc-switch/assets/index.css";
import "rc-slider/assets/index.css";
import "react-rater/lib/react-rater.css";
import { Colxx } from "../../components/common/CustomBootstrap";
import Select from "react-select";
import CustomSelectInput from "../../components/common/CustomSelectInput";
import moment from "moment";
import {Socio} from "../../models/socio";
class MemberForm extends Component {
constructor(props) {
super(props);
moment.locale("it");
this.state = {
member: (typeof this.props.member === 'undefined') ? Socio : this.props.member,
selectQualifica: [{
label: (typeof this.props.member === 'undefined') ? '' : this.props.member.qualifica,
value: (typeof this.props.member === 'undefined') ? '' : this.props.member.qualifica
}],
selectTitolo: [{
label: (typeof this.props.member === 'undefined') ? '' : this.props.member.titolo,
value: (typeof this.props.member === 'undefined') ? '' : this.props.member.titolo
}],
dataDiNascita: moment((typeof this.props.member === 'undefined') ? null : this.props.member.dataDiNascita, "DD/MM/YYYY"),
dataRichiestaIscrizione: moment((typeof this.props.member === 'undefined') ? null : this.props.member.dataRichiestaIscrizione, "DD/MM/YYYY"),
dataAccettazione: moment((typeof this.props.member === 'undefined') ? null : this.props.member.dataAccettazione, "DD/MM/YYYY")
}
}
handleChangeQualifica = qualifica => {
this.setState({
member: {
...this.state.member,
qualifica: qualifica.value
},
selectQualifica: [{
label: qualifica.value,
value: qualifica.value
}]
});
}
handleChangeTitolo = titolo => {
this.setState({
member: {
...this.state.member,
titolo: titolo.value
},
selectTitolo: [{
label: titolo.value,
value: titolo.value
}]
});
}
handlerChange = event => {
const name = event.target.name;
const value = event.target.value;
if (value!=this.state.member[name]) {
console.log("il valore è cambiato, aggiorno stato");
console.log(value, this.state.member[name]);
this.setState({
member: {
...this.state.member,
[name]: value
}
});
} else {
console.log("il valore è lo stesso!, non aggiorno stato");
console.log(value, this.state.member[name]);
}
}
handleChangeDataNascita = date => {
this.setState({
member: {
...this.state.member,
dataDiNascita: date.format("DD/MM/YYYY")
},
dataDiNascita: date
});
};
handleChangeDataRichiestaIscrizione = date => {
this.setState({
member: {
...this.state.member,
dataRichiestaIscrizione: date.format("DD/MM/YYYY")
},
dataRichiestaIscrizione: date
});
};
handleChangeDataAccettazione = date => {
this.setState({
member: {
...this.state.member,
dataAccettazione: date.format("DD/MM/YYYY")
},
dataAccettazione: date
});
};
handleSubmit = () => {
this.props.onSubmit(this.state.member);
}
render () {
console.log("render memberform");
const {
member,
selectQualifica,
selectTitolo,
dataDiNascita,
dataRichiestaIscrizione,
dataAccettazione
} = this.state;
return (
(member &&
<Form>
<FormGroup row>
<Colxx sm={2}>
<FormGroup>
<Label for="memberTitle">
<IntlMessages id="member.idSocio"/>
</Label>
<Input
disabled
readOnly
type="text"
name="memberId"
id="memberId"
defaultValue={member.numeroSocio}
/>
</FormGroup>
</Colxx>
<Colxx sm={2}>
<FormGroup>
<Label for="memberTitle">
<IntlMessages id="member.title"/>
</Label>
<Select
components={{Input: CustomSelectInput}}
className="react-select"
classNamePrefix="react-select"
name="form-field-name"
id="memberTitle"
options={[
{label: "Sig", value: "Sig", key: 0},
{label: "Sig.ra", value: "Sig.ra", key: 1}
]}
value={selectTitolo}
onChange={this.handleChangeTitolo.bind(this)}
/>
</FormGroup>
</Colxx>
<Colxx sm={3}>
<Label for="memberName">
<IntlMessages id="member.name"/>
</Label>
<Input
type="text"
name="nome"
id="memberName"
value={member.nome}
onChange={this.handlerChange.bind(this)}
/>
</Colxx>
<Colxx sm={3}>
<Label for="memberSurname">
<IntlMessages id="member.surname"/>
</Label>
<Input
type="text"
name="cognome"
id="memberSurname"
value={member.cognome}
onChange={this.handlerChange.bind(this)}
/>
</Colxx>
<Colxx sm={2}>
<Label for="memberSurname">
<IntlMessages id="member.birthday"/>
</Label>
<DatePicker
dateFormat="DD/MM/YYYY"
selected={dataDiNascita}
onChange={this.handleChangeDataNascita.bind(this)}
id="memberBirthday"/>
</Colxx>
</FormGroup>
<FormGroup row>
<Colxx sm={3}>
<FormGroup>
<Label for="birth-comune">
<IntlMessages id="member.birth-comune"/>
</Label>
<Input
type="text"
name="comuneDiNascita"
id="birth-comune"
onChange={this.handlerChange.bind(this)}
value={member.comuneDiNascita}
/>
</FormGroup>
</Colxx>
<Colxx sm={2}>
<FormGroup>
<Label for="birth-provincia">
<IntlMessages id="member.birth-provincia"/>
</Label>
<Input
type="text"
name="provinciaDiNascita"
id="birth-provincia"
onChange={this.handlerChange.bind(this)}
value={member.provinciaDiNascita}
/>
</FormGroup>
</Colxx>
<Colxx sm={4}>
<FormGroup>
<Label for="residential-address">
<IntlMessages id="member.residential-address"/>
</Label>
<Input
type="text"
name="indirizzoDiResidenza"
id="residential-address"
onChange={this.handlerChange.bind(this)}
value={member.indirizzoDiResidenza}
/>
</FormGroup>
</Colxx>
<Colxx sm={3}>
<FormGroup>
<Label for="codice-fiscale">
<IntlMessages id="member.codice-fiscale"/>
</Label>
<Input
type="text"
name="codiceFiscale"
id="codice-fiscale"
onChange={this.handlerChange.bind(this)}
value={member.codiceFiscale}
/>
</FormGroup>
</Colxx>
</FormGroup>
<FormGroup row>
<Colxx sm={3}>
<FormGroup>
<Label for="data-richiesta-iscrizione">
<IntlMessages id="member.data-richiesta-iscrizione"/>
</Label>
<DatePicker
dateFormat="DD/MM/YYYY"
selected={dataRichiestaIscrizione}
onChange={this.handleChangeDataRichiestaIscrizione.bind(this)}
id="data-richiesta-iscrizione"/>
</FormGroup>
</Colxx>
<Colxx sm={3}>
<FormGroup>
<Label for="data-accettazione">
<IntlMessages id="member.data-accettazione"/>
</Label>
<DatePicker
dateFormat="DD/MM/YYYY"
selected={dataAccettazione}
onChange={this.handleChangeDataAccettazione.bind(this)}
id="data-accettazione"/>
</FormGroup>
</Colxx>
<Colxx sm={3}>
<FormGroup>
<Label for="payment">
<IntlMessages id="member.payment"/>
</Label>
<Input
type="text"
name="quotaVersata"
id="payment"
onChange={this.handlerChange.bind(this)}
value={member.quotaVersata}
/>
</FormGroup>
</Colxx>
<Colxx sm={3}>
<FormGroup>
<Label for="qualification">
<IntlMessages id="member.qualification"/>
</Label>
<Select
components={{Input: CustomSelectInput}}
className="react-select"
classNamePrefix="react-select"
name="form-field-name"
id="qualification"
options={[
{label: "Socio", value: "Socio", key: 0},
{label: "Socio Fondatore", value: "Socio Fondatore", key: 1},
{label: "Consigliere", value: "Consigliere", key: 2}
]}
value={selectQualifica}
onChange={this.handleChangeQualifica}
/>
</FormGroup>
</Colxx>
</FormGroup>
<Button color="primary" onClick={this.handleSubmit.bind(this)}>
<IntlMessages id="pages.member.save"/>
</Button>
</Form>
)
)
}
}
export default React.memo(MemberForm);
You can't pass params to a function outside a function.
Correct:
< ... onChange={function} />
Not correct:
< ... onChange={function(params)} />
To use params you need to nest you function inside anonymous function :
< ... onChange={() => function(params)} />
In you case you want to write this :
< ... onChange={() => this.handlerChange.bind(this)} />

react-bootstrap - login form - Uncaught TypeError

I have the following login.js
import React, { Component, PropTypes } from 'react';
import { Button, Form, FormControl, FormGroup } from 'react-bootstrap';
export default class Login extends Component {
render() {
const {errorMessage} = this.props
return (
<Form inline>
<FormGroup controlId="formInlineEmail">
<FormControl type="email" ref="username" placeholder="Email"/>
</FormGroup>
<FormGroup controlId="formInlinePassword">
<FormControl type="password" ref="password"/>
</FormGroup>
<Button type="submit" onClick={(event) => this.handleClick(event)}>
Login
</Button>
{errorMessage &&
<p style={{color:'red'}}>{errorMessage}</p>
}
</Form>
)
}
handleClick(event) {
const username = this.refs.username
const password = this.refs.password
const creds = { username: username.value.trim(), password: password.value.trim() }
this.props.onLoginClick(creds)
}
}
Login.propTypes = {
onLoginClick: PropTypes.func.isRequired,
errorMessage: PropTypes.string
}
When attempting to login I get the following error:
Uncaught TypeError: (0, _reactDom2.default) is not a function at Login.handleClick
if I avoid using react-bootstrap and do the following in my Login.js it works fine.
<div>
<input type='text' ref='username' className="form-control" style={{ marginRight: '5px' }} placeholder='Username'/>
<input type='password' ref='password' className="form-control" style={{ marginRight: '5px' }} placeholder='Password'/>
<button onClick={(event) => this.handleClick(event)} className="btn btn-primary">
Login
</button>
{errorMessage &&
<p style={{color:'red'}}>{errorMessage}</p>
}
</div>
Don't ask why but this works:
import React, { Component, PropTypes } from 'react';
import { findDOMNode } from 'react-dom';
import { Button, ControlLabel, Form, FormControl, FormGroup } from 'react-bootstrap';
export default class Login extends Component {
handleSubmit(event) {
const username = findDOMNode(this.refs.username)
const password = findDOMNode(this.refs.password)
const creds = { username: username.value.trim(), password: password.value.trim() }
this.props.onLoginClick(creds)
}
render() {
const {errorMessage} = this.props
return (
<Form inline>
<FormGroup controlId="formHorizontalEmail">
<ControlLabel>Email </ControlLabel>
<FormControl type="username" ref="username" onChange={this.handleChange} placeholder="Email" />
</FormGroup>
<FormGroup controlId="formHorizontalPassword">
<ControlLabel>Password </ControlLabel>
<FormControl type="password" ref="password" onChange={this.handleChange} placeholder="Password" />
</FormGroup>
<Button onClick={(event) => this.handleSubmit(event)}>Login</Button>
{errorMessage &&
<p style={{color:'red'}}>{errorMessage}</p>
}
</Form>
)
}
}
Login.propTypes = {
onLoginClick: PropTypes.func.isRequired,
errorMessage: PropTypes.string
}

Resources