RadioGroup not working in redux-form - forms

I am using the custom radio button component in redux-form. I am not able to make it work properly. Following the custom component code.
import React, { PropTypes } from 'react';
import {
Col,
ControlLabel,
FormGroup,
Label,
} from 'react-bootstrap';
import { RadioGroup, Radio } from 'react-radio-group';
import _ from 'lodash';
import './style.css';
const FormRadioGroup = props => (
<FormGroup controlId={props.name || ''}>
<Col
componentClass={ControlLabel}
sm={props.labelColValue || 2}
className={props.labelClass || ''}
>
{ props.label || 'Checkbox Label'}
</Col>
<Col sm={props.radioColValue || 6}>
<RadioGroup
name={props.name}
selectedValue={props.input.value}
onChange={value => { props.input.onChange(value); }}
className="react-radio-group-button"
>
{
_.map(props.options, option =>
(
<Label>
<Radio value={option.key} />
{option.value}
</Label>
))
}
</RadioGroup >
</Col>
</FormGroup>
);
FormRadioGroup.propTypes = {
name: PropTypes.string,
labelColValue: PropTypes.number,
labelClass: PropTypes.string,
label: PropTypes.string,
radioColValue: PropTypes.number,
options: PropTypes.array,
disabled: PropTypes.boolean,
radioClass: PropTypes.string,
sendBoolean: PropTypes.boolean,
input: PropTypes.object,
meta: PropTypes.object,
};
export default FormRadioGroup;
In redux-form I am using as follows.
<Field
name="json_boolean_property"
component={FormRadioGroup}
format={value => (value ? 'yes':'no')}
parse={value => (value === 'yes')}
type="radio"
labelColValue={2}
label="Radio Group Label"
radioBoxColValue={6}
sendBoolean
options={[
{ key: 'yes', value: 'Yes' },
{ key: 'no', value: 'No' },
]}
/>
I am not able to set the default value when it comes form the server also when i select the radio button it fire the change event but along with that it also give redux-form/UPDATE_SYNC_ERRORS.
I am not sure, what wrong I am doing but its failing very badly.

Related

Formik - arrayfields -- validation handling

I am working with formik/material ui -- and yup validation. I am struggling to get validation showing/working on field arrays
my schema and validation looks like this currently for each field.
"fields": [
{
"type": "date",
"label": "Start Date",
"name": "startDate",
"validation": yup.date().default(function () { return new Date() }).required("date is required").nullable().typeError(''),
"minDate": moment().add(1, 'weeks'),
"maxDate": moment().add(8, 'weeks'),
"disablePast": true,
"disableFuture": false,
//"disabled": true
},
{
"type": "date",
"label": "End Date",
"name": "endDate",
"validation": yup.date().default(function () { return new Date() }).required("date is required").nullable().typeError(''),
"minDate": moment().add(1, 'weeks'),
"maxDate": moment().add(8, 'weeks'),
"disablePast": true,
"disableFuture": false,
//"disabled": true
}
]
I've seen on formik - they have some validation like this - but how do I apply it my code base for dates?
https://formik.org/docs/api/fieldarray
const schema = Yup.object().shape({
friends: Yup.array()
.of(
Yup.object().shape({
name: Yup.string().min(4, 'too short').required('Required'), // these constraints take precedence
salary: Yup.string().min(3, 'cmon').required('Required'), // these constraints take precedence
})
)
.required('Must have friends') // these constraints are shown if and only if inner constraints are satisfied
.min(3, 'Minimum of 3 friends'),
});
my fieldarray looks like this -- and I believe errors should appear under the field group -- the fields outer border goes red -- but it doesn't seem to work for when I null the date - like is required date working?
<>
<FieldArray
name={item.name}
onChange={event => {
console.log("event field array change", event)
}}
>
{({ insert, remove, push }) => (
<div className="field field-array">
<div className="row" key={0}>
{item.fields.map((ch, inx) => (
<span key={"x"+inx}>
<div className="col-x">
<Field
name={`${item.name}.${ch.name}`}
>
{({
field, // { name, value, onChange, onBlur }
form,
meta,
}) => (
<>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
label={ch.label}
disablePast={ch.disablePast}
disableFuture={ch.disableFuture}
minDate={moment(ch.minDate)}
maxDate={moment(ch.maxDate)}
value={field.value? moment(field.value).format('YYYY-MM-DD'): moment().format('YYYY-MM-DD')}
{...field}
onChange={(value) => {
form.setFieldValue(field.name, value);
this.props.onHandle(field.name, value);
}}
renderInput={(params) => {
return (<TextField {...params} name={field.name} />)
}}
/>
</LocalizationProvider>
{meta.touched && meta.error && (
<div className="error">{meta.error}</div>
)}
</>
)}
</Field>
</div>
{inx === 0 &&
(<span></span>)
}
</span>
))}
</div>
</div>
)}
</FieldArray>
</>
I worked this out
"validation": yup.array().of( yup.object().shape({ firstName: yup.string().min(4, 'too short').required('Required'), lastName: yup.string().min(3, 'cmon').required('Required'), }) ).min(1, 'Minimum of 1 friends')
-- but in the display of errors had to check if it was an array or a string to avoid a render error
under the add more button to display array errors of the main list.
<FormHelperText
error={(form.errors[parent.name] && form.errors[parent.name].length > 0 ? true : false)}
>
{typeof form.errors[parent.name] === "string" &&
<>{form.errors[parent.name]}</>
}
</FormHelperText>
and under the fields - meta errors
{(getHelperVisibility(values, ch)) &&
<FormHelperText
error={meta.touched && (meta.error && meta.error.length > 0 ? true : false)}
>
{meta.error}
</FormHelperText>
}

