In my code, was trying to render a <DatePicker> in <Field> component of Redux Form, via a function called renderDatePicker(). This function is linked to handleClick() function where the state variable isOpen is set to true.
So ideally, onClick should render the <DatePicker> as it is set to visible. But code doesn't update anything. Where am I doing wrong here?
Note: Rendering <DatePicker> alone directly without the help of <Field component=...>, works fine.
For debugging, complete code is in CodeSandbox,
SimpleForm.js
import React from "react";
import { reduxForm } from "redux-form";
import { Field } from "redux-form";
import DatePicker from "react-mobile-datepicker";
class SimpleForm extends React.Component {
constructor(props) {
super(props);
this.state = {
time: new Date(),
isOpen: false
};
this.handleClick = this.handleClick.bind(this);
}
renderDatePicker = () => (
<DatePicker
value={this.state.time}
isOpen={this.state.isOpen}
onSelect={this.handleSelect}
onCancel={this.handleCancel}
/>
);
handleClick() {
this.setState({ isOpen: true }, function() {
console.log(this.state.isOpen);
});
}
handleCancel = () => {
this.setState({ isOpen: false });
};
handleSelect = time => {
this.setState({ time, isOpen: false });
};
render() {
return (
<div>
<button className="select-btn" onClick={this.handleClick}>
select time
</button>
<Field
name="date"
type="date"
component={this.renderDatePicker}
label={"date"}
/>
</div>
);
}
}
export default reduxForm({
form: "simple" // a unique identifier for this form
})(SimpleForm);
The reason for this behavior lies in the implementation of react-form's Field component. It does a shallow compare of all its properties to decide whether it should rerender. You can change your component's state as much as you like, the reference to this.renderDatePicker won't change.
Field passes properties including an onChange handler and the current value into the field's component stateless function call to notify of changes, but this doesn't really apply here because your toggle button is outside of the field.
So one option that comes to my mind is to move your button into the rendered field and then call onChange(!value).
The easier yet dirtier option would be to use an arrow function in your component property: component={() => this.renderDatePicker()} - this instance changes with every re-render of your SimpleForm (i.e. if the state changes), so it comes with a cost, but depending on the complexity of your application the cost is negligible. To mitigate the impact, you could implement shouldComponentUpdate (just like redux-form's Field does) to decide whether it should rerender or not, based on the current and next isOpen state.
Check this bit in redux-form for more details: https://github.com/erikras/redux-form/blob/master/src/createField.js#L44
Related
I made a custom component called TagsInput, and added it to a form. My problem is, when the form is submitted, the value for field tags is always undefined, because the Form.Item cannot retrieve the value from the TagsInput child. This is usually not a problem when children of Form.Item are antd components.
export default function NewProjectForm(props) {
return (
<Form layout="vertical" onFinish={props.onSubmit} id="newProjectForm">
<Form.Item label="Tags" name="tags">
<TagsInput />
</Form.Item>
</Form>
);
}
TagsInput is a class component that has its value within its state:
class TagsInput extends React.Component{
constructor(props){
super(props);
this.state = {
value: []
}
}
render(){
return(<div></div>)
}
}
I dont want to elevate the value field from TagsInput state to NewProjectForm because it is not a class component... I would be doing it just for the sake of solving this issue. I am asking this question because I think there is a cleaner way of solving this.
How can I make NewProjectForm retrieve the value of TagsInput from within its state?
Is there any function I can pass to TagsInput as a property, so that the form asks the component for a value when it is submitted?
In order to achive what you want, you need to handle the state of the form (and its components) at the parent component (NewProjectForm).
If you are willing to keep that component a function component, you can use React Hooks and define a state with useState like this:
const [tagValue, setTagValue] = useState([]);
While tagValue is the value you will get from the child TagsInput, and the setTagValue is the function that can set that value. You need to pass setTagValue as a function prop to TagsInput and use it on change. It will look something like this:
export default function NewProjectForm(props) {
const [tagValue, setTagValue] = useState([]);
return (
<Form layout="vertical" onFinish={props.onSubmit} id="newProjectForm">
<Form.Item label="Tags" name="tags">
<TagsInput setTagValue={(val)=>setTagValue(val)} />
</Form.Item>
</Form>
);
}
class TagsInput extends React.Component{
constructor(props){
super(props);
this.state = {
value: []
}
}
.........
.........
onChangeInput(val){
this.props.setTagValue(val);
}
.........
.........
render(){
return(<div></div>)
}
}
EDIT
After the clarification in the comments, you actually need to follow the steps of using Customized Form Controls, As described here.
You need to pass an object with value and onChange that changes the value. Then, the Form component should manage to get that value automatically.
So I guess it should look something like that:
const TagsInput= ({ value = {}, onChange }) => {
const triggerChange = newValue => {
if (onChange) {
onChange(newValue);
}
};
return(<div></div>)
};
I have this container where and is not placed in the same level. How can I get the state of the Form when I click on the button (which is placed on the parent) ?
I've created a demo to address my issue.
https://codesandbox.io/s/kmqw47p8x7
class App extends React.Component {
constructor(props) {
super(props);
}
save = () => {
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form />
<button onClick={this.save}>save</button>
</div>
);
}
}
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
To access a child instance from parent, your need to know about ref:
First, add formRef at top your App class:
formRef = React.createRef();
Then in App render, pass ref prop to your Form tag:
<Form ref={this.formRef} />
Finaly, get state from child form:
save = () => {
alert("how to get state of Form?");
const form = this.formRef.current;
console.log(form.state)
};
Checkout demo here
ideally, your form submit action belongs to the Form component
You can put button inside your From component and pass a submit callback to the form.
class App extends React.Component {
constructor(props) {
super(props);
}
save = (data) => {
// data is passed by Form component
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form onFormSubmit={this.save} />
</div>
);
}
}
you can write the code like this
https://codesandbox.io/s/23o469kyx0
As it was mentioned, a ref can be used to get stateful component instance and access the state, but this breaks encapsulation:
<Form ref={this.formRef}/>
A more preferable way is to refactor Form to handle this case, i.e. accept onChange callback prop that would be triggered on form state changes:
<Form onChange={this.onFormChange}/>
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
Forms will need to handle this any way; it would be impossible to reach nested form with a ref from a grandparent. This could be the case for lifting the state up.
E.g. in parent component:
state = {
formState: {}
};
onFormChange = (formState) => {
this.setState(state => ({
formState: { ...state.formState, ...formState }
}));
}
render() {
return (
<Form state={this.state.formState} onChange={this.onFormChange} />
);
}
In form component:
handleChange = e =>
this.props.onChange({
[e.target.name]: e.target.value
});
render() {
return (
<input
onChange={this.handleChange}
name="firstName"
value={this.props.state.firstName}
/>
);
}
Here is a demo.
I have a state like this :
{
textfield: '',
data: [] //huge, used to render elements within the render()
}
When I want to update the textfield value (simple text input), I use this.setState({ textfield: newValue });. The problem is that there is some lag when I write a character in the field because it is re-rendering everything.
Is using shouldComponentUpdate() and deeply check my data object the only way to avoid re-rendering everything? Or is there a better/more efficient way?
Thanks
Am guessing its rerendering the entire component due to the state change on every key.
you could isolate your input element in a separate stateful component, hence only triggering a re-render on itself and not on your entire app.
So something like:
class App extends Component {
render() {
return (
<div>
...
<MyInput />
...
</div>
);
}
}
class MyInput extends Component {
constructor() {
super();
this.state = {textfield: ""};
}
update = (e) => {
this.setState({textfield: e.target.value});
}
render() {
return (
<input onChange={this.update} value={this.state.textfield} />
);
}
}
I use Redux Form Version 6.8.0.
I have a form component which get's it's "initialValues" via a "mapStateToProps" function. On the first render everything works quite fine.
Following my form config:
const CategoryChangeForm = reduxForm({
form: 'change-category',
validate: newCategoryValidate,
enableReinitialize: true
})(CategoryChange);
When i change the Field "category" and the update succeed, i receive the new updated value from firebase.
I pass this updated value via the mentioned "mapStateToProps" function to the "initialValues":
function mapStateToProps(state, ownProps) {
return {
initialValues: { category: state.categories[ownProps.id].name, id: ownProps.id }
};
}
I expected that the new Value would be applied to the "category"-Field Component. But it doesn't get the updated value.
The config of my "Field" Components:
const fieldProps = {
category: {
name: 'category',
type: 'text',
placeholder: 'Bezeichnung',
component: InputField
},
hidden: {
name: 'id',
type: 'hidden',
component: 'input'
}
};
and here is my Form Component:
export const CategoryChange = props => {
const {
color,
message,
handleSubmit,
stateComponent
} = props;
console.log("Props: ", props);
return (
<span>
<Subline color={ color } marginTop={ 70 } marginBottom={ 30 } align="center">{ message }</Subline>
<form>
<Field { ...fieldProps.category } />
<Field { ...fieldProps.hidden } />
<Button onClick={ handleSubmit(changeCategory.bind(stateComponent)) }>Ändern</Button>
<Button marginBottom={ 5 } marginTop={ 10 }>Löschen</Button>
</form>
</span>
);
}
I can observe that after an update, my form component rerenders 2 times. First time its prop "initialized" is set to "true". But the second time its set to "false".
The second render occurs due to a stateChange of the hoc component which wrapped my form component. The "setState" for the hoc is triggered when the update was successful and show an appropriate message to the user.
But cause of the second render the form component did not initialize.
If you need any more code to see, let me know.
Hope someone has a hint to solve this problem...
According to the docs:
By default, you may only initialize a form component once via
initialValues. There are two methods to reinitialize the form
component with new "pristine" values:
Pass a enableReinitialize prop or reduxForm() config parameter set to
true to allow the form the reinitialize with new "pristine" values
every time the initialValues prop changes. To keep dirty form values
when it reinitializes, you can set keepDirtyOnReinitialize to true. By
default, reinitializing the form replaces all dirty values with
"pristine" values.
Dispatch the INITIALIZE action (using the action creator provided by
redux-form).
You can change CategoryChangeForm from function to class and call initialize action from redux-forms in the componentWillReceiveProps method.
import {initialize} from 'redux-form'
CategoryChangeForm extends Component {
...
componentWillReceiveProps(nextProps) {
// check the props and call initialize when needed
}
}
mapDispatchToProps = dispatch => ({
initialize: (data) => initialize('change-category', data)
})
How can I get the value of datapicker in react toobox?
I am using custom components.
I am using 2 components first one is called InputDateCustom.js with the code below:
import DatePicker from 'react-toolbox/lib/date_picker/DatePicker';
import React, { Component } from 'react';
const datetime = new Date(2015, 10, 16);
datetime.setHours(17);
datetime.setMinutes(28);
export default class InputDateCustomizado extends Component{
state = {date2: datetime};
handleChange = (item, value) => {
console.log(item+" - "+value)
this.setState({...this.state, [item]: value});
};
render() {
return (
<div>
<DatePicker
label={this.props.label}
locale={localeExample}
name={this.props.name}
required={this.props.required}
onChange={this.handleChange.bind(this, 'date1')}
value={this.state.date1}
/>
</div>
);
}
}
Another component is called Cadastro.js that contains the following logic:
constructor(props) {
super(props);
this.state = {msg: '', fim_vigencia:'', nome:''}
this.setNome = this.setNome.bind(this)
this.setFimVigencia = this.setFimVigencia.bind(this)
}
setFimVigencia(evento){
console.log("date")
this.setState({fim_vigencia:evento.target.value});
}
InputDateCustomizado
id="fim_vigencia"
label="Fim"
name="fim_vigencia"
value = {this.state.fim_vigencia}
onSubmit = {this.setFimVigencia}
/>
Get the value in an onChange event or using the value prop. Doc examples: http://react-toolbox.com/#/components/date_picker
<DatePicker label='Birthdate' onChange={this.handleChange.bind(this, 'date1')} value={this.state.date1} />
You can get access to the value in the handleChange event allowing you to update your state with the currently selected date.
EDIT: Ah okay I think I understand what you are asking now. You have wrapped DatePicker with your own component and now you want to get the DatePicker value through the Cadastro.js component.
You need to create a method in the Cadastro.js that accepts state changes from the InputDateCustomizado component and then pass that function as a prop to the InputDateCustomizado component. In the InputDateCustomizado when the state changes, call the passed in function and it should update the state in the parent component. Then you will always have the datepicker value in the parent component.
It looks like you are almost there. You need to add an updateState function to the Cadastro.js component. In the InputDateCustomizado component handleChange event, you need to call this.props.updateState and pass in the new value.
In Cadastro.js
updateState = (data) => {
this.setState({
date: data.data //set your state here to the date
})
}
In InputDateCustomizado
handleChange = (item, value) => {
console.log(item+" - "+value)
this.setState({...this.state, [item]: value});
this.props.updateState(this.state);
};