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 }}
/>
Related
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()
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;
I want to get AutoComplete onChange data. I got data from onChange((e,value) => console.log(value).
But how to put data on State .
This AutoComplete component behaviour is (Creatable and selectable) . I got all data on onChange but on in the state, If I set the data on State , selectOnFocus is not working.
How to get rid of that?
Thank you
`
<Autocomplete
multiple
limitTags={5}
onChange={(event, newValue) => {
setToEmails(newValue);
}}
filterOptions={(options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== "") {
filtered.push({
inputValue: params.inputValue,
label: `Add "${params.inputValue}"`,
value: nanoid(7),
});
}
return filtered;
}}
selectOnFocus
blurOnSelect
clearOnBlur
handleHomeEndKeys
id="free-solo-with-text-demo"
options={toBlockers}
getOptionLabel={(option) => {
if (typeof option === "string") {
return option;
}
if (option.inputValue) {
return option.inputValue;
}
return option.label;
}}
renderOption={(option) => option.label}
freeSolo
renderInput={(params) => (
<TextField {...params} label="Blockers" variant="outlined" />
)}
/>
`
If I use this , Selectable is not working.
Later on I got solved by myself . This took about 3 days ,
here is full example of Creatable with selectable with formData thing.
const [toEmails, setToEmails] = useState([]);
//if you want to post this data on formData , you have structured this way
const appendedObject = [
{ label: "_creative", value: "node_process_Lh1L8RsafJbUas" },
{ label: "Azim__bro", value: "node_process_LhL1s8RaafJbUas" },
];
const finalBlockers = [...appendedObject, ...toEmails];
formData.append("attachmentList", []);
console.log({ toEmails, finalBlockers });
if (finalBlockers) {
for (const blocker of finalBlockers) {
console.log({ blocker });
formData.append("blockers", JSON.stringify(blocker));
}
}
<Autocomplete
multiple
limitTags={5}
getOptionSelected={(option, value) => {
const optionTitle =
typeof option === "string" ? option : option.value;
const valueTitle =
typeof value === "string" ? value : value.value;
return optionTitle === valueTitle;
}}
onChange={(event, newValue) => {
if (Array.isArray(newValue)) {
const updatedArrayValue = newValue.filter((e) =>
typeof e === "string" ? e.trim() : e
);
const newArrayValue = [...updatedArrayValue];
const updatedArray = newArrayValue.map((item) => {
if (typeof item === "string") {
const newItem = {};
newItem.label = item;
newItem.value = nanoid(12);
newItem.type = "created";
return newItem;
}
return item;
});
setToEmails(updatedArray);
}
}}
filterOptions={(options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== "") {
filtered.push({
inputValue: params.inputValue,
title: `Add "${params.inputValue}"`,
value: nanoid(12),
type: "created",
});
}
return filtered;
}}
selectOnFocus
clearOnBlur
handleHomeEndKeys
id="free-solo-with-text-demo"
options={toBlockers}
filterSelectedOptions
getOptionLabel={(option) => {
// Value selected with enter, right from the input
if (typeof option === "string") {
return option;
}
// Add "xxx" option created dynamically
if (option.inputValue) {
return option.inputValue;
}
// Regular option
return option.label;
}}
renderOption={(option) => option.label}
freeSolo
renderInput={(params) => (
<TextField {...params} label="Blockers" variant="outlined" />
)}
/>
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.
I have a group of form inputs that are produced like so:
Please see UPDATE 2 below for full component.
So, if there are three columns then three of this group will be shown in the form. I am trying to extract the data from these inputs but I only need the ids. How can I extract the column id from the TextField?
Also, I need to get the data (i.e. the ids) per group so that they appear in an array:
transformations: [{columnId: 1, ruleId: 4}, {columnId: 2, ruleId: 2}, {columnId:3 , ruleId: 1}]
These are just example values, the main problem as I mentioned, is that I'm not sure how to extract the value of the columnId from the first input. I'm also struggling with getting the multiple sets of data.
Any help with this problem would be much appreciated.
Thanks for your time.
UPDATE:
handleRuleChange looks like this:
handleRuleChange = (e, index, value) => {
this.setState({
ruleId: value
})
}
UPDATE 2:
Here is the component:
import React from 'react'
import Relay from 'react-relay'
import { browserHistory } from 'react-router'
import SelectField from 'material-ui/SelectField'
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: '',
ruleId: 'UnVsZTo1',
}
state = this.initial_state
onSubmit = () => {
const onSuccess = (response) => {
console.log(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: ///this is where I need to get the values///,
}
),
{ onSuccess: onSuccess }
)
}
handleTextChange = (e) => {
this.setState({
targetTableName: e.target.value
})
}
handleRuleChange = (e, index, value) => {
this.setState({
ruleId: value
})
}
render() {
return (
<div>
<TextField
floatingLabelText="Table Name"
value={this.state.targetTableName}
onChange={this.handleTextChange}
/>
<br />
{
this.props.viewer.version.columns.edges.map((edge) => edge.node).map((column) =>
<div key={column.id}>
<TextField
id={column.id}
floatingLabelText="Column"
value={column.name}
disabled={true}
style={{ margin: 12 }}
/>
<SelectField
floatingLabelText="Select a Rule"
value={this.state.ruleId}
onChange={this.handleRuleChange}
style={{ margin: 12 }}
>
{
this.props.viewer.allRules.edges.map(edge => edge.node).map(rule =>
<MenuItem
key={rule.id}
value={rule.id}
primaryText={rule.name}
/>
)
}
</SelectField>
</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
}
}
}
}
allRules(first: 100) {
edges {
node {
id
name
}
}
}
}
`
},
initialVariables: {
modalEntityId: '',
modalName: '',
modalShow: true,
},
prepareVariables: ({ modalEntityId, modalName }) => {
return {
modalEntityId,
modalName,
modalShow: modalName === 'createTransformationSet'
}
}
})
It is using Relay but that isn't connected to the question, just need to extract the data from the inputs into the transformations array.
This can meet your requirement. Most of code will be understandable. feel free to ask for queries.
class CreateTransformationSetDialog extends React.Component {
componentWillMount() {
this.props.setOnDialog({
onSubmit: this.onSubmit,
title: 'Create and Apply Transformation Set'
})
}
initial_state = {
targetTableName: '',
transformations: [];
ruleId:'UnVsZTo1' //default values for all rules
}
state = this.initial_state
onSubmit = () => {
const onSuccess = (response) => {
console.log(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: this.state.transformations,
}
),
{ onSuccess: onSuccess }
)
}
handleTextChange = (e) => {
this.setState({
targetTableName: e.target.value
})
}
handleRuleChange = (index, ruleId, columnId) => { //TODO: make use of index if needed
let transformations = this.state.transformations;
const isInStateWithIndex = transformations.findIndex((el) => el.columnId === columnId);
if(isInStateWithIndex > -1){
transformations[isInStateWithIndex].ruleId = ruleId; //changed rule
}else{
transformations.push({columnId: columnId, ruleId: ruleId}); //new column added to state.
}
this.setState({
transformations: transformations
}); //do with es6 spread operators to avoid immutability if any
}
render() {
return (
<div>
<TextField
floatingLabelText="Table Name"
value={this.state.targetTableName}
onChange={this.handleTextChange}
/>
<br />
{
this.props.viewer.version.columns.edges.map((edge) => edge.node).map((column) =>
<div key={column.id}>
<TextField
id={column.id}
floatingLabelText="Column"
value={column.name}
disabled={true}
style={{ margin: 12 }}
/>
<SelectField
floatingLabelText="Select a Rule"
value={this.state.ruleId}
onChange={(e, index, value) => this.handleRuleChange(index, value, column.id )}
style={{ margin: 12 }}
>
{
this.props.viewer.allRules.edges.map(edge => edge.node).map(rule =>
<MenuItem
key={rule.id}
value={rule.id}
primaryText={rule.name}
/>
)
}
</SelectField>
</div>
)
}
</div>
)
}
}
Maintaining the state for transformations in state with dynamically created columns.