How can I use props to auto-populate editable redux-form fields in React? - forms

I'm new to React so I've tried to show as much code as possible here to hopefully figure this out! Basically I just want to fill form fields with properties from an object that I fetched from another API. The object is stored in the autoFill reducer. For example, I would like to fill an input with autoFill.volumeInfo.title, where the user can change the value before submitting if they want.
I used mapDispatchtoProps from the autoFill action creator, but this.props.autoFill is still appearing as undefined in the FillForm component. I'm also confused about how to then use props again to submit the form. Thanks!
My reducer:
import { AUTO_FILL } from '../actions/index';
export default function(state = null, action) {
switch(action.type) {
case AUTO_FILL:
return action.payload;
}
return state;
}
Action creator:
export const AUTO_FILL = 'AUTO_FILL';
export function autoFill(data) {
return {
type: AUTO_FILL,
payload: data
}
}
Calling the autoFill action creator:
class SelectBook extends Component {
render() {
return (
....
<button
className="btn btn-primary"
onClick={() => this.props.autoFill(this.props.result)}>
Next
</button>
);
}
}
....
function mapDispatchToProps(dispatch) {
return bindActionCreators({ autoFill }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SelectBook);
And here is the actual Form where the issues lie:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { createBook } from '../actions/index;
class FillForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
onSubmit(props) {
this.props.createBook(props)
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { fields: { title }, handleSubmit } = this.props;
return (
<form {...initialValues} onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<input type="text" className="form-control" name="title" {...title} />
<button type="submit">Submit</button>
</form>
)
}
export default reduxForm({
form: 'AutoForm',
fields: ['title']
},
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}), {createBook})(FillForm)

I think you're mixing up connect and reduxForm decorators in the actual form component. Currently your code looks like this (annotations added by me):
export default reduxForm({
// redux form options
form: 'AutoForm',
fields: ['title']
},
// is this supposed to be mapStateToProps?
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}),
/// and is this mapDispatchToProps?
{createBook})(FillForm)
If this is the case, then the fix should be as simple as using the connect decorator as it should be (I also recommend separating this connect props to their own variables to minimize confusions like this):
const mapStateToProps = state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
})
const mapDispatchToProps = { createBook }
export default connect(mapStateToProps, mapDispatchToProps)(
reduxForm({ form: 'AutoForm', fields: ['title'] })(FillForm)
)
Hope this helps!

Related

Reactjs how to get value from selected element

