Generate React form fields from JSON - reactjs

i am trying to generate a form from JSON config. I am parsing the JSON and using map functions to generate material UI TextField components.
But issue is that the generated components do not get rendered, instead the whole JS code appears on screen. Not sure why.
here is my code in 2 files:
FormConfig.js:
"Form1": {
"fields": [
{
uiElement: "TextField",
id: '"standard-name"',
name: "'asdada'",
className: "{classes.textField}",
value: "{this.state.name}",
onChange: '{this.handleChange("name")}',
required: true,
margin: '"normal"'
},
{
uiElement: "TextField",
id: '"standard-uncontrolled"',
name: '"asda"',
label: '"Required"',
className: '"{classes.textField}"',
value: '"asd"',
onChange: "{}",
required: true,
margin: '"normal"'
}
]
},
"OtherForm":
{
"fields": [{}, {}]
}
}
const getForm = formName => {
return FormConfig[formName].fields.map(field => `<${field.uiElement} `+
Object.keys(field).filter(k => k !== 'uiElement')
.map(k => {
return k + "=" + field[k];
})
.join(" ") + `/>`
)
}
export default getForm;
TestForm.js
class TextFields extends React.Component {
state = {
name: 'Cat in the Hat',
age: '',
multiline: 'Controlled',
currency: 'EUR',
};
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
render() {
const { classes } = this.props;
return (
<form className={classes.container} noValidate autoComplete="off">
{
getForm("Form1")
}
<TextField
required
id="standard-required"
label="Required"
defaultValue="Hello World"
className={classes.textField}
margin="normal"
/>
I was expecting that the call to getForm() would have rendered my fields, but instead it spits out this on the web page. Am I doing something wrong?
<TextField id="standard-name" name='asdada' className={classes.textField} value={this.state.name} onChange={this.handleChange("name")} required=true margin="normal"/><TextField id="standard-uncontrolled" name="asda" label="Required" className="{classes.textField}" value="asd" onChange={} required=true margin="normal"/><TextField id="standard-read-only-input" name="asd" label="Read Only" className={classes.textField} value="asd" onChange={} required=false margin="normal" InputProps={{readOnly: true}}/><TextField id="standard-dense" name="w3rg" label="Dense" className={classNames(classes.textField, classes.dense)} value="sdas" onChange={} required=false margin="dense"/>

try to return the component at mapping:
const getForm = formName => {
return FormConfig[formName].fields.map(field => evalComponent(field))
}
const evalComponent = field => {
let { uiElement, ...props } = field
switch(uiElement) {
case 'TextField':
return <TextField {...props}/>
default:
return false
}
}

Related

how handle multiple inputs thats created dynamically from object react native

i have a file contain objects data.js.
components: [
{
id: "1",
nameC: "name",
type: "TextInput",
options: { placeholder: "saisir nom", required: true },
},
{
id: "2",
nameC: "Phone",
type: "Phone",
options: { placeholder: "saisir number", required: false },
},
{
id: "3",
nameC: "name",
type: "TextInput",
options: { placeholder: "saisir nom", required: true },
},
i got those objects to create inputs Dynamically in this file TemplateScreen.js .
<>
{getData.length === 0 ? (
<Empty />
) : (
getData.map((item, index) => {
switch (item.type) {
case "TextInput":
return (
<>
<InputText
ModuleName={item.nameC}
placeholder={item.options.placeholder}
required={item.options.required}
/>
</>
);
case "Phone":
return (
<>
<Phone
ModuleName={item.nameC}
placeholder={item.options.placeholder}
required={item.options.required}
/>
</>
);
default:
return <Text>Nothing hear</Text>;
}
})
)}
</>
i render inputs successfully but i'can't handle those inputs :( .i'm tried many methods but anyone worked for me.i am tried many tricks from reactjs tuto but nothing worked for me .i'm blocked 4 days in this problem,please anyone can help me :(
this file contain TextInput component, i am called it in templateScreen.js
Phone component is the same as TextInput component with a bit of difference
export const InputText = (props) => {
const [state, setState] = React.useState("");
return (
<View style={styles.container} key={props.keys}>
<View style={styles.Namecontainer}>
<Text style={styles.moduleName}>{props.ModuleName}</Text>
{props.required ? <Text style={styles.required}>*</Text> : <></>}
</View>
<TextInput
{...props}
value={state}
onChangeText={(text) => setState(text)}
placeholder={props.placeholder}
style={styles.inputtext}
/>
</View>
);
};```
Instead of placing state inside the Input text and Phone text component, why not just use useRef hook inside the template screen js? We should generate refs depending on the length of the data, thus
const inputRefs = getData.reduce((acc,curr)=>{
const ref = useRef(“”);
acc[curr.nameC] = ref;
return acc:
}, {});
this will generate refs for each of your inputs. Now in our map method, we just place our input refs to each ex:
<InputText
inputRefs[item.nameC]
ModuleName={item.nameC}
placeholder={item.options.placeholder}
required={item.options.required}
/>
In order to get their values, map the inputRefs and try console.log(inputRef.current) to see.
I have created the replica in reactjs because react-native is not set up on my PC but the logic remains the same, as I correctly understand what you wanna do is render dynamic input form according to the data this is provided to you and store the value in the state
import React, { useEffect, useState } from "react";
const components = [
{
id: "1",
nameC: "name",
type: "TextInput",
options: { placeholder: "saisir nom", required: true },
},
{
id: "2",
nameC: "phone",
type: "Phone",
options: { placeholder: "saisir number", required: false },
},
{
id: "3",
nameC: "city",
type: "TextInput",
options: { placeholder: "saisir nom", required: true },
},
];
const DynamicInput = () => {
const [field, setField] = useState();
const handleChange = (event) => {
setField({ ...field, [event.target.name]: event.target.value });
};
useEffect(() => {
let obj = {};
components.forEach((item, index) => {
obj = { ...obj, [item.nameC]: "" };
});
setField(obj);
}, []);
console.log(field)
return (
<div>
{!field ? (
<div>Loading...</div>
) : (
components.map((item) => {
const value = field[item.nameC];
switch (item.type) {
case "TextInput":
return (
<InputText
key={item.nameC}
value={value}
onChangeHandler={handleChange}
placeholder={item.options.placeholder}
isRequired={item.options.required}
name={item.nameC}
/>
);
case "Phone":
return <div key={item.nameC}>This will be same as input</div>;
default:
return <div key={item.nameC}>Nothing hear</div>;
}
})
)}
</div>
);
};
export default DynamicInput;
export const InputText = ({
value,
onChangeHandler,
placeholder,
isRequired,
name,
}) => {
return (
<input
value={value}
name={name}
onChange={onChangeHandler}
placeholder={placeholder}
required={isRequired}
/>
);
};

RTL - test expect(onChange).toHaveBeenCalledWith({ name, value }) not working

Im using Material-Ui & Typescript.
I`m testing the 'onChange' input component and it does not working with value.
Form component wraps the input component and it holds the input state.
const Form: React.FC = () => {
const [state, setState] = useState({ name: '', email: '' });
const { name, email } = state;
const onChange = ({ target: { name, value }}:IOnChange) => setState({ ...state, [name]: value });
const onSubmit = (e: any) => {
e.preventDefault();
};
return (
<Grid xs={12} container item component='form' onSubmit={onSubmit} noValidate>
<Input
label={'Name'}
name='name'
value={name}
onChange={onChange}
type='text'
/>
<Input
label={'Email'}
name='email'
value={email}
onChange={onChange}
type='email'
/>
</Grid>
);
};
export default Form;
this is the Input component:
const Input: React.FC<IProps> = ({ label, name, value, onChange, type }) => (
<TextField
label={label}
name={name}
value={value}
onChange={e => onChange(e)}
type={type}
variant="outlined"
inputProps={{
"data-testid": `input-${name}`,
}}
/>
);
interface IProps {
label: string
name: string
value: string
onChange: (e:React.InputHTMLAttributes<HTMLInputElement> & { target: { name: string, value: string }}) => void
type: string
};
export default Input;
And this is my test:
import { cleanup, fireEvent, render } from "#testing-library/react";
const props = { label: 'label', name: 'name', value: '', onChange: jest.fn(), type:'text' };
const { name, onChange } = props
describe("<Input />", () => {
describe("onChange", () => {
test("function call", () => {
const { getByTestId } = render(<Input {...props} />);
const e = expect.objectContaining({ target: expect.objectContaining({ name, value: 'value here' })});
fireEvent.change(getByTestId('input-name'), { target: { value: 'value here', name }});
this works
expect(onChange).toHaveBeenCalledTimes(1);
here is the error:
expect(onChange).toHaveBeenCalledWith(e);
Expected: ObjectContaining {"target": ObjectContaining {"name": "name", "value": "value here"}}
Received: {"_reactName": "onChange", "_targetInst": null, "bubbles": true, "cancelable": false, "currentTarget": null, "defaultPrevented": false, "eventPhase": 3, "isDefaultPrevented": [Function functionThatReturnsFalse], "isPropagationStopped": [Function functionThatReturnsFalse], "isTrusted": false, "nativeEvent": {"isTrusted": false},
"target": <input aria-invalid="false" class="MuiInputBase-input MuiOutlinedInput-input" data-testid="input-name" id="outlined-label" name="name" type="text" value="" />, "timeStamp": 1624028430742, "type": "change"}
});
});
});
The value on input is empty.
I tried whit userEvent and still not working.
How can i test onChange to be called with the new value?
Thank you..

How to set an object in state from values set in textfield Material UI

I tried a lot to find the answer but failed. please help me.
First, a working example
this.state = {
module:'',
description: '',
status: '',
redirectToModule: false,
error: ''
}
-----------------
handleChange = name => event => {
this.setState({[name]: event.target.value})
}
Render section has
<TextField id="name" label="Module Name" className={classes.textField} value={this.state.module} onChange={this.handleChange('module')} margin="normal"/><br/>
<TextField id="description" type="text" label="Description" className={classes.textField} value={this.state.description} onChange={this.handleChange('description')} margin="normal"/><br/>
<TextField id="status" type="text" label="Status" className={classes.textField} value={this.state.status} onChange={this.handleChange('status')} margin="normal"/>
The above works successfully.
However, I require to set the state object like this
this.state = {
module: {
module: '',
description: '',
status: ''
},
redirectToModule: false,
error: ''
}
I thought I should mention for example 'module.status' instead of 'module' in onChange={this.handleChange('')} BUT THEN
Should I change the function handleChange, something like this..?
this.setState({module.[name]: event.target.value});
What should be the correct way...?
please help me. I am quite new to react but I grasped a lot in a short while. To be frank, I am still a novice.
The whole file is as below...without import and style sections
class EditModule extends Component {
constructor({match}) {
super();
// this.state = {
// module: '',
// description: '',
// status: '',
// roles:[],
// redirectToModule: false,
// error: ''
// }
this.state = {
module: {
module: '',
description: '',
status: '',
roles:[]
},
redirectToModule: false,
error: ''
}
this.match = match
}
componentDidMount = () => {
read({
moduleId: this.match.params.moduleId
}).then((data) => {
if (data.error) {
this.setState({error: data.error});
} else {
this.setState({
module: {
module: data.module,
description: data.description,
status: data.status,
roles: data.roles
}
});
}
})
};
clickSubmit = () => {
const module = {
module: this.state.module || undefined,
description: this.state.description || undefined,
status: this.state.status || undefined,
roles: this.state.roles || undefined
}
update({moduleId: this.match.params.moduleId}, module)
.then((data) => {
if (data.error) {
this.setState({error: data.error})
} else {
this.setState({'moduleId': data._id, 'redirectToModule': true});
}
}
);
};
handleChange = name => event => {
this.setState( { module: JSON.stringify( { [name]: event.target.value } ) } )
};
render() {
const {classes} = this.props
if (this.state.redirectToModule) {
return (<Redirect to={'/module/' + this.state.moduleId}/>)
}
return (
<Card className={classes.card}>
<CardContent>
<Typography type="headline" component="h2" className={classes.title}>
Edit Module
</Typography>
<TextField id="name" label="Module Name" className={classes.textField} value={this.state.module.module} onChange={this.handleChange('module')} margin="normal"/><br/>
<TextField id="description" type="text" label="Description" className={classes.textField} value={this.state.module.description} onChange={this.handleChange('description')} margin="normal"/><br/>
<TextField id="status" type="text" label="Status" className={classes.textField} value={this.state.module.status} onChange={this.handleChange('status')} margin="normal"/>
<br/> {
this.state.error && (<Typography component="p" color="error">
<Icon color="error" className={classes.error}>error</Icon>
{this.state.error}
</Typography>)
}
</CardContent>
<CardActions>
<Button color="primary" variant="raised" onClick={this.clickSubmit} className={classes.submit}>Submit</Button>
</CardActions>
</Card>
)
}
}
EditModule.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(styles)(EditModule)
#Avinash
Please change your handleChange function like this.
handleChange = name => event => {
this.setState({module[name]: event.target.value})
}

<Select> component from material-ui not updating changes

The dropdown looks and works fine, but will not update a new selection and I get the following error message:
Warning: Use the `defaultValue` or `value` props on <select> instead of setting `selected` on <option>.
Selected is not set on <option> as far as I'm aware, so it must be set by material-ui's component. Anyway, if I change value into defaultValue, I get the following error message:
aterial-UI: the `value` property is required when using the `Select` component with `native=false` (default).
I thought it was a problem in Material-ui itself, but their example works fine, although there are no parent/child components in the example like mine.
Example:
https://codesandbox.io/s/7yk922om7x
My code (shortened for brevity):
constructor(props) {
super(props)
this.state = {
languageValues: {
htmlFor: 'lstLanguages',
value: 'english',
input: <Input name="language" id="lstLanguages"/>,
options: [
{ value: 'dutch', text: 'Nederlands' },
{ value: 'french', text: 'Français' },
{ value: 'english', text: 'English' },
{ value: 'german', text: 'Deutsch' }],
helperText: 'Choose your language',
}
}
}
handleChange = event => {
event.preventDefault();
this.setState({ [event.target.name]: event.target.value });
}
render() {
return (
<div>
<h2 id="speechTitle">Speech Settings</h2>
<hr/>
<FormGroup column='true'>
<DropDown
dataToFill={ this.state.languageValues }
onChange={ this.handleChange.bind(this) }
/>
Dropdown.js:
const DropDown = ({dataToFill}) => {
const menuItemValueList = dataToFill.options.map((item, i) => {
return <MenuItem value={ item.value } key={ i }>{ item.text }</MenuItem> //Always provide a key
})
return (
<FormGroup column='true'>
<FormControl>
<InputLabel htmlFor={ dataToFill.htmlFor }>Languages</InputLabel>
<Select
defaultValue={ dataToFill.value }
input={ dataToFill.input }
>
{ menuItemValueList }
</Select>
<FormHelperText>{ dataToFill.helperText }</FormHelperText>
</FormControl>
</FormGroup>
);
}
EDIT 1
I think I have found the problem: The handleChange function expects [event.target.name] but the value that needs changing is nested in the state, I'm not sure how I can access it...
Event handler code:
handleChange = event => {
this.setState({ [event.target.name]: event.target.value })
}
Nested state object:
languageValues: {
htmlFor: 'lstLanguages',
value: 'english',
input: <Input name="language" id="lstLanguages"/>,
options: [
{ value: 'dutch', text: 'Nederlands' },
{ value: 'french', text: 'Français' },
{ value: 'english', text: 'english' },
{ value: 'german', text: 'German' }
],
helperText: 'Choose your language',
},

semantic ui react Setting dropdown value to state

how to have dropdowns selected value in state.here is my code iam getting value for name field but dropdown not working, can anyone find out what i am missing?
MyComponent.js
import React,{Component} from 'react';
class MyComponent extends Component{
state={
data:{
name:'',
subject:''
}
}
onChange = e =>
this.setState({
data: { ...this.state.data, [e.target.name]: e.target.value }
},()=>{
console.log(this.state.data);
}
)
render(){
const {data}=this.state;
const subjects= [
{text: '1',value: 'kannada'},
{text: '2', value: 'english'},
{text: '3',value: 'hindhi'}
]
return(
<div>
<Input
name="name"
onChange={this.onChange}
placeholder='Your name ...'
/>
<Dropdown
placeholder='Select Subject'
name="subject"
onChange={this.onChange}
selection
options={subjects}
/>
</div>
)
}
}
export default MyComponent;
how to have selected dropdown value in state?, iam getting changed value for name field but for dropdown not getting.
handleChange = (e, { value }) => this.setState({ value })
Add value prop to Dropdown
render(
const { value } = this.state;
return(
<Dropdown
placeholder='Select Subject'
name="subject"
onChange={this.handleChange}
selection
options={subjects}
value={value}
/>)
)
If anyone is using react hooks and semantic ui react, this is how I got it to work, without having to create a separate change handler function for it.
const options = [
{ key: "1", text: "Speaker", value: "SPEAKER" },
{ key: "2", text: "Event Planner", value: "EVENT_PLANNER" }
];
const [values, setValues] = useState({
firstName: "",
userType: ""
});
const onChange = (event, result) => {
const { name, value } = result || event.target;
setValues({ ...values, [name]: value });
};
<Form.Dropdown
placeholder="I am a.."
name="userType"
label="User Type"
selection
onChange={onChange}
options={options}
value={values.userType}
/>
What kept throwing me off was the 'result' that the onChange function takes as an argument. Since the options are stored as objects in an array, the correct way to access their values is with the 'result' and not 'event.target.'
One more way to use DropDown in React Semantic. it worked perfectly for me.
const options = [
{ key: 'ex1', text: 'Example 1', value: 'Example 1' },
{ key: 'ex2', text: 'Example 2', value: 'Example 2' },
]
Method to set value
handleSelectChange=(e,{value})=>this.setState({stateValue:value})
In Form
<Form.Select fluid label='List Example' options={options}
placeholder='List Example'
value={value}
onChange={this.handleSelectChange} />
You can use the Form.Field also.
For more information.
constructor(props) {
super(props);
this.state={
subject : ""
}
}
handleChange = (e, result) => {
const { name, value } = result;
this.setState({
[name]: value
});
};
render() {
const options = [
{ key: 'ex1', text: 'Example 1', value: 'Example 1' },
{ key: 'ex2', text: 'Example 2', value: 'Example 2' },
];
return(
<div>
<Form>
<Form.Field
placeholder="Subject"
name="subject"
label="Subject"
control={Dropdown}
fluid
selection
onChange={this.handleChange}
options={options}
value={this.state.subject}
/>
</Form>
</div>
);
}

Resources