I create form, I have several TextField, DropDownMenu material-ui components included, question is how I can collect all data from all TextFields, DropDownMenus in one obj and sent it on server. For TextField it has TextField.getValue() Returns the value of the input. But I can`t understand how to use it.
var React = require('react'),
mui = require('material-ui'),
Paper = mui.Paper,
Toolbar = mui.Toolbar,
ToolbarGroup = mui.ToolbarGroup,
DropDownMenu = mui.DropDownMenu,
TextField = mui.TextField,
FlatButton = mui.FlatButton,
Snackbar = mui.Snackbar;
var menuItemsIwant = [
{ payload: '1', text: '[Select a finacial purpose]' },
{ payload: '2', text: 'Every Night' },
{ payload: '3', text: 'Weeknights' },
{ payload: '4', text: 'Weekends' },
{ payload: '5', text: 'Weekly' }
];
var menuItemsIcan = [
{ payload: '1', text: '[Select an objective]' },
{ payload: '2', text: 'Every Night' },
{ payload: '3', text: 'Weeknights' },
{ payload: '4', text: 'Weekends' },
{ payload: '5', text: 'Weekly' }
];
var menuItemsHousing = [
{ payload: '1', text: '[Select housing]' },
{ payload: '2', text: 'Every Night' },
{ payload: '3', text: 'Weeknights' },
{ payload: '4', text: 'Weekends' },
{ payload: '5', text: 'Weekly' }
];
var menuItemsIlive = [
{ payload: '1', text: '[Select family mambers]' },
{ payload: '2', text: 'Every Night' },
{ payload: '3', text: 'Weeknights' },
{ payload: '4', text: 'Weekends' },
{ payload: '5', text: 'Weekly' }
];
var menuItemsLifestyle = [
{ payload: '1', text: '[Select lifestyle]' },
{ payload: '2', text: 'Every Night' },
{ payload: '3', text: 'Weeknights' },
{ payload: '4', text: 'Weekends' },
{ payload: '5', text: 'Weekly' }
];
var menuItemsLifestyle2 = [
{ payload: '1', text: '[Select savings]' },
{ payload: '2', text: 'Every Night' },
{ payload: '3', text: 'Weeknights' },
{ payload: '4', text: 'Weekends' },
{ payload: '5', text: 'Weekly' }
];
var menuItemsIncome = [
{ payload: '1', text: '[Select your yearly income]' },
{ payload: '2', text: 'Every Night' },
{ payload: '3', text: 'Weeknights' },
{ payload: '4', text: 'Weekends' },
{ payload: '5', text: 'Weekly' }
];
var Content = React.createClass({
getInitialState: function() {
return {
//formData: {
// name: '',
// age: '',
// city: '',
// state: ''
//},
errorTextName: '',
errorTextAge: '',
errorTextCity: '',
errorTextState: ''
};
},
render: function() {
return (
<div className="container-fluid">
<div className="row color-bg"></div>
<div className="row main-bg">
<div className="container">
<div className="mui-app-content-canvas page-with-nav">
<div className="page-with-nav-content">
<Paper zDepth={1}>
<h2 className="title-h2">Now, what would you like to do?</h2>
<Toolbar>
<ToolbarGroup key={1} float="right">
<span>I want to</span>
<DropDownMenu
className="dropdown-long"
menuItems={menuItemsIwant}
//autoWidth={false}
/>
</ToolbarGroup>
</Toolbar>
<div className="clearfix"></div>
<Toolbar>
<ToolbarGroup key={2} float="right">
<span>So I can</span>
<DropDownMenu
className="dropdown-long"
menuItems={menuItemsIcan}
//autoWidth={false}
/>
</ToolbarGroup>
</Toolbar>
<h2 className="title-h2">Please, share a little about you.</h2>
<div className="clearfix"></div>
<Toolbar>
<ToolbarGroup key={3} float="right">
<span>I am</span>
<TextField
id="name"
className="text-field-long"
ref="textfield"
hintText="Full name"
errorText={this.state.errorTextName}
onChange={this._handleErrorInputChange}
/>
<span>and I am</span>
<TextField
id="age"
className="text-field-short"
ref="textfield"
hintText="00"
errorText={this.state.errorTextAge}
onChange={this._handleErrorInputChange}
/>
<span className="span-right-measure">years of age.</span>
</ToolbarGroup>
</Toolbar>
<div className="clearfix"></div>
<Toolbar>
<ToolbarGroup key={4} float="right">
<span>I</span>
<DropDownMenu
hintText="I"
menuItems={menuItemsHousing}
//autoWidth={false}
/>
<span>in</span>
<TextField
id="city"
ref="textfield"
className="text-field-long"
hintText="City"
errorText={this.state.errorTextCity}
onChange={this._handleErrorInputChange}
/>
<span>,</span>
<TextField
id="state"
ref="textfield"
className="text-field-short text-field-right-measure"
hintText="ST"
errorText={this.state.errorTextState}
onChange={this._handleErrorInputChange}
/>
</ToolbarGroup>
</Toolbar>
<div className="clearfix"></div>
<Toolbar>
<ToolbarGroup key={5} float="right">
<span>Where I live</span>
<DropDownMenu
className="dropdown-long"
menuItems={menuItemsIlive}
//autoWidth={false}
/>
</ToolbarGroup>
</Toolbar>
<div className="clearfix"></div>
<Toolbar>
<ToolbarGroup key={6} float="right">
<span>My lifestyle is</span>
<DropDownMenu
className="dropdown-short"
menuItems={menuItemsLifestyle}
//autoWidth={false}
/>
<span>and I've saved</span>
<DropDownMenu
className="dropdown-short"
menuItems={menuItemsLifestyle2}
//autoWidth={false}
/>
</ToolbarGroup>
</Toolbar>
<div className="clearfix"></div>
<Toolbar>
<ToolbarGroup key={7} float="right">
<span>My yearly household is about</span>
<DropDownMenu
className="dropdown-mobile"
menuItems={menuItemsIncome}
//autoWidth={false}
/>
</ToolbarGroup>
</Toolbar>
<div className="clearfix"></div>
<div className="button-place">
<FlatButton
onTouchTap={this._handleClick}
label="I'm done lets go!"
/>
<Snackbar
ref="snackbar"
message="Invalid input, please check and try again"
/>
</div>
</Paper>
</div>
</div>
</div>
</div>
</div>
);
},
_handleErrorInputChange: function(e) {
if (e.target.id === 'name') {
var name = e.target.value;
this.setState({
//name: name,
errorTextName: e.target.value ? '' : 'Please, type your Name'
});
} else if (e.target.id === 'age') {
var age = e.target.value;
this.setState({
//age: age,
errorTextAge: e.target.value ? '' : 'Check Age'
});
} else if (e.target.id === 'city') {
var city = e.target.value;
this.setState({
//city: city,
errorTextCity: e.target.value ? '' : 'Type City'
});
} else if (e.target.id === 'state') {
var state = e.target.value;
this.setState({
//state: state,
errorTextState: e.target.value ? '' : 'Type State'
});
}
},
_handleClick: function(e) {
this.refs.snackbar.show();
//TODO: find a way to change errorText for all empty TextField
if (this.refs.textfield && this.refs.textfield.getValue().length === 0) {
this.setState({
errorTextState: 'Type State',
errorTextCity: 'Type City',
errorTextAge: 'Check Age',
errorTextName: 'Please, type your Name'
});
}
}
});
module.exports = Content;
I want sent it on server in _handleClick method.
Add an onChange handler to each of your TextField and DropDownMenu elements. When it is called, save the new value of these inputs in the state of your Content component. In render, retrieve these values from state and pass them as the value prop. See Controlled Components.
var Content = React.createClass({
getInitialState: function() {
return {
textFieldValue: ''
};
},
_handleTextFieldChange: function(e) {
this.setState({
textFieldValue: e.target.value
});
},
render: function() {
return (
<div>
<TextField value={this.state.textFieldValue} onChange={this._handleTextFieldChange} />
</div>
)
}
});
Now all you have to do in your _handleClick method is retrieve the values of all your inputs from this.state and send them to the server.
You can also use the React.addons.LinkedStateMixin to make this process easier. See Two-Way Binding Helpers. The previous code becomes:
var Content = React.createClass({
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return {
textFieldValue: ''
};
},
render: function() {
return (
<div>
<TextField valueLink={this.linkState('textFieldValue')} />
</div>
)
}
});
Here all solutions are based on Class Component,
but i guess most of the people who learned React recently (like me),
at this time using functional Component.
So here is the solution based on functional component.
Using useRef hooks of ReactJs and inputRef property of TextField.
import React, { useRef, Component } from 'react'
import { TextField, Button } from '#material-ui/core'
import SendIcon from '#material-ui/icons/Send'
export default function MultilineTextFields() {
const valueRef = useRef('') //creating a refernce for TextField Component
const sendValue = () => {
return console.log(valueRef.current.value) //on clicking button accesing current value of TextField and outputing it to console
}
return (
<form noValidate autoComplete='off'>
<div>
<TextField
id='outlined-textarea'
label='Content'
placeholder='Write your thoughts'
multiline
variant='outlined'
rows={20}
inputRef={valueRef} //connecting inputRef property of TextField to the valueRef
/>
<Button
variant='contained'
color='primary'
size='small'
endIcon={<SendIcon />}
onClick={sendValue}
>
Send
</Button>
</div>
</form>
)
}
Try this,
import React from 'react';
import {useState} from 'react';
import TextField from '#material-ui/core/TextField';
const Input = () => {
const [textInput, setTextInput] = useState('');
const handleTextInputChange = event => {
setTextInput(event.target.value);
};
return(
<TextField
label="Text Input"
value= {textInput}
onChange= {handleTextInputChange}
/>
);
}
export default Input;
Explanation if the above code was not clear.
First we create a state to store the text input called textInput and assign it the value ''.
Then we return a material UI <TextField /> component whose value attribute is set to the textInput state. Doing this we display the current value of the textInput in the <TextField />. Any changes to the value of textInput will change the value attribute of the <TextField />, courtesy of React.
Then we use the onChange attribute of <TextField /> to run a handler function every time the value of the <TextField /> value attribute changes. This handler function is an arrow function stored in the constant handleTextInputChange. It takes an event as an argument. When the onChange attribute runs the handler function, it sends the event as an argument to the handler function.
The value of the <TextField /> is stored in event.target.value. We then use the setTextInput method of the state to set the state to the value attribute of the <TextField />. Thus this change is reflected in the <TextField /> whose value attribute is the value of the textInput state.
Thus the data input into the <TextField /> is stored in the state textInput, ready to be used when required.
flson's code did not work for me. For those in the similar situation, here is my slightly different code:
<TextField ref='myTextField'/>
get its value using
this.refs.myTextField.input.value
The strategy of the accepted answer is correct, but here's a generalized example that works with the current version of React and Material-UI.
The flow of data should be one-way:
the initialState is initialized in the constructor of the MyForm control
the TextAreas are populated from this initial state
changes to the TextAreas are propagated to the state via the handleChange callback.
the state is accessed from the onClick callback---right now it just writes to the console. If you want to add validation it could go there.
import * as React from "react";
import TextField from "material-ui/TextField";
import RaisedButton from "material-ui/RaisedButton";
const initialState = {
error: null, // you could put error messages here if you wanted
person: {
firstname: "",
lastname: ""
}
};
export class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
// make sure the "this" variable keeps its scope
this.handleChange = this.handleChange.bind(this);
this.onClick = this.onClick.bind(this);
}
render() {
return (
<div>
<div>{this.state.error}</div>
<div>
<TextField
name="firstname"
value={this.state.person.firstname}
floatingLabelText="First Name"
onChange={this.handleChange}/>
<TextField
name="lastname"
value={this.state.person.lastname}
floatingLabelText="Last Name"
onChange={this.handleChange}/>
</div>
<div>
<RaisedButton onClick={this.onClick} label="Submit!" />
</div>
</div>
);
}
onClick() {
console.log("when clicking, the form data is:");
console.log(this.state.person);
}
handleChange(event, newValue): void {
event.persist(); // allow native event access (see: https://facebook.github.io/react/docs/events.html)
// give react a function to set the state asynchronously.
// here it's using the "name" value set on the TextField
// to set state.person.[firstname|lastname].
this.setState((state) => state.person[event.target.name] = newValue);
}
}
React.render(<MyForm />, document.getElementById('app'));
(Note: You may want to write one handleChange callback per MUI Component to eliminate that ugly event.persist() call.)
In 2020 for TextField, via functional components:
const Content = () => {
...
const textFieldRef = useRef();
const readTextFieldValue = () => {
console.log(textFieldRef.current.value)
}
...
return(
...
<TextField
id="myTextField"
label="Text Field"
variant="outlined"
inputRef={textFieldRef}
/>
...
)
}
Note that this isn't complete code.
Faced to this issue after a long time since question asked here. when checking material-ui code I found it's now accessible through inputRef property.
...
<CssTextField
inputRef={(c) => {this.myRefs.username = c}}
label="Username"
placeholder="xxxxxxx"
margin="normal"
className={classes.textField}
variant="outlined"
fullWidth
/>
...
Then Access value like this.
onSaveUser = () => {
console.log('Saving user');
console.log(this.myRefs.username.value);
}
Here's the simplest solution i came up with, we get the value of the input created by material-ui textField :
create(e) {
e.preventDefault();
let name = this.refs.name.input.value;
alert(name);
}
constructor(){
super();
this.create = this.create.bind(this);
}
render() {
return (
<form>
<TextField ref="name" hintText="" floatingLabelText="Your name" /><br/>
<RaisedButton label="Create" onClick={this.create} primary={true} />
</form>
)}
hope this helps.
I don't know about y'all but for my own lazy purposes I just got the text fields from 'document' by ID and set the values as parameters to my back-end JS function:
//index.js
<TextField
id="field1"
...
/>
<TextField
id="field2"
...
/>
<Button
...
onClick={() => { printIt(document.getElementById('field1').value,
document.getElementById('field2').value)
}}>
//printIt.js
export function printIt(text1, text2) {
console.log('on button clicked');
alert(text1);
alert(text2);
};
It works just fine.
class Content extends React.Component {
render() {
return (
<TextField ref={(input) => this.input = input} />
);
}
_doSomethingWithData() {
let inputValue = this.input.getValue();
}
}
Related
This is the code I am trying to rebuild using functional component, but my arrays do not behave correctly.
EXPECTED RESULT: https://stackblitz.com/edit/antd-showhidecolumns
My forked functional component version:
MY WORK https://stackblitz.com/edit/antd-showhidecolumns-rdyc8h
Main issue here is I am not able to show/hide column cells, I am not sure why my array is different when I use the same method as the original code.
My code:
const onChange = (e) => {
let { checkedColumns } = colmenu;
if (e.target.checked) {
checkedColumns = checkedColumns.filter((id) => {
return id !== e.target.id;
});
console.log('if checked columns is', checkedColumns);
} else if (!e.target.checked) {
checkedColumns.push(e.target.id);
console.log('elseif checked columns', checkedColumns);
}
const filtered = checkedColumns.filter((el) => {
return el.dataIndex !== checkedColumns.el;
});
console.log('filtered items', filtered);
setColmenu({ ...colmenu, columns: filtered });
};
working version from the old code (class component)
onChange = (e) => {
var checkedColumns = this.state.checkedColumns
if(e.target.checked){
checkedColumns = checkedColumns.filter(id => {return id !== e.target.id})
}
else if(!e.target.checked){
checkedColumns.push(e.target.id)
}
var filtered = this.state.initialColumns;
for(var i =0;i< checkedColumns.length; i++)
filtered = filtered.filter(el => {return el.dataIndex !== checkedColumns[i]})
this.setState({columns: filtered, checkedColumns: checkedColumns})
}
Something really went wrong with your code (or homework i guess?)
Please have a look at least at the docs for React.useState to set some basics.
First you should init your initalColumns and later you should filter on them.
Additional i init the checkColumns with the correct values and changed the wrong logic for changing them.
Have a look how the filtering is done via Array.includes maybe someone will ask for this ;-)
Another point is that you may split the state object in separate primitive states.
Nevertheless here is a working stackblitz and the depending code.
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Table, Button, Dropdown, Menu, Checkbox } from 'antd';
const App = () => {
const columns = [
{
title: 'Description',
dataIndex: 'description',
},
{
title: 'Employees',
dataIndex: 'employees',
},
];
const [colmenu, setColmenu] = React.useState({
value: false,
checkedColumns: ['description', 'employees'],
visibleMenuSettings: false,
columns,
initialColumns: columns,
});
const onChange = (e) => {
let { checkedColumns, columns, initialColumns } = colmenu;
if (!e.target.checked) {
checkedColumns = checkedColumns.filter((id) => {
return id !== e.target.id;
});
console.log('if checked columns is', checkedColumns);
} else if (e.target.checked) {
checkedColumns.push(e.target.id);
console.log('elseif checked columns', checkedColumns);
}
console.log(columns);
columns = initialColumns.filter((col) =>
checkedColumns.includes(col.dataIndex)
);
setColmenu({ ...colmenu, columns, checkedColumns });
};
const handleVisibleChange = (flag) => {
setColmenu({ ...colmenu, visibleMenuSettings: flag });
};
const menu = (
<Menu>
<Menu.ItemGroup title="Columns">
<Menu.Item key="0">
<Checkbox id="description" onChange={onChange} defaultChecked>
Description
</Checkbox>
</Menu.Item>
<Menu.Item key="1">
<Checkbox id="employees" onChange={onChange} defaultChecked>
Employees
</Checkbox>
</Menu.Item>
</Menu.ItemGroup>
</Menu>
);
const dataSource = [
{
key: '1',
description: 'Holiday 1',
employees: '79',
},
{
key: '2',
description: 'Holiday 2',
employees: '12',
},
{
key: '3',
description: 'Holiday 3',
employees: '0',
},
];
return (
<div>
<div className="row">
<div className="col-12 mb-3 d-flex justify-content-end align-items-center">
<Dropdown
overlay={menu}
onVisibleChange={handleVisibleChange}
visible={colmenu.visibleMenuSettings}
>
<Button>Show/Hide Columns</Button>
</Dropdown>
</div>
</div>
<div className="row">
<div className="col-12">
<Table
columns={colmenu.columns}
dataSource={dataSource}
size="small"
pagination={{
pageSizeOptions: ['20', '50'],
showSizeChanger: true,
}}
/>
</div>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('container'));
I am new to React. I'm using react-select and I've used the following code. The dropdown is displayed but I'm unable to see names and unable to view after selecting.
<Select
variant="outlined"
margin="normal"
fullWidth
value={this.state.selected}
options={RewardAutomationsList}
name="selected"
onChange={this.handleChange}
placeholder='None'
>
{RewardAutomationsList.map((option) => (
<option key={option.id} value ={option.name} label={option.name}>
{option.name}
</option>
))}
</Select>
handleChange = event => {
this.setState({
selected: event.name
});
};
The RewardAutomationsList looks like this:
RewardAutomationsList:
0:{name: "TEST 1 (INR 100)", id: "123"}
1:{name: "test 2 (INR 250)", id: "456"}
Can someone help with this?
same npm package use like this block code.
import React, { Component } from 'react'
import Select from 'react-select'
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
const MyComponent = () => (
<Select options={options} />
)
react-select accepts an array of objects having label and value keys. Your option objects in RewardAutomationsList have id and name keys, so it can't be displayed. You need to change them.
Also, when you subscribe to change events with react-select's onChange prop, the callback function you provide receives the selectedOption, not the event.
The following should work:
const RewardAutomationsList = [
{ label: "TEST 1 (INR 100)", value: "123" },
{ label: "test 2 (INR 250)", value: "456" },
];
class App extends React.Component {
state = {
selected: null,
}
handleChange = (selectedOption) => {
this.setState({
selected: selectedOption,
});
};
render() {
return (
<React.Fragment>
<Select
fullWidth
margin="normal"
name="selected"
onChange={this.handleChange}
options={RewardAutomationsList}
placeholder="None"
value={this.state.selected}
variant="outlined"
/>
{/* It's not necessary and it's only here to show the current state */}
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</React.Fragment>
);
}
}
I want to show a save button if the radio button value changes from it's default value. Lets say default value is 'red'. User changes from red to green I want to show a save button. Lets say user again changed to red before saving green option then I don't want to show it.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
/* Renders a radio group */
class Radio extends Component {
constructor(props) {
super(props);
this.state = {
selected: props.selected
};
this.onChange = this.onChange.bind(this);
}
onChange(ev) {
this.setState({ selected: ev.target.value });
this.props.onChange(ev);
}
render() {
const {
options, inputClasses, labelClasses, hiddenLabel, name, inline
} = this.props;
return (
<div className={`radio-group ${inline ? 'inline-radio-group' : ''}`}>
{
options.map((option) => {
const {
id, value, label
} = option;
return (
<div className="radio" key={`${id}`}>
<input
type="radio" id={id} className={`radio-input ${inputClasses}`}
name={name} value={value} onChange={this.onChange}
checked={this.state.selected === value ? 'checked' : ''}
/>
<label htmlFor={id} className={`radio-label ${labelClasses} ${hiddenLabel ? 'sr-only' : ''}`} >
{label}
</label>
</div>);
})
}
</div>
);
}
}
Radio.propTypes = {
options: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number]).isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
})).isRequired,
name: PropTypes.string.isRequired,
selected: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number]),
inputClasses: PropTypes.string,
labelClasses: PropTypes.string,
hiddenLabel: PropTypes.bool,
onChange: PropTypes.func,
inline: PropTypes.bool
};
Radio.defaultProps = {
selected: '',
inputClasses: '',
labelClasses: '',
hiddenLabel: false,
inline: false,
onChange: () => { }
};
export default Radio;
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import Radio from '/Radio';
const DOContainer = () => {
const options = [
{
id: 'red',
label: 'Red',
value: 'red'
},
{
id: 'green',
label: 'Green',
value: 'green'
}
];
return (
<Fragment>
<div className="do-container">
<h2>choose a color</h2>
<div>
<p>color choose</p>
<Radio
options={options} name="do" inline
selected="red"
/>
</div>
</div>
</Fragment>
);
};
export default DOContainer;
I have updated my Radio component.
You could provide and support onChange callback to <Radio /> component.
Therefore in your <DOContainer /> you will add a handler, where you will get the selected Radio value and keep it in the state (should convert DOContainer to stateful component). Having selected and default value, you can compare them and conditionally show the <Button />.
Something like that will be the <DOContainer /> render method implementation:
const options = [
{
id: 'red',
label: 'Red',
value: 'red'
},
{
id: 'green',
label: 'Green',
value: 'green'
}
];
class DOContainer extends Component {
constructor(props) {
super(props)
const initialValue = 'red'
this.state = {
// Default value. It will be always the same.
default: initialValue,
// What's the Radio value. It will be keep in sync with the selected Radio value.
// Initially it has the same value as `initivalValue`,
// but later will be updated by `onRadioChange`.
selected: initialValue,
}
this.onRadioChange = this.onRadioChange.bind(this)
}
onRadioChange (selected) {
this.setState({ selected })
}
render() {
const { selected, default } = this.state
return (
<Fragment>
<div className="do-container">
<h2>choose a color</h2>
<div>
<p>color choose</p>
<Radio
options={options} name="do" inline
selected={selected}
onChange={this.onRadioChange}
/>
{ selected !== default ? <Button /> : null }
</div>
</div>
</Fragment>
);
}
}
I'm building a shopping cart application and I ran into a problem where all my inputs have the same state value. Everything works fine but when I type in one input box, it's the same throughout all my other inputs.
I tried adding a name field to the input and setting my initial state to undefined and that works fine but the numbers don't go through.
How do we handle inputs to be different when they have the same state value? Or is this not possible / dumb to do?
class App extends Component {
state = {
items: {
1: {
id: 1, name: 'Yeezys', price: 300, remaining: 5
},
2: {
id: 2, name: 'Github Sweater', price: 50, remaining: 5
},
3: {
id: 3, name: 'Protein Powder', price: 30, remaining: 5
}
},
itemQuantity: 0
},
render() {
return (
<div>
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>$ {item.price}</h2>
{item.remaining === 0 ? (
<p style={{ 'color': 'red' }}>Sold Out</p>
) : (
<div>
<p>Remaining: {item.remaining}</p>
<input
type="number"
value={ itemQuantity }
onChange={e => this.setState({ itemQuantity: e.target.value})}
placeholder="quantity"
min={1}
max={5}
/>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
)}
</div>
))}
</div>
)
}
}
If you are using same state key for all input, All input take value from one place and update to one place. To avoid this you have to use separate state. I suppose you are trying to show input for a list of item.
To achive you can create a component for list item and keep state in list item component. As each component have their own state, state value will not conflict.
Here is an example
class CardItem extends Component {
state = {
number: 0
}
render() {
render (
<input type="text" value={this.state.number} onChange={e => this.setState({ number: e.target.value })} />
)
}
}
class Main extends Component {
render () {
const list = [0,1,2,3,4]
return (
list.map(item => <CardItem data={item} />)
)
}
}
This is a solution which the problem is loosely interpreted, but it does work without having to create another component. As you know, you needed to separate the state of each items in the cart. I did this by dynamically initializing and setting the quantity states of each item. You can see the state changes with this example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { quantities: {} }
}
componentDidMount() {
let itemIDs = ['1', '2', '3', 'XX']; //use your own list of items
itemIDs.forEach(id => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: 0})});
})
}
render() {
let list = Object.keys(this.state.quantities).map(id => {
return (
<div>
<label for={id}>Item {id}</label>
<input
id={id}
key={id}
type="number"
value={this.state.quantities[id]}
onChange={e => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: e.target.value})})
}}
/>
</div>
);
})
return (
<div>
{list}
<div>STATE: {JSON.stringify(this.state)}</div>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='root'></div>
You can modify the state structure to your liking.
Here is how I usually handle this scenario. You say that you get an array of items? Each item object should contain a key to store the value (count in my example). You can use a generic onChange handler to update an individual item in the array. So now, your state is managing the list of items instead of each individual input value. This makes your component much more flexible and it will be able to handle any amount of items with no code changes:
const itemData = [
{ id: 0, count: 0, label: 'Number 1' },
{ id: 1, count: 0, label: 'Number 2' },
{ id: 2, count: 0, label: 'Number 3' },
{ id: 3, count: 0, label: 'Number 4' }
];
class App extends React.Component {
state = {
items: itemData
}
handleCountChange = (itemId, e) => {
// Get value from input
const count = e.target.value;
this.setState( prevState => ({
items: prevState.items.map( item => {
// Find matching item by id
if(item.id === itemId) {
// Update item count based on input value
item.count = count;
}
return item;
})
}))
};
renderItems = () => {
// Map through all items and render inputs
return this.state.items.map( item => (
<label key={item.label}>
{item.label}:
<input
type="number"
value={item.count}
onChange={this.handleCountChange.bind(this, item.id)}
/>
</label>
));
};
render() {
return (
<div>
{this.renderItems()}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
label {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can't use the same state for the both inputs. Try to use a different state for each one like that:
class App extends Component {
state = {
number: ""
}
render() {
return (
<div>
<input
type="number"
value={this.state.number}
onChange={e => this.setState({ number: e.target.value })}
/>
<input
type="number"
value={this.state.number2}
onChange={e => this.setState({ number2: e.target.value })}
/>
</div>
)
}
}
I am learning react by myself, and I am having a hard time doing something thought it would be simple.
In summary, I have a menu with a few items.
I want to be able to select that menu item and when that happens, open a form next to it, the form has inputs, and those input will be prefilled in case there's a saved value for it.
I want, if possible, to hide the editable form in case I click away from the form.
I am not sure how to do that. I have been playing with the props, and react is complaining about uncontrollable and controllable components. I read about it and I get and. Now I am not sure what is the best way to do this. I don't need a "hack" in case my solution is not the right way to do it. I am really looking for how people would handle similar problem in an elegant way in React.
Here's parts of the code I was writing, using material-ui-next
class EditMenu extends React.Component {
constructor(props) {
super(props);
console.log(props);
const itemsInfo = [
{id: 11 ,
title: 'title 1',
description: 'desc 1'
},
{id: 22 ,
title: 'title 2',
description: 'desc 2'
},
{id: 33 ,
title: 'title 3',
description: 'desc 3'
},
];
let itemId = this.props.selectedItem;
let item = _.find(itemsInfo, {id:itemId});
this.state = {
value: '',
item: item,
itemName: '',
itemDescription: ''
};
}
handleitemNameSetting = (event) => {
event.preventDefault();
debugger;
this.setState({
itemName: event.target.value
});
}
render() {
return (
<div className="form-container">
<form >
<TextField
id="item-name"
label="item Name"
margin="normal"
onChange={this.handleItemNameSetting}
value={this.state.item.title}
/>
<br />
<TextField
id="dish-desc"
label="item Description"
margin="normal"
value={this.state.item.description}
/>
<br />
<TextField
className="value-field-container"
label="value"
type="number"
hinttext="item value" />
</form>
</div>
);
}
}
class MenuList extends React.Component {
state = { editMenuOpen: false };
handleClick = (id,event, item, ind) => {
this.setState({editMenuOpen: true, selectedItem: id});
};
render() {
const { classes } = this.props;
const menuItems = [
{id: 11 ,
title: 'title 1'
},
{id: 22 ,
title: 'title 2'
},
{id: 33 ,
title: 'title 3'
},
];
return (
<div className={classes.root}>
<Grid container spacing={24}>
<Grid item xs={2}>
<div>
<List
component="nav"
subheader={<ListSubheader component="div">Lunch Menu</ListSubheader>}
>
{menuItems.map(item => (
<ListItem button key={`${item.id}`} onClick= { () => this.handleClick(item.id)}>
<ListItemText primary={`${item.title}`} />
</ListItem>
))}
</List>
</div>
</Grid>
<Grid item xs>
<div>
{ this.state.editMenuOpen ? <EditMenu selectedItem={this.state.selectedItem}></EditMenu> : null }
</div>
</Grid>
</Grid>
</div>
);
}
}
I would add an onClick event handler on the outermost parent element, that triggers a setState and changes the editMenuOpen to false.
Let's assume it's called handleCloseMenuClick.
But i would also need to add an event handler on my form itself that stops the event from handleCloseMenuClick from triggering when i click my form using event.stopPropagation()
Check the console in this example and see the difference when you remove the event.stopPropagation()