so I have this code for posting to my backend API. Normal form perfectly fine; I managed to post to my database. So I add a Cascader from Ant Design CSS Framework, and every time I selected the value, it produced an error
TypeError: Cannot read property 'value' of undefined
Here is the code:
import React from 'react';
import axios from 'axios';
import { Button, Cascader, Form, Input, Modal } from 'antd';
const FormProduct = Form.Item;
const computerType = [
{
value: 'computer',
label: 'Computer',
},
{
value: 'laptop',
label: 'Laptop',
}
]
export default class FormInventory extends React.Component {
state = {
category: '',
productname: '',
};
handleCategoryChange = event => { this.setState({ category: event.target.value }) }
handleProductNameChange = event => { this.setState({ productname: event.target.value }) }
handleSubmit = event => {
event.preventDefault();
axios.post('myapi',
{
category: this.state.category,
productname: this.state.productname,
})
.then(
function success() {
const modal = Modal.success({
title: 'Success',
content: 'Data successfully add',
});
setTimeout(() => modal.destroy(), 2000);
}
)
}
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormProduct {...formProductLayout} label="Computer Category">
<Cascader options={computerType} category={this.state.value} onChange={this.handleCategoryChange} />
</FormProduct>
<FormProduct {...formProductLayout} label="Product Name">
<Input type="text" productname={this.state.productname} onChange={this.handleProductNameChange} />
</FormProduct>
<FormProduct wrapperCol={{ span: 12, offset: 2 }}>
<Button type="primary" htmlType="submit">
Add Item
</Button>
</FormProduct>
</Form>
)
}
}
You need to either bind your event handlers in the constructor or use arrow function.
Option 1: Bind
constructor(props) {
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
Option 2: Arrow function
<Input onChange={(e) => this.handleChange(e)} />
According to antd docs you don't need event.target.
https://ant.design/components/cascader/
handleCategoryChange = category => { this.setState({ category }) }
The code above will work fine.

On refresh react application I need to get from componentWillReceiveProps values for input text

I have the following problem and I really need help on that.
export class DeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
let data = this.props.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
componentWillMount() {
let data = this.props.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
componentWillReceiveProps(newProps) {
let data = newProps.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { error } = this.props;
return (
<FormLabel>Internal ID</FormLabel>
<input type="text" defaultValue={this.state.value} onChange= .
{this.handleChange} />
</Form.Label>)
}
}
So what I want is that when I refresh the page, I want to get the the this.state.value on my input.. which in this case I am not able to do that. So I would like to know what I am doing wrong here. If I set it on value on the input I did get what I want, but then I have an warning like that:
A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
What can I do?
Actually, you should handle changes there and you can use just value on the input field instead of defaultValue.
For example:
export class AdminDeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: '',
// if it comes from props by default
// you can use, if not just leave as it is
value: props.value
};
}
handleChange = e => {
this.setState({value: e.target.value});
}
render() {
const { error } = this.props;
return (
<form>
<FormLabel>Internal ID</FormLabel>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</form>
)
}
}
Hope it will helps.
So from what I understand you want to make controlled input but use props.value as a default value. What if you do:
export class AdminDeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: props.value,
};
this.handleChange = this.handleChange.bind(this);
}
componentWillReceiveProps(newProps) {
if(this.props.value !== newProps.value) {
this.setState({ value: newProps.value }) // reset input value
}
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { error } = this.props;
return (
<FormLabel>Internal ID</FormLabel>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</Form.Label>)
}
}
Certainly get rid of componentWillMount and componentDidMount. You don't need them here.

Form fields lose focus when input value changes

