the autocomplete option labels are not getting cleared when reset() onSubmit(), is something wrong with this code or mui autocomplete issue? - material-ui

Hi here I am building a form with material ui autocomplete component and react-hook-form useController and useForm, as mui select component's option labels are getting clear on form reset but with autocomplete it is not happening, though its value gets cleared on reset as wanted, so is something wrong with this code or autocomplete issue,
Registered with useController (react-hook-form)
import React from "react";
import Autocomplete from "#mui/material/Autocomplete";
import TextField from "#mui/material/TextField";
import { useController, UseControllerProps } from "react-hook-form";
//type Multi = string;
interface MultiOptionInterface {
label: string;
value: string;
}
interface Props extends UseControllerProps {
label: string;
type?: React.HTMLInputTypeAttribute | undefined;
id: string;
placeholder?: string | undefined;
multiSelectOptions: MultiOptionInterface[];
}
export function RegisteredMultiAutocomplete({
name,
control,
label,
type,
id,
placeholder,
multiSelectOptions
}: Props) {
const {
field: { ref, onChange, ...inputProps },
fieldState: { error },
formState: { isSubmitSuccessful }
} = useController({
name,
control,
defaultValue: ""
});
return (
<Autocomplete
fullWidth
id={id}
multiple
clearOnEscape
options={multiSelectOptions}
getOptionLabel={(option: MultiOptionInterface) => option.label}
renderInput={(params) => (
<TextField
{...inputProps}
{...params}
inputRef={ref}
error={!!error}
helperText={error ? error.message : null}
variant="outlined"
margin="normal"
label={label}
type={type}
placeholder={placeholder}
/>
)}
onChange={(_, data) => {
onChange(data);
return data;
}}
/>
);
}
export default RegisteredMultiAutocomplete;
onSubmit() code
import React from "react";
import { useForm } from "react-hook-form";
import * as Yup from "yup";
import { yupResolver } from "#hookform/resolvers/yup";
import { Box, Button } from "#mui/material";
import { RegisteredMultiAutocomplete } from "./RegisteredMultiAutocomplete";
interface FormInputs {
productCategories: string[];
}
const indcat = [
{ label: "Car", value: "Car" },
{ label: "Bike", value: "Bike" },
{ label: "Bus", value: "Bus" },
{ label: "Truck", value: "Truck" },
{ label: "Van", value: "Van" },
{ label: "Jeep", value: "Jeep" },
{ label: "Tractor", value: "Tractor" }
];
export function App() {
const validateSchema = Yup.object().shape({
productCategories: Yup.array().required("Product category is required")
});
const { handleSubmit, control, reset } = useForm({
mode: "onChange",
resolver: yupResolver(validateSchema)
});
const onSubmit = (data: unknown) => {
console.log(data);
reset();
};
return (
<Box component="form" onSubmit={handleSubmit(onSubmit)}>
<RegisteredMultiAutocomplete
id="product-categories"
name="productCategories"
control={control}
label="Select Product Categories"
type="text"
placeholder="Category"
multiSelectOptions={indcat}
/>
<Button type="submit" color="primary" variant="contained">
{" "}
Submit
</Button>
</Box>
);
}
export default App;
here is the app on codesandbox
on submit the value of autocomplete gets cleared but the option label not
expecting the autocomplete option label should get cleared on reset()

Related

react-hook-form onSubmit is not working after applying redux-persist

