I have a form that dynamically generates inputs, where one input is a material-ui TextField and SelectField with multiple options. I am having a problem with telling the select fields apart from each other though. In an ideal world I would like to be able to collect the data from both of these inputs and store them as an object (i.e. {name: Employee, type_id: 1}), which will become an array of objects depending on how many inputs are generated.
My current code looks like this:
import React from 'react';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import DatatypeStore from '../../stores/DatatypeStore';
const styles = {
customWidth: {
width: 100,
},
};
class MultipleEntry extends React.Component {
state={inputs: [], columnHeaders: [], value: 1};
addInputField(e) {
e.preventDefault();
let inputs = this.state.inputs;
inputs.push({name: null});
this.setState({inputs});
}
handleChange(e, index, value) {
const isSelectField = value !== undefined;
if (isSelectField) {
console.log(index, value);
} else {
console.log(e.target.value);
}
}
render() {
let {inputs, columnHeaders, value} = this.state;
return (
<div className="col-md-12">
{inputs.map((input, index) => {
let name = "header " + index;
return (
<div key={index}>
<br />
<TextField
hintText="Enter the name for the column"
floatingLabelText="Column Name"
type="text"
name={name}
onChange={e => this.handleChange(e)}
/>
<SelectField
value={this.state.value}
onChange={e => this.handleChange(e, index, value)}
style={styles.customWidth}
>
{DatatypeStore.getDatatypes().map(el => {
return <MenuItem key={el.id} value={el.id} primaryText={el.name} />;
})}
</SelectField>
<br />
</div>
);
})}
<br/>
<RaisedButton
style={{marginTop: 50}}
label="Add column input"
secondary={true}
onClick={e => this.addInputField(e)}
/>
<br />
</div>
);
}
}
export default MultipleEntry;
So yeah, examples doing what I would like to do would be much appreciated. If you can do it using material-ui components so much the better!
Thanks for your time
Update
Here is the parent component
import React from 'react';
import MultipleEntry from './MultipleEntry.jsx';
import Paper from 'material-ui/Paper';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import TokenStore from '../../stores/TokenStore';
const styles = {
paper: {
marginTop: 50,
paddingBottom: 50,
width: '100%',
textAlign: 'center',
display: 'inline-block',
},
};
class ColumnHeaderForm extends React.Component {
state = {title: '', input: null};
changeValue(e) {
const title = e.target.value;
this.setState({
title
});
}
handleInputChange(columnHeaderArray) {
let input = this.state.input;
input = columnHeaderArray;
this.setState({input});
}
handleFormSubmit(e) {
e.preventDefault();
let access_token = TokenStore.getToken();
let title = this.state.title;
let inputs = this.state.input;
this.props.handleFormSubmit(access_token, title, inputs);
}
render() {
let {title, input} = this.state;
return (
<div>
<Paper style={styles.paper}>
<form role="form" autoComplete="off">
<div className="text-center">
<h2 style={{padding: 10}}>Fill out the column names (you can add as many as you need)</h2>
<div className="col-md-12">
<TextField
hintText="Enter a title for the table"
floatingLabelText="Title"
type="text"
onChange={e => this.changeValue(e)}
/>
</div>
<div className="col-md-12">
<MultipleEntry handleInputChange={this.handleInputChange.bind(this)} />
</div>
<RaisedButton
style={{marginTop: 50}}
label="Submit"
primary={true}
onClick={e => this.handleFormSubmit(e)}
/>
</div>
</form>
</Paper>
</div>
);
}
}
export default ColumnHeaderForm;
well from my understanding you want to handle the TextField and SelectField onChange in the same method. They do have different signatures
TextField (event, value)
SelectField (event, index, value)
But you can achieve it easily by testing the third argument for example:
handleChange(event, index, value) {
const isSelectField = value !== undefined;
if(isSelectField) {
// do whatever you need to do with the SelectField value
} else {
// do whatever you need to do with the TextField value
}
}
Note:
You shouldn't mutate your state, that's wrong.
let columnHeaders = this.state.columnHeaders;
columnHeaders[e.target.name] = e.target.value;
To avoid it you can "clone" the state object and apply the changes there..
Object.assign({}, this.state.columnHeaders, {
[e.target.name]: event.target.value
})
Read more about Object.assign here: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
----------------------------------------------------
UPDATED EXAMPLE 26/04/2016
Now you can see I'm just changing the typeId inside the input object (that I found by its id) for SelectFields. And almost the same thing for TextField - just change the field name..
handleChange(inputId, event, index, value) {
const isSelectField = value !== undefined;
if(isSelectField) {
this.setState({
inputs: this.state.inputs.map((input) => {
return input.id === inputId ? Object.assign({}, input, {
typeId: value
}) : input
})
})
} else {
this.setState({
inputs: this.state.inputs.map((input) => {
return input.id === inputId ? Object.assign({}, input, {
name: event.target.value
}) : input
})
})
}
}
//Make sure the id is unique for each input
addInputField(e) {
e.preventDefault();
this.setState({
inputs: this.state.inputs.concat({ id: 1, name: null })
});
}
//Binding the ID in the call so we can use it in that method..
<SelectField
value={input.typeId}
onChange={this.handleChange.bind(this, input.id)}
style={styles.customWidth}
>
Related
I'm iterating through notes in a React component.
If the user wants to edit a note, I'd like to replace the note text with an input field, populate it with the current text value, handleChange, and only show the input field for that particular entry.
Right now it changes all of the entries because it's within the iteration in the component.
I'm using Semantic-ui-react.
Here's an image of the undesired results after the user clicks the edit icon:
My component looks like this:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Comment, Table, Form, Button, Icon, Input } from "semantic-ui-react";
import { createNote } from "../actions/createNoteAction";
const uuidv4 = require("uuid/v4");
class ServiceLogsComponent extends Component {
state = {
entry: "",
showInput: false
}
handleChange = (e, { name, value }) => this.setState({ [name]: value })
handleSubmit = (e) => {
e.preventDefault()
const userId = 2;
this.props.createNote(this.state.entry, this.props.serviceId, userId)
this.setState({ entry: "" })
}
handleUpdate = e => {
this.setState({ showInput: true })
}
filterNotes = () => {
const filteredNotes = this.props.notes.filter(note => note.service_id === this.props.serviceId)
return filteredNotes
}
render() {
console.log(this.state)
return (
<>
<div style={{ display: this.props.showServiceLogs }}>
<Table>
<Table.Body>
<Comment>
<Comment.Group>
{this.filterNotes().map((serviceNote) => (
<Comment.Content>
<Table.Row key={uuidv4}>
<Table.Cell>
<Comment.Author as="a">
{serviceNote.created_at}
</Comment.Author>
</Table.Cell>
<Table.Cell>
<Comment.Metadata>
{serviceNote.user.username}
</Comment.Metadata>
</Table.Cell>
{serviceNote.user.id !== 1 ? (
<Table.Cell>
<Icon
name="edit outline"
onClick={(e) => this.handleUpdate(e)}
/>
<Icon name="delete" />
</Table.Cell>
) : null}
</Table.Row>
<Table.Row>
<Table.Cell>
{!this.state.showInput ?
<Comment.Text>{serviceNote.entry}</Comment.Text>
:
<Form onSubmit={(e) => this.handleSubmit(e)}>
<Form.Input value={serviceNote.entry} name="entry" onChange={this.handleChange}>
</Form.Input>
<Button type="submit" size="small">Update</Button>
</Form>
}
</Table.Cell>
</Table.Row>
</Comment.Content>
))}
<Form onSubmit={(e) => this.handleSubmit(e)}>
<Form.TextArea
style={{ height: "50px" }}
onChange={this.handleChange}
name="entry"
value={this.state.entry}
/>
<Form.Button
type="submit"
content="Add Note"
labelPosition="left"
icon="edit"
primary
/>
</Form>
</Comment.Group>
</Comment>
</Table.Body>
</Table>
</div>
</>
);
}
}
const mapStateToProps = state => {
return {
services: state.services.services,
notes: state.notes.notes
};
};
const mapDispatchToProps = dispatch => {
return {
createNote: (entry, serviceId, userId) => dispatch(createNote(entry, serviceId, userId))
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ServiceLogsComponent);
I'm also having trouble with onChange when editing text. It doesn't change.
You have one state flag "showInput" that is shared by all of your notes, therefore when you turn it on, all of your notes go to edit mode.
In this line:
<Form.Input value={serviceNote.entry} name="entry" onChange={this.handleChange}>
The value is not updated as a result of onChange, value should be assigned to something from the state that has been updated by onChange
You should create a child component to display one note, so that each note can have its own showInput flag and value to display.
I have a input field that I apply a mask to using react-input-mask. I want to change the mask depending on a dropdown value. What is happening is when I change the dropdown value the new mask is applied on the UI but the form value does not get modified. So, when I submit the form the old mask is used. If I modify the value manually in the textbox then the form value change takes affect.
Here is an simplified example:
import React from "react";
import ReactDOM from "react-dom";
import InputMask from "react-input-mask";
import "antd/dist/antd.css";
import { Form, Select } from "antd";
const FormItem = Form.Item;
class FormComponent extends React.Component {
constructor(props) {
super(props);
this.state = { isMaskOne: true };
}
onSelectChange = e => {
if (e === "one") {
this.setState({ isMaskOne: true });
} else {
this.setState({ isMaskOne: false });
}
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form>
<FormItem>
<Select onChange={this.onSelectChange}>
<Select.Option value="one" key="one">
one
</Select.Option>
<Select.Option value="two" key="two">
two
</Select.Option>
</Select>
</FormItem>
<FormItem>
{getFieldDecorator("note")(
<InputMask
mask={this.state.isMaskOne ? "999-99-9999" : "99-9999999"}
maskChar=""
/>
)}
</FormItem>
<p>{JSON.stringify(this.props.form.getFieldValue("note"))}</p>
</Form>
);
}
}
const WrappedFormComponent = Form.create()(FormComponent);
const rootElement = document.getElementById("root");
ReactDOM.render(<WrappedFormComponent />, rootElement);
If the numbers 123-45-6789 are enter into the text box with the dropdown selection of "one" then this.props.form.getFieldValue("note") returns 123-45-6789. When I change the dropdown to "two" I would expect this.props.form.getFieldValue("note") to return 12-3456789 but the value remains 123-45-6789 even though the text has changed to the new mask. What am I not understanding?
You need to use ref to access the input masked value, then you need to update the Form.Item using setFieldsValue i.e. this.props.form.setFieldsValue({ note: this.myRef.current.value });
class FormComponent extends React.Component {
constructor(props) {
super(props);
this.state = { isMaskOne: true };
this.myRef = React.createRef();
}
onSelectChange = e => {
if (e === "one") {
this.setState({ isMaskOne: true }, () => {
console.log("ref value:", this.myRef.current.value);
this.props.form.setFieldsValue({ note: this.myRef.current.value });
});
} else {
this.setState({ isMaskOne: false }, () => {
console.log("ref value:", this.myRef.current.value);
this.props.form.setFieldsValue({ note: this.myRef.current.value });
});
}
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form>
<FormItem>
<Select onChange={this.onSelectChange}>
<Select.Option value="one" key="one">
one
</Select.Option>
<Select.Option value="two" key="two">
two
</Select.Option>
</Select>
</FormItem>
<FormItem style={{ marginTop: "100px" }}>
{getFieldDecorator("note")(
<InputMask
mask={this.state.isMaskOne ? "999-99-9999" : "99-9999999"}
maskChar=""
ref={this.myRef}
/>
)}
</FormItem>
<p>{JSON.stringify(this.props.form.getFieldValue("note"))}</p>
</Form>
);
}
}
It doesn't work correctly, I want to select only one value, but I can select one and more... and can't deselect
there is code for that function.
const RadioButtonField = ({type, options, disabled, onChange}) => {
const handleChange = (value) => {
onChange(type, value);
};
return (
<div>
{options.map(({ value, label }) =>
<Radio.Group key={value}>
<Radio
disabled={disabled}
onChange={() => handleChange(value)}
>
{label}
</Radio>
</Radio.Group>
)}
</div>
);
};
You can try this for single selection and also you can reselect too
import React from "react";
import "./styles.css";
import Radio from "#material-ui/core/Radio";
export default class App extends React.Component {
state = {
selectedValue: null,
radioOptions: [
{ id: 1, title: "1" },
{ id: 2, title: "2" },
{ id: 3, title: "3" },
{ id: 4, title: "4" }
]
};
handleChange = id => {
this.setState({
selectedValue: id
});
};
render() {
const { selectedValue, radioOptions } = this.state;
return (
<div className="App">
{radioOptions.map(option => {
return (
<div className="radio-parent">
<lable
onClick={() => this.handleChange(option.id)}
className="radio-btn"
>
<Radio
color="default"
value={option.id}
name="radioValue"
checked={selectedValue == option.id}
/>
{option.title}
</lable>
</div>
);
})}
</div>
);
}
}
codesandBox link for Demo
You can refer to the following code:
class App extends React.Component {
state = {
initialValue: "A",
options: ["A", "B", "C", "D"]
};
onChange = e => {
console.log("radio checked", e.target.value);
this.setState({
value: e.target.value
});
};
render() {
return (
<Radio.Group value={this.state.initialValue}>
{this.state.options.map((value, index) => {
return (
<Radio onChange={this.onChange} key={index} value={value}>
{" "}
{value}{" "}
</Radio>
);
})}
</Radio.Group>
);
}
}
Working codesandbox demo
I think you do not need to pass anything to the function. Whenever the option will be clicked, the event (e) object will be passed to onChange(e) and you will get the value of clicked item and checked value in onChange(e) e.target.value and e.target.checked respectively.
I have a PlaceInput component which support google place autocomplete.
class PlaceInput extends Component {
state = {
scriptLoaded: false
};
handleScriptLoaded = () => this.setState({ scriptLoaded: true });
render() {
const {
input,
width,
onSelect,
placeholder,
options,
meta: { touched, error }
} = this.props;
return (
<Form.Field error={touched && !!error} width={width}>
<Script
url="https://maps.googleapis.com/maps/api/js?key={my google api key}&libraries=places"
onLoad={this.handleScriptLoaded}
/>
{this.state.scriptLoaded &&
<PlacesAutocomplete
inputProps={{...input, placeholder}}
options={options}
onSelect={onSelect}
styles={styles}
/>}
{touched && error && <Label basic color='red'>{error}</Label>}
</Form.Field>
);
}
}
export default PlaceInput;
I also have a menu item which is an<Input> from semantic-ui-react. The frontend is like below:
The menu code is like below:
<Menu.Item>
<Input
icon='marker'
iconPosition='left'
action={{
icon: "search",
onClick: () => this.handleClick()}}
placeholder='My City'
/>
</Menu.Item>
How can I leverage the PlaceInput component to make menu <Input> box to achieve the place autocomplete? Thanks!
If you could share a working sample of your app (in e.g. codesandbox) I should be able to help you make your PlaceInput class work with the Menu.Input from semantic-ui-react.
Otherwise, you can test a fully working example of such integration with the code below, which is based off of the Getting Started code from react-places-autocomplete.
import React from "react";
import ReactDOM from "react-dom";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
import { Input, Menu } from "semantic-ui-react";
const apiScript = document.createElement("script");
apiScript.src =
"https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places";
document.head.appendChild(apiScript);
const styleLink = document.createElement("link");
styleLink.rel = "stylesheet";
styleLink.href =
"https://cdn.jsdelivr.net/npm/semantic-ui/dist/semantic.min.css";
document.head.appendChild(styleLink);
class LocationSearchInput extends React.Component {
constructor(props) {
super(props);
this.state = { address: "" };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => console.log("Success", latLng))
.catch(error => console.error("Error", error));
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<Menu>
<Menu.Item>
<Input
icon="marker"
iconPosition="left"
placeholder="My City"
{...getInputProps({
placeholder: "Search Places ...",
className: "location-search-input"
})}
/>
</Menu.Item>
</Menu>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "#fafafa", cursor: "pointer" }
: { backgroundColor: "#ffffff", cursor: "pointer" };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
ReactDOM.render(<LocationSearchInput />, document.getElementById("root"));
Hope this helps!
Set the attributes of a input field or any component by taking input from the user dynamically?
I would like to know if there is any way, where I would give user an option to choose a component from the list of components i would mention, and allow him to customize the components attributes. For example if the user chooses a Input component, he must be able to set the attributes of that particular component, like "required", "type", "placeholder".
You can achieve it by passing all attributes you want as props to the child component.
You should also add them to state of parent component with change handler.
Each time the user change something of the attributes, the state should update.
As the state updates, the new state will pass as props to child Component and it'll update.
I made a simple example to input: You can change its placeholder, minLength, and requierd.
Check This Example
in the render, method you can do something like this
render() {
// change the name and values base on your user input
userInputtedAttribName = "placeholder";
userInputtedAttribValue = "the placeholder";
// the object to contain your user defined attribute name and values
const dynamicAttributes = {
[userInputtedAttribName]: userInputtedAttribValue
};
return (
<div>
<input type="text" {...dynamicAttributes}></input>
</div>
)
}
the spread operator, {...dynamicAttributes}, will build the attributes and their values dynamically
Probably not even what you're looking for, but I made a medium-sized prototype that can show you how to create Components (input, button, textarea), dynamically.
It's like filling out a form. Choose a type of component you want to make from the select-list. Then define the attributes you want in the proceeding textboxes. Once you're done adding all the attributes, hit Generate to render your customized component.
Sandbox: https://codesandbox.io/s/dynamic-component-generator-mhuh5
Working code:
import React from "react";
import ReactDOM from "react-dom";
import Input from "./Input";
import Button from "./Button";
import TextArea from "./TextArea";
import "./styles.css";
class ComponentGenerator extends React.Component {
state = {
componentInProgress: null,
customizeMode: false,
attributeName: "",
attributeSetting: "",
boolean: false,
builtComponents: []
};
handleSelection = e => {
this.setState({
componentInProgress: { componentType: e.target.value },
customizeMode: true
});
};
createOptions = () => {
const { customizeMode, componentInProgress } = this.state;
return (
<div>
<h4>Choose a Component:</h4>
<select
onChange={this.handleSelection}
value={!customizeMode ? "Select" : componentInProgress.componentType}
>
<option>Select</option>
<option>Input</option>
<option>TextArea</option>
<option>Button</option>
</select>
</div>
);
};
handleOnChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleOnSubmit = e => {
const {
attributeName,
attributeSetting,
boolean,
componentInProgress
} = this.state;
e.preventDefault();
let componentCopy = JSON.parse(JSON.stringify(componentInProgress));
componentCopy.props = {
...componentCopy.props,
[attributeName]: boolean ? boolean : attributeSetting
};
this.setState({
componentInProgress: componentCopy,
attributeName: "",
attributeSetting: "",
boolean: false
});
};
setBoolean = boolean => {
this.setState({
boolean: boolean
});
};
generateComponent = () => {
const { componentInProgress, builtComponents } = this.state;
this.setState({
componentInProgress: null,
customizeMode: false,
builtComponents: [...builtComponents, componentInProgress]
});
};
defineComponentAttributes = () => {
const {
componentInProgress,
attributeName,
attributeSetting,
boolean
} = this.state;
return (
<div>
<h4>
Customizing:{" "}
<span className="highlight">{componentInProgress.componentType}</span>
</h4>
{/*Render form */}
<form onSubmit={this.handleOnSubmit}>
<label>Attribute: </label>
<input
className="form-group"
onChange={this.handleOnChange}
value={attributeName}
name="attributeName"
placeholder="Choose attribute (type)"
/>
<label>Definition: </label>
<input
className="form-group"
onChange={this.handleOnChange}
value={attributeSetting}
name="attributeSetting"
placeholder="Define attribute (text)"
/>
<label>This is a Boolean type: </label>
<input
type="radio"
name="boolean"
onChange={() => this.setBoolean(true)}
/>
True
<input
type="radio"
name="boolean"
checked={boolean === false}
onChange={() => this.setBoolean(false)}
/>
False
<button className="form-group" type="submit">
Add
</button>
</form>
{/*Create List of attributes */}
{componentInProgress.props && (
<div>
<h4>Defined Attributes:</h4>
{Object.entries(componentInProgress.props).map(
([propName, propValue]) => {
return (
<div key={propName}>
<span>{propName}: </span>
<span>{propValue + ""}</span>
</div>
);
}
)}
</div>
)}
<div>
<h4>Click to finish and generate:</h4>
<button onClick={this.generateComponent}>Generate</button>
</div>
</div>
);
};
renderComponents = () => {
const { builtComponents } = this.state;
return builtComponents.map((component, index) => {
let renderedComponent = () => {
switch (component.componentType) {
case "Input":
return <Input {...component.props} />;
case "Button":
return <Button {...component.props} />;
case "TextArea":
return <TextArea {...component.props} />;
default:
return null;
}
};
return (
<div key={index} className="componentSection">
<h4>{component.componentType}</h4>
{renderedComponent()}
<div>
<p>Attributes: </p>
{Object.entries(component.props).map(([propName, propValue]) => {
return (
<div key={propName}>
<span>{propName}: </span>
<span>{propValue + ""}</span>
</div>
);
})}
</div>
</div>
);
});
};
render() {
const { customizeMode } = this.state;
return (
<div>
{this.createOptions()}
{customizeMode && this.defineComponentAttributes()}
<div className="components">{this.renderComponents()}</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<ComponentGenerator />, rootElement);