I'm trying to build a form with conditional fields from a JSON schema using react-jsonschema-form and react-jsonschem-form-conditionals.
The components I'm rendering are a FormWithConditionals and a FormModelInspector. The latter is a very simple component that shows the form model.
The relevant source code is:
import React from 'react';
import PropTypes from 'prop-types';
import Engine from "json-rules-engine-simplified";
import Form from "react-jsonschema-form";
import applyRules from "react-jsonschema-form-conditionals";
function FormModelInspector (props) {
return (
<div>
<div className="checkbox">
<label>
<input type="checkbox" onChange={props.onChange} checked={props.showModel}/>
Show Form Model
</label>
</div>
{
props.showModel && <pre>{JSON.stringify(props.formData, null, 2)}</pre>
}
</div>
)
}
class ConditionalForm extends React.Component {
constructor (props) {
super(props);
this.state = {
formData: {},
showModel: true
};
this.handleFormDataChange = this.handleFormDataChange.bind(this);
this.handleShowModelChange = this.handleShowModelChange.bind(this);
}
handleShowModelChange (event) {
this.setState({showModel: event.target.checked});
}
handleFormDataChange ({formData}) {
this.setState({formData});
}
render () {
const schema = {
type: "object",
title: "User form",
properties: {
nameHider: {
type: 'boolean',
title: 'Hide name'
},
name: {
type: 'string',
title: 'Name'
}
}
};
const uiSchema = {};
const rules = [{
conditions: {
nameHider: {is: true}
},
event: {
type: "remove",
params: {
field: "name"
}
}
}];
const FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
return (
<div className="row">
<div className="col-md-6">
<FormWithConditionals schema={schema}
uiSchema={uiSchema}
formData={this.state.formData}
onChange={this.handleFormDataChange}
noHtml5Validate={true}>
</FormWithConditionals>
</div>
<div className="col-md-6">
<FormModelInspector formData={this.state.formData}
showModel={this.state.showModel}
onChange={this.handleShowModelChange}/>
</div>
</div>
);
}
}
ConditionalForm.propTypes = {
schema: PropTypes.object.isRequired,
uiSchema: PropTypes.object.isRequired,
rules: PropTypes.array.isRequired
};
ConditionalForm.defaultProps = {
uiSchema: {},
rules: []
};
However, every time I change a field's value, the field loses focus. I suspect the cause of the problem is something in the react-jsonschema-form-conditionals library, because if I replace <FormWithConditionals> with <Form>, the problem does not occur.
If I remove the handler onChange={this.handleFormDataChange} the input field no longer loses focus when it's value changes (but removing this handler breaks the FormModelInspector).
Aside
In the code above, if I remove the handler onChange={this.handleFormDataChange}, the <FormModelInspector> is not updated when the form data changes. I don't understand why this handler is necessary because the <FormModelInspector> is passed a reference to the form data via the formData attribute. Perhaps it's because every change to the form data causes a new object to be constructed, rather than a modification of the same object?
The problem is pretty straightforward, you are creating a FormWithConditionals component in your render method and in your onChange handler you setState which triggers a re-render and thus a new instance of FormWithConditionals is created and hence it loses focus. You need to move this instance out of render method and perhaps out of the component itself since it uses static values.
As schema, uiSchema and rules are passed as props to the ConditionalForm, you can create an instance of FormWithConditionals in constructor function and use it in render like this
import React from 'react';
import PropTypes from 'prop-types';
import Engine from "json-rules-engine-simplified";
import Form from "react-jsonschema-form";
import applyRules from "react-jsonschema-form-conditionals";
function FormModelInspector (props) {
return (
<div>
<div className="checkbox">
<label>
<input type="checkbox" onChange={props.onChange} checked={props.showModel}/>
Show Form Model
</label>
</div>
{
props.showModel && <pre>{JSON.stringify(props.formData, null, 2)}</pre>
}
</div>
)
}
class ConditionalForm extends React.Component {
constructor (props) {
super(props);
this.state = {
formData: {},
showModel: true
};
const { schema, uiSchema, rules } = props;
this.FormWithConditionals = applyRules(schema, uiSchema, rules, Engine)(Form);
this.handleFormDataChange = this.handleFormDataChange.bind(this);
this.handleShowModelChange = this.handleShowModelChange.bind(this);
}
handleShowModelChange (event) {
this.setState({showModel: event.target.checked});
}
handleFormDataChange ({formData}) {
this.setState({formData});
}
render () {
const FormWithConditionals = this.FormWithConditionals;
return (
<div className="row">
<div className="col-md-6">
<FormWithConditionals schema={schema}
uiSchema={uiSchema}
formData={this.state.formData}
onChange={this.handleFormDataChange}
noHtml5Validate={true}>
</FormWithConditionals>
</div>
<div className="col-md-6">
<FormModelInspector formData={this.state.formData}
showModel={this.state.showModel}
onChange={this.handleShowModelChange}/>
</div>
</div>
);
}
}
ConditionalForm.propTypes = {
schema: PropTypes.object.isRequired,
uiSchema: PropTypes.object.isRequired,
rules: PropTypes.array.isRequired
};
ConditionalForm.defaultProps = {
uiSchema: {},
rules: []
};
For anyone bumping into the same problem but using Hooks, here's how without a class :
Just use a variable declared outside the component and initialize it inside useEffect. (don't forget to pass [] as second parameter to tell react that we do not depend on any variable, replicating the componentWillMount effect)
// import ...
import Engine from 'json-rules-engine-simplified'
import Form from 'react-jsonschema-form'
let FormWithConditionals = () => null
const MyComponent = (props) => {
const {
formData,
schema,
uischema,
rules,
} = props;
useEffect(() => {
FormWithConditionals = applyRules(schema, uischema, rules, Engine)(Form)
}, [])
return (
<FormWithConditionals>
<div></div>
</FormWithConditionals>
);
}
export default MyComponent
Have you tried declaring function FormModelInspector as an arrow func :
const FormModelInspector = props => (
<div>
<div className="checkbox">
<label>
<input type="checkbox" onChange={props.onChange} checked={props.showModel}/>
Show Form Model
</label>
</div>
{
props.showModel && <pre>{JSON.stringify(props.formData, null, 2)}</pre>
}
</div>
)

Dynamically include files (components) and dynamically inject those components

Looking around the next I could not find the answer: How do I dynamicly include a file, based on prop change per say: here some sudo code to intrastate what I'm trying to do!
class Test extends React.Component {
constructor(props){
super(props)
this.state = { componentIncluded: false }
includeFile() {
require(this.props.componetFileDir) // e.g. ./file/dir/comp.js
this.setState({ componentIncluded: true });
}
render() {
return(
<div className="card">
<button onClick={this.includeFile}> Load File </button>
{ this.state.componentIncluded &&
<this.state.customComponent />
}
</div>
)
}
}
so this.props.componetFileDir has access to the file dir, but I need to dynamically include it, and can't really do require() as its seems to running before the action onClick get called. Any help would be great.
Em, Your code looks a bit wrong to me. So I created a separate demo for dynamic inject components.
While in different situation you can use different React lifecycle functions to inject your component. Like componentWillReceiveProps or componentWillUpdate.
componentDidMount() {
// dynamically inject a Button component.
System.import('../../../components/Button')
.then((component) => {
// update the state to render the component.
this.setState({
component: component.default,
});
});
}
render() {
let Button = null;
if (this.state.component !== null) {
Button = this.state.component;
}
return (
<div>
{ this.state.component !== null ? <Button>OK</Button> : false }
</div>
);
}
After you edited your code, it should be something similar to below:
class Test extends React.Component {
constructor(props){
super(props)
this.state = { customComponent: null }
this.includeFile = this.includeFile.bind(this);
}
includeFile() {
System.import(this.props.componetFileDir)
.then((component) => {
this.setState({ customComponent: component.default });
});
}
render() {
return(
<div className="card">
<button onClick={this.includeFile}> Load File </button>
{
this.state.customComponent
}
</div>
)
}
}

unable set a default value in redux-form w. react

I'm unable to set a default value of a form w/redux-form. The result I'm looking for is an editable text field to later to submit to the database. i.e. updating an email address.
I've tried setting a property in the form to value or defaultValue.
Note: I've taken repetitive code out to make this easier to read with just a "name" field.
any insight is appreciated!
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
export const fields = [ 'name']
//(container) page-profile.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Profile from '../components/Profile';
class PageProfile extends Component {
render() {
return (
<Profile
userInfo = {this.props.userInfo}
/>
)
}
}
// requiring this page before rendering -- breaks page
PageProfile.propTypes = {
//userInfo: PropTypes.object.isRequired
}
function mapStateToProps(state) {
return {
userInfo : state.auth.userInfo
}
}
// injection to child
export default connect(mapStateToProps, {
})(PageProfile);
// profile.js
export default class Profile extends Component {
render() {
const { fields: {name }, resetForm, handleSubmit, submitting } = this.props
return (
<div>
<img className="image" src={this.props.userInfo.img_url}/>
<form onSubmit={handleSubmit}>
<div>
<div>
<label>name</label>
<div>
<input type="text" defaultValue={this.props.userInfo.name} placeholder="name" {...name}/>
</div>
{name.touched && name.error && <div>{name.error}</div>}
</div>
<button type="submit" disabled={submitting}>
{submitting ? <i/> : <i/>} Submit
</button>
</form>
</div>
)
}
}
Profile.propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
}
export default reduxForm({
form: 'Profile',
fields,
validate
})(Profile)
You can supply initialValues in reduxForm's mapStateToProps:
const mapFormToProps = {
form: 'Profile',
fields,
validate
};
const mapStateToProps = (state, ownProps) => ({
initialValues: {
name: ownProps.userInfo.name
}
});
reduxForm(
mapFormToProps,
mapStateToProps
)(Profile)
Then just bind like so: <input type="text" placeholder="name" {...name} />.