React-hook-form + dynamic form: Render element upon dropdown selection

I am working in form using react-hook-form. This form use useFieldArray, it has to be dynamic.
Right now is very simple, it contains a react-select component with a few options and a textfield that get rendered depending on the option that the user select on the select component.
The problem I have is that the textfield component renders when the state updates, which is correct until I add a new group of element to the form. Since the textfield is listening to the same state it doesn't matter which select I use to render the textfield element, it gets rendered in all groups.
I am looking a way to specify which textfield should be rendered when the user change the select.
I the sandbox you can see what I have done. To reproduce the problem click on the "Add"-button and you will see two areas, each one with a select component.
When you choose "Other" in the select component a textfield appears, but not only in the area where the select was changed but in all areas.
How can I avoid that behavior?
https://codesandbox.io/s/vibrant-fast-381q0?file=/src/App.tsx
Extract:
const [isDisabled, setIsDisabled] = useState<boolean>(true);
const { control, handleSubmit, getValues } = useForm<IFormFields>({
defaultValues: {
managerialPositions: [
{
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
}
]
}
});
useFieldArray implementation:
const {
fields: managerialPositionsFields,
append: managerialPositionsAppend,
remove: managerialPositionsRemove
} = useFieldArray({
name: "managerialPositions",
control
});
Here i update the state when the user select "Other title" in the select component:
const watchChange = (value?: number, i?: number) => {
let values: any = getValues();
if (values.managerialPositions[i].title === 3) {
setIsDisabled(false);
}
};
And here is where I render the button to create a new group of elements and the select component and the textfield that should be rendered if "isDisabled" is false.
{managerialPositionsFields.map((field, index) => {
return (
<Stack className="sectionContainer" key={field.id}>
<Stack horizontal horizontalAlign="space-between">
<StackItem>
<CommandBarButton
iconProps={{ iconName: "AddTo" }}
text="Add"
type="button"
onClick={() => {
managerialPositionsAppend({
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
});
}}
/>
</StackItem>
</Stack>
<Stack horizontal tokens={{ childrenGap: 20 }}>
<StackItem>
<Label className="select-label requiredIkon">Title</Label>
<Controller
control={control}
name={`managerialPositions.${index}.title`}
render={({ field: { onChange, value, ref } }) => (
<>
<Select
className="react-select-container authoritySelect"
classNamePrefix="react-select"
placeholder="Select title"
options={titelList}
id={`managerialPositions.${index}.title`}
value={
titelList.find((g) => g.value === value)
? titelList.find((g) => g.value === value)
: null
}
onChange={(val) => {
onChange(val.value);
watchChange(val.value, index);
}}
/>
{
// this input is for select validation
<input
tabIndex={-1}
autoComplete="off"
style={{ opacity: 0, height: 0 }}
value={
titelList.find((g) => g.value === value)
? titelList
.find((g) => g.value === value)
.toString()
: ""
}
required={true}
//Without this console will get an error:
onChange={() => {}}
/>
}
</>
)}
/>
</StackItem>
{!isDisabled && (
<StackItem className="">
<Controller
name={`managerialPositions.${index}.otherTitle`}
control={control}
render={({
field: { onChange, name: fieldName, value }
}) => (
<TextField
label="Other title"
name={fieldName}
onChange={(e) => {
onChange(e);
}}
value={value}
/>
)}
/>
</StackItem>
)}
</Stack>
</Stack>
);
})}

I have React Hook Form With Controller With Yup as validataro The Material UI Select stays red after selecting something and won't go away

