I'm interested in using React Rating to allow my users to provide 1-5 star reviews.
The Rating component renders fine like so:
<Rating />
I would like this Rating ability to appear within react redux-form. How can I integrate Rating within my existing form which has fields like so:
let fields = {}
const fieldoptions = {
type: 'select',
options: [
{},
{ label: '1', value: '1' },
{ label: '2', value: '2' },
{ label: '3', value: '3' },
{ label: '4', value: '4' },
{ label: '5', value: '5' },
]
};
....
const renderField = ({input, field, label, meta: {submitFailed, touched, error, pristine}}) => {
const { type, placeholder } = field
if (type === 'text' || type === 'email' || type === 'number' || type === 'checkbox') {
return <input {...input} placeholder={placeholder} type={type} />
} else if (type === 'select') {
const { options } = field
return (
<div className={classNames("form-group", {
"has-danger": (submitFailed && error) || (!pristine && touched && error),
"has-success": !pristine && touched && !error
})}>
<label>{field.name}</label>
<div>
<select name={field.skill_id} onChange={input.onChange} className={touched ? (error ? "form-control form-control-danger" : "form-control form-control-success") : "form-control"}>
{options.map((option, index) => {
return <option key={index} value={option.value}>{option.label}</option>
})}
</select>
{touched && error && <span className="form-control-feedback">{label} {error}</span>}
</div>
</div>
)
} else {
return <div>Type not supported.</div>
}
}
{this.props.fields.map(field => (
<div key={field.skill_id}>
<Field
name={ 'skill_id_' + field.skill_id }
component={renderField}
field={field}
/>
</div>
))}
You can integrate react-rating with redux form as shown below. Working sample at https://jsfiddle.net/sherin81/d82a1tao/ - The values are logged in console on click on the submit button.
class Page extends Component {
constructor(props) {
super(props)
this.renderField = this.renderField.bind(this)
}
renderField({ input, type }) {
if(type=== 'text') {
return (<input {...input} type={type} />)
} else {
return (<div><Rating {...input} initialRate={parseInt(input.value)} /></div>)
}
}
render() {
const { handleSubmit, onSubmit } = this.props
return (
<div>
<Field
name="rating"
component={this.renderField}
/>
<Field
name="name"
type="text"
component={this.renderField}
/>
<button type="submit" onClick={handleSubmit(onSubmit)}>Submit</button>
</div>
)
}
}
const MyTextFields = reduxForm({
form: 'Page'
})(Page)
The initial values can be set as shown below.
<MyTextFields initialValues={{ rating: 2, name: 'test' }} onSubmit={(values) => console.log(values)} />
Related
I'm using Formik and windmill for the multiselect form, see picture below. The problem is that I implemented a search functionality to look into the array of options and now it appears that even though I'm selecting an option it is not passing the value to Formik and is not submitting the form. Any help would be greatly appreciated. Thanks!
form
import { React, useState } from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import { partService } from '../../services';
import { SearchIcon, AddIcon } from '../../icons';
import { Input, Label, Select, HelperText } from '#windmill/react-ui';
function CreateElementForm({ formRef, callback, partId }) {
const [searchTerm, setSearchTerm] = useState('');
const [iconSelection, setIconSelection] = useState(true);
const tempArray = [
{ name: 'data1', value: 'value1' },
{ name: 'data2', value: 'value2' },
{ name: 'data3', value: 'value3' },
{ name: 'data4', value: 'value4' },
];
return (
<Formik
innerRef={formRef}
initialValues={{
description: '',
percentage: '',
material: '',
}}
validationSchema={Yup.object().shape({
description: Yup.string().required('Element name is required'),
percentage: Yup.number().required('Percentage is required'),
material: Yup.string().required('Material is required'),
})}
onSubmit={(
{ description, percentage, material },
{ setStatus, setSubmitting }
) => {
setStatus();
setTimeout(async () => {
await partService
.createElement(partId, description, percentage, material)
.then(
(response) => {
callback(true);
},
(error) => {
if (error.response) {
setStatus(error.response.data.message);
} else {
setStatus('Some error occured.');
}
callback(false);
}
);
}, 400);
}}
>
{({ errors, status, touched, isSubmitting, setFieldValue, values }) => (
<Form>
<Label>
<span>Description</span>
<Field
className='mt-1'
as={Input}
name='description'
type='text'
placeholder='Part Element'
/>
<ErrorMessage name='name'>
{(msg) => <HelperText valid={false}>{msg}</HelperText>}
</ErrorMessage>
</Label>
<Label>
<span>Percentage</span>
<Field
className='mt-1'
as={Input}
name='percentage'
type='number'
placeholder='%'
/>
<ErrorMessage name='percentage'>
{(msg) => <HelperText valid={false}>{msg}</HelperText>}
</ErrorMessage>
</Label>
<Label>
<span>Search Field</span>
<div className='relative text-gray-500 focus-within:text-purple-600 dark:focus-within:text-purple-400'>
<Input
className='mt-1 pl-10 text-black dark:text-gray-300'
placeholder='Jane Doe'
onChange={(event) => {
setSearchTerm(event.target.value);
}}
/>
<div className='absolute inset-y-0 flex items-center ml-3 pointer-events-none'>
{iconSelection ? (
<SearchIcon className='w-5 h-5' aria-hidden='true' />
) : (
<AddIcon className='w-5 h-5' aria-hidden='true' />
)}
</div>
</div>
</Label>
<Label className='mt-4'>
<span>Multiselect</span>
<Field
className='mt-1'
as={Select}
name='material'
multiple={true}
type='text'
onChange={(evt) =>
setFieldValue('material', evt.target.selectedOptions.val)
}
>
{tempArray
.filter((value) => {
if (searchTerm === '') {
return value;
} else if (
value.name.toLowerCase().includes(searchTerm.toLowerCase())
) {
return value;
}
})
.map((i) => (
<option key={i.name} val={i.value}>
{i.name}
</option>
))}
</Field>
</Label>
{status && <HelperText valid={false}>{status}</HelperText>}
</Form>
)}
</Formik>
);
}
export default CreateElementForm;
My formik form is like this
const NewProduct = ({}) => {
const validate = (values) => {
const errors = {}
if (!values.title) {
errors.title = 'Required'
}
return errors
}
const formik = useFormik({
initialValues: {
title: '',
price: '',
},
validate,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2))
},
})
return (
<div className="newProductComponent">
<form onSubmit={formik.handleSubmit}>
<label>title</label>
<input
type="text"
id="title"
name="title"
onChange={formik.handleChange}
value={formik.values.title}
/>
{formik.errors.title ? (
<div className="error">{formik.errors.title}</div>
) : null}
<NumberFormat
value={formik.values.price}
thousandSeparator={true}
onValueChange={(values) => {
const { formattedValue, value } = values
}}
/>
</form>
</div>
)
}
How can I get number format component in that form to work with formik?
useFormik - the hook you're already using - returns a function setFieldValue that can be used to manually set a value.
First arg is field name price and second is the value. You also must set attribute name="price" on <NumberFormat>.
const App = () => {
const validate = (values) => {
const errors = {}
if (!values.title) {
errors.title = 'Required'
}
return errors
}
const formik = useFormik({
initialValues: {
title: '',
price: '',
},
validate,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2))
},
})
return (
<div className="newProductComponent">
<form onSubmit={formik.handleSubmit}>
<label>title</label>
<input
type="text"
id="title"
name="title"
onChange={formik.handleChange}
value={formik.values.title}
/>
{formik.errors.title ? (
<div className="error">{formik.errors.title}</div>
) : null}
<br />
<label>Price</label>
<NumberFormat
name="price"
value={formik.values.price}
thousandSeparator={true}
onValueChange={(values) => {
const { value } = values;
formik.setFieldValue('price', value);
}}
/>
<br />
<button type="submit">Submit</button>
</form>
</div>
)
};
Live Demo
You can set the values using useField and useFormikContext
example:
const [field, meta] = useField('price');
const { setFieldValue } = useFormikContext();
const isError = meta.touched && Boolean(meta.error);
<NumberFormat
value={formik.values.price}
thousandSeparator={true}
onValueChange={(values) => {
const { formattedValue, value } = values
setFieldValue(field.name, formattedValue)
}}
/>
{isError && <div className="error">{meta.error}</div>}
This post is very close to what I'm trying to do, but doesn't cover dynamic fields: Updating an object with setState in React
What I mean by dynamic fields: (Working Code)
import React, { Component } from 'react';
class ComponentName extends Component {
constructor(props) {
super(props);
this.state = {
name: "",
age: "",
email: "",
manager: ""
}
}
handleValueChange = (ev) => {
this.setState({[ev.target.name]: ev.target.value}); // <----- dynamic field
}
handleSubmit = (ev) => {
ev.preventDefault();
console.log("Form Values =", this.state);
}
render() {
<form onSubmit={ev => this.handleSubmit(ev)}>
<div>
<label>Name:</label>
<input type="text" name="name" onChange={ev => this.handleValueChange(ev)} value={this.state.name} />
</div>
<div>
<label>Age:</label>
<input type="text" name="age" onChange={ev => this.handleValueChange(ev)} value={this.state.age} />
</div>
<div>
<label>Age:</label>
<input type="email" name="email" onChange={ev => this.handleValueChange(ev)} value={this.state.email} />
</div>
<div>
<label>Age:</label>
<input type="text" name="manager" onChange={ev => this.handleValueChange(ev)} value={this.state.manager} />
</div>
<button type="submit">Submit</button>
</form>
}
}
export default BadgeContractorRequest;
^Notice how I use this.setState({[ev.target.name]: ev.target.value}); to avoid hardcoding every field.
What I want to do... is update multiple form objects within the same state: (Broken Code)
import React, { Component } from 'react';
// Components
import FormOne from './formOne.js';
import FormTwo from './formTwo.js';
import FormThree from './formThree.js';
class ComponentName extends Component {
constructor(props) {
super(props);
this.state = {
formRequestTypeValue: "",
formOne: {
name: "",
age: "",
requestType: "",
manager: ""
},
formTwo: {
managerName: "",
email: "",
position: ""
},
formThree: {
product: "",
details: ""
}
}
}
formRequestTypeOnChangeHandler = (ev) => {
this.setState({...this.state, formRequestTypeValue: ev.target.value});
}
handleValueChange = (ev) => {
// This is where I'm Lost...
this.state.formRequestTypeValue === "formOne" && this.setState({...this.state, formOne.[ev.target.name]: ev.target.value});
this.state.formRequestTypeValue === "formTwo" && this.setState({...this.state, formTwo.[ev.target.name]: ev.target.value});
this.state.formRequestTypeValue === "formThree" && this.setState({...this.state, formThree.[ev.target.name]: ev.target.value});
}
handleSubmit = (ev) => {
ev.preventDefault();
console.log("Form Values =", this.state);
}
render() {
<form onSubmit={ev => this.handleSubmit(ev)}>
<div>
<select onChange={ev => this.formRequestTypeOnChangeHandler(ev)} value={this.state.formRequestTypeValue}>
<option value="">Please Select a Form</option>
<option value="formOne">Form One</option>
<option value="formTwo">Form Two</option>
<option value="formThree">Form Three</option>
</select>
</div>
{
this.state.formRequestTypeValue === "formOne" &&
<div>
<FormOne handleValueChange={this.handleValueChange} handleSubmit={this.handleSubmit} formValues={this.state.formOne} />
</div>
}
{
this.state.formRequestTypeValue === "formTwo" &&
<div>
<FormTwo handleValueChange={this.handleValueChange} handleSubmit={this.handleSubmit} formValues={this.state.formTwo} />
</div>
}
{
this.state.formRequestTypeValue === "formThree" &&
<div>
<FormThree handleValueChange={this.handleValueChange} handleSubmit={this.handleSubmit} formValues={this.state.formThree} />
</div>
}
</form>
}
}
export default ComponentName;
The reason I'm not separating the <form> out into their own components, is because I want to maintain state of field values if the user selects a different form from the dropdown menu, and then goes back to their previous selection.
Here is where I'm stuck:
handleValueChange = (ev) => {
// This is where I'm Lost...
this.state.formRequestTypeValue === "formOne" && this.setState({...this.state, formOne.[ev.target.name]: ev.target.value});
this.state.formRequestTypeValue === "formTwo" && this.setState({...this.state, formTwo.[ev.target.name]: ev.target.value});
this.state.formRequestTypeValue === "formThree" && this.setState({...this.state, formThree.[ev.target.name]: ev.target.value});
}
Anyone have any idea what the syntax magic should be for handleValueChange?
Edit Update:
Also tried the following, but with no luck:
First:
You don't need to have . after formThree for example. Like below:
formThree[ev.target.name]
Then you need to have your past formOne fields to prevent them become empty. For that you need a code like below:
this.state.formRequestTypeValue === "formOne" &&
this.setState((prevState) => ({
...prevState,
formOne: {
...prevState.formOne,
[ev.target.name]: ev.target.value,
},
}));
prevState is your last this.state that has not changes yet.
with formOne: {...prevState.formOne,[ev.target.name]: ev.target.value}, you are carrying old formOne fields and also overriding [ev.target.name] field.
You need to repeat this for other form, too.
I am trying to describe a model for a contact us form. This form will have two text fields and one textarea field which will be used for the message. I have defined the message field as type: "textarea" but when it is rendered it is showing up as a normal text field. Can anyone suggest how to define textarea field here?
This is for a simple contact us form in reactjs.
<DynamicForm className="contactform"
title = "Contact us"
model = {[
{key: "name", label: "Name", props: {required:true}},
{key: "email", label: "E-mail", type:"email"},
{key: "message", label: "Message", type: "textarea" }
]}
onSubmit = {(model) => {this.onSubmit(model)}}
/>
I expect that a textarea will render when I give type as textarea in the above code.
Dynamic Form is defined here :
'''
import React from 'react';
import './contact.css';
export default class DynamicForm extends React.Component {
state = {
}
constructor(props) {
super(props);
}
onSubmit = (e) => {
e.preventDefault();
if (this.props.onSubmit) this.props.onSubmit(this.state);
}
onChange = (e, key) => {
this.setState({
[key]: this[key]
})
}
renderForm = () => {
let model = this.props.model;
let formUI = model.map((m) => {
let key = m.key;
let type = m.type || "text";
let props = m.props || {};
return (
<div key={key} className="form-group">
<label className="formlabel"
key={"l" + m.key}
htmlFor={m.key}>
{m.label}
</label>
<input {...props}
ref={(key) => { this[m.key] = key }}
className="form-input"
type={type}
key={"i" + m.key}
onChange={(e) => { this.onChange(e, key) }}
>
</input>
</div>
)
});
return formUI;
}
render() {
let title = this.props.title || "Dynamic Contact Us Form";
return (
<div className={this.props.className}>
<h3>{title}</h3>
<form className="dynamic-form" onSubmit={(e) => this.onSubmit(e)}>
<div className="form-group">
{this.renderForm()}
<button type="submit">Submit</button>
</div>
</form>
</div>
)
}
}
'''
textarea is not a valid type of the input type.
You should instead use the textarea HTML element.
Here is something that could work:
renderForm = () => {
let model = this.props.model;
let formUI = model.map((m) => {
let key = m.key;
let type = m.type || "text";
let props = m.props || {};
return (
<div key={key} className="form-group">
<label className="formlabel"
key={"l" + m.key}
htmlFor={m.key}>
{m.label}
</label>
{type === "textarea" ?
(<textarea [Add here the textarea props]>)
:
(<input {...props}
ref={(key) => { this[m.key] = key }}
className="form-input"
type={type}
key={"i" + m.key}
onChange={(e) => { this.onChange(e, key) }}
>)}
</input>
</div>
)
});
return formUI;
}
I'm looking to learn how to dynamically build a redux-form. Idea being, the component called, componentDidMount goes at fetches a list of items from the server and inserts them in the store, store.my_items:
{'item1', 'itemB', 'itemZZZ', etc...}
Now that these items are in the store, I want to build the redux-form with a Field for each item in store.my_items.
Example, several of these dynamically created:
<div>
<label>ItemXXX</label>
<div>
<label>
<Field name="ItemXXX" component="input" type="radio" value="1" />
{' '}
1
</label>
<label>
<Field name="ItemXXX" component="input" type="radio" value="2" />
{' '}
2
</label>
</div>
</div>
With react, redux-form, what would be the right way to approach building this type of dynamic redux form?
Thank you
I am following this approach for building a form dynamically. I am just declaring metadata of form fields (type, placeholder, the unique name of each field, etc.) like this:
const fields = [
{ name: 'name', type: 'text', placeholder: 'Enter Name' },
{ name: 'age', type: 'number', placeholder: 'Enter age' },
{ name: 'email', type: 'email', placeholder: 'Enter Email' },
{ name: 'employed', type: 'checkbox' },
{
name: 'favouriteColors',
type: 'select',
options: [
{ label: 'Red', value: 'red' },
{ label: 'Yellow', value: 'yellow' },
{ label: 'Green', value: 'green' },
],
},
]
Now, I am just iterating over these fields and rendering input for each field like the way I have done in renderField component given below. My general form component looks like this:
import React from 'react'
import { Field, reduxForm } from 'redux-form/immutable'
const renderField = ({ input, field }) => {
const { type, placeholder } = field
if (type === 'text' || type === 'email' || type === 'number' || type === 'checkbox') {
return <input {...input} placeholder={placeholder} type={type} />
} else if (type === 'select') {
const { options } = field
return (
<select name={field.name} onChange={input.onChange}>
{options.map((option, index) => {
return <option key={index} value={option.value}>{option.label}</option>
})}
</select>
)
} else {
return <div>Type not supported.</div>
}
}
const SimpleForm = ({ handleSubmit, fields }) => {
return (
<div>
{fields.map(field => (
<div key={field.name}>
<Field
name={field.name}
component={renderField}
field={field}
/>
</div>
))}
<div onClick={handleSubmit}>Submit</div>
</div>
)
}
export default reduxForm({
form: 'simpleForm'
})(SimpleForm)
and passing fields to SimpleForm component like this:
<SimpleForm fields={fields} onSubmit={() =>{}}/>
Now it's your choice that you want to fetch fields like this from the server or just want to fetch only items and make fields like this (by passing item as the name of field) on the frontend.
By using this approach, I am able to re-use template based on given type.
If someone has a better approach to make a form dynamically, then I would love to learn that too.
Edit:
If we have to pass form name dynamically, then a small change will be required:
export default reduxForm({})(SimpleForm)
and we can pass form name while this component like this:
<SimpleForm form={'simpleForm'} fields={fields} onSubmit={() =>{}} />
you can create a component form and use it in a .map() function and pass formName as props like this...
render() {
return(
<div>
{yourDataFromServer.map((item, index) =>
<ReduxFormComp
form={`${FORM_NAMES.SIMPLE_FORM}__${index}`}
initialValues={item}
/>
)}
</div>
);
}
NOTE: when you use from initialValues for init your form, all field name should equal to your data.