I'm making wizard form with react-hook-form and redux tookit.
And now I'm trying to apply redux-persist to keep the form's values after refreshing or clicking to go back.
But handleSubmit is not working after I set redux-toolkit.
There's no error in console, so I'd like to know what is wrong.
Here are my codes.
store.js
import { configureStore } from '#reduxjs/toolkit';
import { formReducer } from './features/formSlice';
import storageSession from 'redux-persist/lib/storage/session';
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import thunk from 'redux-thunk';
const persistConfig = {
key: 'root',
storage: storageSession,
};
const persistedReducer = persistReducer(persistConfig, formReducer);
export const store = configureStore({
reducer: persistedReducer,
devTools: process.env.NODE_ENV !== 'production',
middleware: [thunk],
});
export const persistor = persistStore(store);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './i18n';
import { store, persistor } from './store';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root'),
);
programName.js (step 1 in my wizard form)
function ProgramName() {
const programName = useSelector((state) => state.programName);
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: programName,
});
const dispatch = useDispatch();
const history = useHistory();
const handleOnSubmit = (data) => {
console.log(Object.values(data));
dispatch(submitStep1(Object.values(data)));
history.push('./step2');
};
return (
<form onSubmit={handleSubmit(handleOnSubmit)}>
<Text size={30} weight={800} mb={10}>
Step 1. Program name
</Text>
<label htmlFor="programName">
Program Name
<input
name="programName"
{...register('programName', { required: 'fill out!' })}
/>
<ErrorMessage errors={errors} name="programName" as="p" />
</label>
<Button type="submit">next</Button>
</form>
);
}
export default ProgramName;
formSlice.js
import { createSlice } from '#reduxjs/toolkit';
export const formSlice = createSlice({
name: 'form',
initialState: {
programName: '',
targetName: '',
category: [],
tags: [],
targetLocation: '',
rewardType: '',
rewardAmount: '',
},
reducers: {
submitStep1: (state, action) => {
state.programName = action.payload;
}, //etc..
export const formReducer = formSlice.reducer;
export const {
submitStep1,
submitStep2,
submitStep3,
submitStep4,
submitStep5,
submitStep6,
submitStep7,
submitStep8,
} = formSlice.actions;
more detail example
when I click the next, handleSubmit is not working and the error message is showing underneath though I fill the input, so I cannot be into step 2 form.

AgGrid - How can i have radio button filter instead of checkbox?

I have a custom filter values such as:
filterParams: {
values: ['Admin', 'Proje Yƶneticisi', 'Muhasebe'],
defaultToNothingSelected: true,
suppressSelectAll: true
},
However, I can choose multiple values like this. But I don't want to do that, I want to choose only one value instead of multiple choices.
Is there a way to convert this checkbox filter into a radio filter?
Thanks.
You can make a custom filter and there is a video on it: https://www.youtube.com/watch?v=yO3_nTyDv6o
Create a component like this, i am dynamically looking up the options to be displayed based on the extra column parameters supplied in the column def (e.g. thats where props.meta comes in)
import { Button, Radio, RadioGroup, Stack } from "#chakra-ui/react";
import { IFilterParams } from "ag-grid-community";
import React from "react";
import { IRegistryDataColumn } from "../../../../models/RegistryDataColumn";
interface IProps extends IFilterParams {
meta?: IRegistryDataColumn;
}
interface IOption {
value: string;
label: string;
}
export const FilterRadio = React.forwardRef((props: IProps, ref) => {
const [radioOptions, setRadioOptions] = React.useState<IOption[]>([]);
const [filterState, setFilterState] = React.useState<string>();
const handleClear = () => {
setFilterState(undefined);
};
// expose AG Grid Filter Lifecycle callbacks
React.useImperativeHandle(ref, () => {
return {
isFilterActive() {
return filterState !== undefined;
},
doesFilterPass(params) {
const isPass =
params.data[props.colDef.field as string] === filterState;
return isPass;
},
getModel() {},
setModel() {},
};
});
React.useEffect(() => {
props.filterChangedCallback();
}, [filterState]);
React.useEffect(() => {
const radioOptionsUpdate: IOption[] = [];
if (props.meta?.radio_options) {
Object.entries(props.meta.radio_options).forEach(([key, value]) => {
radioOptionsUpdate.push({ value: value.value, label: value.label });
});
}
setRadioOptions(radioOptionsUpdate);
}, [props.meta?.radio_options]);
return (
<Stack p={4} spacing={6} style={{ display: "inline-block" }}>
<Button size="sm" onClick={handleClear}>
Clear filter
</Button>
<RadioGroup onChange={setFilterState} value={filterState}>
<Stack spacing={4}>
{radioOptions.map((option) => (
<Radio key={option.value} value={option.value}>
{option.label}
</Radio>
))}
</Stack>
</RadioGroup>
</Stack>
);
});
And then include it in the column definition:
newCol.filter = FilterRadio;

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.

Update values in a pair of text fields

I have a form component:
import React from 'react'
import Relay from 'react-relay'
import { browserHistory } from 'react-router'
import MenuItem from 'material-ui/MenuItem'
import TextField from 'material-ui/TextField'
import CreateTransformationSetMutation from '../mutations/CreateTransformationSetMutation'
class CreateTransformationSetDialog extends React.Component {
componentWillMount() {
this.props.setOnDialog({
onSubmit: this.onSubmit,
title: 'Create and Apply Transformation Set'
})
}
initial_state = {
targetTableName: '',
transformations: this.props.viewer.version.columns.edges.map(edge => edge.node).map(column => {
return {
targetColumnName: column.name,
ruleExpression: '{{' + column.name + '}}'
}
})
}
state = this.initial_state
onSubmit = () => {
const onSuccess = (response) => {
browserHistory.push('/table')
}
const onFailure = () => {}
Relay.Store.commitUpdate(
new CreateTransformationSetMutation(
{
viewer: this.props.viewer,
version: this.props.viewer.version,
targetTableName: this.state.targetTableName,
transformations: JSON.stringify({transformations: this.state.transformations}),
}
),
{ onSuccess: onSuccess }
)
}
handleTableNameChange = (e) => {
this.setState({
targetTableName: e.target.value
})
}
handleTransChange = (e) => {
////what should go here////
}
render() {
return (
<div>
<TextField
floatingLabelText="Table Name"
value={this.state.targetTableName}
onChange={this.handleTableNameChange}
/>
<br />
{
this.props.viewer.version.columns.edges.map((edge) => edge.node).map((column, i) =>
<div key={column.id}>
<TextField
name="targetColumnName"
floatingLabelText="Target Column"
value={this.state.transformations[i].targetColumnName}
onChange={this.handleTransChange}
style={{ margin: 12 }}
/>
<TextField
name="ruleExpression"
floatingLabelText="Rule to Apply"
value={this.state.transformations[i].ruleExpression}
onChange={this.handleTransChange}
style={{ margin: 12 }}
/>
</div>
)
}
</div>
)
}
}
export default Relay.createContainer(CreateTransformationSetDialog, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
${CreateTransformationSetMutation.getFragment('viewer')}
version(id: $modalEntityId) #include(if: $modalShow) {
${CreateTransformationSetMutation.getFragment('version')}
id
name
columns(first: 100) {
edges {
node {
id
name
}
}
}
}
}
`
},
initialVariables: {
modalEntityId: '',
modalName: '',
modalShow: true,
},
prepareVariables: ({ modalEntityId, modalName }) => {
return {
modalEntityId,
modalName,
modalShow: modalName === 'createTransformationSet'
}
}
})
Each input is a set of two material-ui TextFields that have a default value obtained from this.state.transformations. However, I am trying to update the values in these fields and I am not having any luck.
The default state of transformations will look something like this:
transformations: [
{targetColumnName: 'ID', ruleExpression: '{{ID}}'},
{targetColumnName: 'First Name', ruleExpression: '{{FirstName}}'},
{targetColumnName: 'Last Name', ruleExpression: '{{LastName}}'}
]
And I want to be able to update it so that the state can change, for example to:
transformations: [
{targetColumnName: 'ID', ruleExpression: '{{ID}}'},
{targetColumnName: 'First Name', ruleExpression: '{{FirstName}}'},
{targetColumnName: 'Percentage', ruleExpression: '{{Percentage/100}}'}
]
The form is built using Relay but that is not relevant to this question.
Any help with this would be much appreciated.
Thanks for your time.
I had a similar problem and instead I put the value in value attribute, uses defaultValue='some-value' so that it can be mutated and changed.
It seems that what you are trying to do is not possible to protect from XSS (Cross-site scripting)
Write like this:
<TextField
name="targetColumnName"
floatingLabelText="Target Column"
defaultValue={this.state.transformations[i].targetColumnName} {/* change from value to defaultValue */}
onChange={this.handleTransChange}
style={{ margin: 12 }}
/>

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

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!