I got TextField to work, now the Material UI Select will turn red if no selection is made but stays red after selection is made and won't let form submit. I'm using Yup as validation library.Maybe I keep using wrong Yup type I try String and array but I can't get it to work.
import {
makeStyles,
Box,
Select,
FormControl,
InputLabel,
MenuItem,
Typography,
} from "#material-ui/core";
import * as yup from 'yup';
import { yupResolver } from '#hookform/resolvers'
import { useForm, Controller } from "react-hook-form";
const FormFields = ({ typeOfInquiry, typeOfProviderSupplier, feedbackform }) => {
const schema = yup.object().shape({
typeofInquiry: yup.array().nullable().required(),
});
const { handleSubmit, control, reset, errors } = useForm();
return (
<Controller
style={{ minWidth: 220 }}
name="typeofInquiry"
render ={({ field: { ...field }, fieldState })=>{
console.log(props)
return ( <Select {...field} >
{typeOfInquiry.map((person) => (
<MenuItem key={person.value} value={person.value} >
{person.label}
</MenuItem>
))}
</Select>
)
}}
control={control}
defaultValue=" "
/>
<Typography className={classes.red}>{errors.typeofInquiry?.message}</Typography>
</FormControl>
</form>
);
}
You've to pass the ref to the TextField component.
Here is a working example
👉🏻 https://codesandbox.io/s/exciting-pateu-3n0i9
You should do something similar with Select.
Some examples with MUI: https://codesandbox.io/s/react-hook-form-v7-controller-5h1q5?file=/src/Mui.js

Vuetify datatable reload when performing CRUD operations

I have a simple vuetify datatable that performs CRUD operations. I am using axios and a mongo database. When I add a new item, the information is correctly displayed in the client side and posted in the mongo database. However, I cannot access to updated information of the mongo database, particularly the id that mongo created, unless I reload the webpage. I am newcomer in Vue, please be patient. A simplified version of the problem:
axios
.post('http://localhost:5000/dessert', {
name: this.editedItem.name
})
console.log(this.editedItem.name) // I CAN ACCES WITHOUT PROBLEM
console.log(this.editedItem._id) // I NEED TO RELOAD THE WEBPAGE IN ORDER TO ACCES THIS ELEMENT. THE ID THAT MONGO CREATED.
Vue file:
<template>
<v-data-table
:headers="headers"
:items="desserts"
sort-by="calories"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>My CRUD</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on }">
<v-btn color="primary" dark class="mb-2" v-on="on">New Item</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.name" label="Dessert name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.calories" label="Calories"></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close">Cancel</v-btn>
<v-btn color="blue darken-1" text #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.action="{ item }">
<v-icon
small
class="mr-2"
#click="editItem(item)"
>
edit
</v-icon>
<v-icon
small
#click="deleteItem(item)"
>
delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</template>
<script>
import axios from 'axios'
export default {
data: () => ({
dialog: false,
headers: [
{
text: 'Dessert (100g serving)',
value: 'name',
},
{ text: 'Calories', value: 'calories' },
{ text: 'Actions', value: 'action', sortable: false },
],
desserts: [],
editedIndex: -1,
editedItem: {
name: '',
calories: 0,
},
defaultItem: {
name: '',
calories: 0,
},
}),
mounted() {
this.fetchItems()
},
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
watch: {
dialog (val) {
val || this.close()
},
},
created () {
this.initialize()
},
methods: {
fetchItems(){
axios
.get('http://localhost:5000/dessert')
.then(response => (this.desserts = response.data.data))
},
editItem (item) {
this.editedIndex = this.desserts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.editedID = this.editedItem._id
this.name = this.editedItem.name
this.calories = this.editedItem.calories
this.dialog = true
},
deleteItem (item) {
const index = this.desserts.indexOf(item)
this.deletedItem = Object.assign({}, item)
console.log(this.deletedItem)
this.deletedID = this.deletedItem._id
console.log(this.deletedID)
if (confirm("Do you really want to delete?")) {
axios.delete(`http://localhost:5000/dessert/${this.deletedID}`);
this.desserts.splice(index, 1);
}
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () { // Edit Item
if (this.editedIndex > -1) {
Object.assign(this.desserts[this.editedIndex], this.editedItem)
axios.delete(`http://localhost:5000/dessert/${this.editedItem._id}`)
axios
.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
})
// New Item
} else {
this.desserts.push(this.editedItem)
axios.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
})
}
this.close()
},
},
}
</script>
Python file:
from flask import Flask
from flask import jsonify
from flask import request
from flask_pymongo import PyMongo
from flask_cors import CORS
from bson.objectid import ObjectId
app = Flask(__name__)
#CORS(app)
# instantiate
app.config.from_object(__name__)
# enable CORS
CORS(app, resources={r'/*': {'origins': '*'}})
app.config['MONGO_DBNAME'] = 'restdb'
app.config['MONGO_URI'] = 'mongodb://localhost:27017/restdb'
mongo = PyMongo(app)
#app.route('/dessert', methods=['POST'])
def add_dessert():
dessert = mongo.db.desserts
name = request.json['name']
calories = request.json['calories']
dessert_id = dessert.insert({
'name': name,
'calories': calories
})
new_dessert = dessert.find_one({'_id': dessert_id })
output = {
'name' : new_dessert['name'],
'calories' : new_dessert['calories']
}
return jsonify({'result' : output})
#app.route('/dessert', methods=['GET'])
def get_all_desserts():
dessert = mongo.db.desserts
output = []
for s in dessert.find():
s['_id'] = str(s['_id'])
output.append({'_id' : s['_id'],
'name' : s['name'],
'calories' : s['calories']
})
return jsonify({'data' : output})
#app.route('/dessert/<dessert_id>', methods=['GET'])
def get_one_dessert(dessert_id):
dessert = mongo.db.desserts
s = dessert.find_one({"_id" : ObjectId(dessert_id)})
s['_id'] = str(s['_id'])
if s:
output = {'_id' : s['_id'], 'name' : s['name'], 'calories' : s['calories']}
else:
output = "No such name"
return jsonify({'result' : output})
#app.route('/dessert/<dessert_id>', methods=['DELETE'])
def delete_one_dessert(dessert_id):
dessert = mongo.db.desserts
s = dessert.find_one({"_id" : ObjectId(dessert_id)})
s['_id'] = str(s['_id'])
dessert.remove({"_id" : ObjectId(dessert_id)})
if s:
output = {'_id' : s['_id'], 'name' : s['name'], 'calories' : s['calories']}
else:
output = "No such name"
return jsonify({'result' : output})
if __name__ == '__main__':
app.run(debug=True)
If I understood it correctly, you want to be able to see in the front-end the newly added item, including the generated ID, after posting it to backend, right?
So, you can just simply call the fetchItems() once you finish posting the new item. It will automaticly update the array of the shown items, including the newly added ID.
The ID property is created when the item is added to the database, so it's not possible to have it unless the back-end gives it back to the front-end.
axios.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
}).then(response => {
this.fetchItems()
})
That means, once finishing the POST, fetchItems() again.

React: Dynamically generated <select> elements - setting 'selected' state in a mapped element

I'm trying to handle the selection of dynamically generated <option>'s in a <select> element. I understand that the onChange trigger is what i need to setState with but i can't seem to get her to work.
Here's what i've got going on:
See the Pen dynamic select by Archibald Hammer (#archaeopteryx) on CodePen.
import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'lodash'
const ITEMS = [
{ name: 'centos', text: 'centos', value: 'centosValue' },
{ name: 'ubuntu', text: 'ubuntu', value: 'ubuntuValue' },
]
const SelectComponent = (props) => (
<select name={props.name}>
{_.map(props.items, (item, i) => <Option
key={i}
name={item.name}
value={item.value}
text={item.text}
handleSelect={props.handleSelect}
/>
)}
</select>
)
const Option = (props) => (
<option
value={props.value}
onChange={props.handleSelect}>{props.text}</option>
)
class App extends React.Component {
constructor() {
super()
this.state = {
selected: ''
}
this.handleSelect = this.handleSelect.bind(this)
}
handleSelect(e) {
this.setState({selected: e.target.value})
}
render() {
return (
<div>
<SelectComponent
name="testSelect"
items={ITEMS}
handleSelect={this.handleSelect}
/>
<div>
<p>Selected: {this.state.selected}</p>
</div>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
This code does render the dropdown selector as expected but it isn't triggering the setState on the selected item. Any thoughts?
Also, does anyone have any pro-tips for troubleshooting this kind of problem? Any super slick dev-tools you know of or methods for finding out which props are being passed, etc?
The problem is that the options in a select element won't trigger any event, the change is happening in the select element not the option. All you have to do is pass the handleSelect method to the <select> component:
const SelectComponent = (props) => (
<select name={props.name}
onChange={props.handleSelect}
>
{_.map(props.items, (item, i) => <Option
key={i}
name={item.name}
value={item.value}
text={item.text}
handleSelect={props.handleSelect}
/>
)}
</select>
);
const Option = (props) => (
<option
value={props.value}
>{props.text}</option>
)
Sorry I forgot to add the live sample link:
https://codesandbox.io/s/31AyQ2woR
In terms of a tip for debugging, in this particular case just know that the event is triggered by the select component and not the option element ;). But the one I use all the time is React developer tools:
https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
https://addons.mozilla.org/es/firefox/addon/react-devtools/