import React from 'react';
import { makeStyles} from '#material-ui/core/styles';
import {Select, MenuItem} from '#material-ui/core';
import useState from 'react';
const test = () => {
const data = [
{TITLE : "Festival", PRIORITY : 3, STEP : 1},
{TITLE : "Headphone", PRIORITY : 2, STEP : 2},
{TITLE : "Mountain", PRIORITY : 1, STEP : 1}
]
return (
<>
{
data.map((info) => (
<div>
<div>{info.TITLE}</div>
<Select value={info.PRIORITY}>
<MenuItem value={1}> 1 </MenuItem>
<MenuItem value={2}> 2 </MenuItem>
<MenuItem value={3}> 3 </MenuItem>
</Select>
<Select value={info.STEP}>
<MenuItem value={1}> 1 </MenuItem>
<MenuItem value={2}> 2 </MenuItem>
<MenuItem value={3}> 3 </MenuItem>
</Select>
</div>
))
}
</>
)}
export default test;
In this code, I'm trying to control PRIORITY value and STEP value respectively.
I'm having trouble because, In my Data array, I have three items. Therefore, If I add
const [priority, setPriority] = useState(undefined);
const [step, setStep] = useState(undefined);
const priorityChange = (e) => {
setPriority(e.target.value)
};
const stepChange = (e) => {
setStep(e.target.value)
};
and put this value in
<Select value={priority} onChange={priorityChange}></Select>
...
<Select value={step} onChange={stepChange}></Select>
...
this item,
Every item gets the same value, therefore I'm unable to control each PRIORITY and STEP value.
How can I control each item? I need some help.
I might misspell. Please be understandable!
Firstly your data array is not hooked to any state managing variable. So when you try showing values initially from the data array and then try showing the updated data from the hooks variable when an action is trigged, it is obvious to cause some clash and hence fail to show the updated values. One way to get around this would be, to associate the initial data with a state hook and then update the data array accordingly. It is also important that correct data is updated that corresponds to the action triggered, so here we'd want to make sure that each object of the collection is unique, this could be accomplished by assigning an id attribute on each object. Further up, we can find out the object on which the action was taken on, mutate the property value and then re-construct the array using the state hook function to re-render with the correct updated value(s).
Kindly refer to the below code and read the comments to get a clearer idea.
import React, { useState } from "react";
const App = () => {
const [data, setData] = useState([
{ id: Math.random(), TITLE: "Festival", PRIORITY: 3, STEP: 1 },
{ id: Math.random(), TITLE: "Headphone", PRIORITY: 2, STEP: 2 },
{ id: Math.random(), TITLE: "Mountain", PRIORITY: 1, STEP: 1 }
]); //id attribute is added to each object to make sure every object in the array is unique.
const priorityChange = (e, id) => {
//This function should accept event and id arguments, to identify and update
//values correctly.
const index = data.findIndex((item) => item.id === id); //find the index of the object (item) whose priority needs to be updated.
const arr = [...data]; //Copy original array data to constant arr.
arr[index].PRIORITY = e.target.value; //mutate the PRIORITY property's value
setData([...arr]); //Set data to the new array with updated value.
};
const valueChange = (e,id) => {
//This function should accept event and id arguments, to identify and update
//values correctly.
const index = data.findIndex((item) => item.id === id); //find the index of the object (item) whose priority needs to be updated.
const arr = [...data];
arr[index].STEP = e.target.value; //mutate the STEP property's value
setData([...arr]); //Set data to the new array with updated value.
};
return (
<>
{data.map((info) => (
<div key={Math.random()}>
<div>{info.TITLE}</div>
<select
value={info.PRIORITY}
onChange={(e) => {
priorityChange(e, info.id); //pass event object and id corresponding to each array object.
}}
>
<option value={1}> 1 </option>
<option value={2}> 2 </option>
<option value={3}> 3 </option>
</select>
<select
value={info.STEP}
onChange={(e) => {
valueChange(e, info.id); //pass event object and id corresponding to each array object.
}}
>
<option value={1}> 1 </option>
<option value={2}> 2 </option>
<option value={3}> 3 </option>
</select>
</div>
))}
</>
);
};
export default App;
Related
I'm working on practicing with React and I'm creating a drop down menu, but in that when a user clicks on either a fruit or a vegetable, it'll filter and show on the page which item was selected.
I'm a little confused as my thought process was creating the changeItem method to filter out those specific items and call that as apart of the onChange in the HTML render, but I'm not sure if this is the correct way of approaching this and it's currently not rendering anything at the moment.
Any guidance would be appreciated
This is the code that I have so far:
class ItemList extends React.Component {
constructor(props) {
super(props)
this.changeItem = this.changeItem.bind(this)
this.state = {
value: 'all' // this would be the initial value
}
changeItem(event) {
this.setState({value: event.target.value} // where the value will be changed
}
}
render() {
return (
<>
<select onChange={this.changeItem} value:{event.target.value}>
<option value='all'>all</option>
<option value='cats'>fruits</option>
<option value='dogs'>vegetables</option>
</select>
<div className="item-list">
{this.props.items.map((item) =>
<SingleItem key={item.id}
item={item} />
)}
</div>
</>
);
}
}
I have prepared a sample which I think it will help you
import React from "react";
function SingleItem({ item }) {
return <div>{item?.type}</div>
}
class ItemList extends React.Component {
currentType = "";
constructor(props) {
super(props)
this.changeItem = this.changeItem.bind(this)
this.state = {
currentType: "all"
}
}
changeItem(event) {
this.setState({ currentType: event.target.value }) // where the value will be changed
}
filter(list) {
switch (this.state.currentType) {
case "cats":
return list.filter(i => i.type === "cats");
case "dogs":
return list.filter(i => i.type === "dogs");
default:
return list;
}
}
render() {
return <React.Fragment>
<select onChange={this.changeItem}>
<option value='all'>all</option>
<option value='cats'>fruits</option>
<option value='dogs'>vegetables</option>
</select>
<div className="item-list">
{this.props.items && this.filter(this.props.items).map((item) => <SingleItem key={item.id} item={item} />
)}
</div>
</React.Fragment>
}
}
export default function Page() {
let items = [
{
id: 1,
type: "cats",
name: "black cat"
},
{
id: 2,
type: "cats",
name: "white cat"
},
,
{
id: 3,
type: "dogs",
name: "Yellow dog"
}
];
return <ItemList items={items} />
}
I see a few different questions here, but I'll try to explain everything in a way that makes sense.
I'm going to suggest using functional components over class components. There's more explanation about the reasoning to using functional components in this answer as well, but to be brief here, the reasoning as explained in the linked answer is summarized as:
With the introduction of React hooks, it seems as though the React teams wants us to use functional components whenever possible (which better follows JavaScript's functional nature).
1.) It’s hard to reuse stateful logic between components
2.) Complex components become hard to understand
3.) Classes confuse both people and machines
First, we need an array of the possible options for the select menu:
const items = ["cats", "dogs"];
Next, we'll define the SingleItem component as simply as possible for the sake of this example:
function SingleItem({ item }) {
return <div>{item}</div>;
}
Then inside of ItemList we use useState to create a state variable so we can keep track of which item the user has selected:
function ItemList() {
const [selectedItem, setSelectedItem] = useState("all");
...
We also need to create a filtered version of the default list of items so that we can show only the selected item, or all items if "all" is selected:
const filteredItems = items.filter(
(item) => selectedItem === "all" || selectedItem === item
);
Then in our select, we set the value prop to the data saved in the selectedItem state variable. We also set the onChange prop to a function that will update selectedItem whenever the user selects a new item:
<select
onChange={(e) => setSelectedItem(e.target.value)}
value={selectedItem}
>
<option value="all">all</option>
<option value="cats">cats</option>
<option value="dogs">dogs</option>
</select>
Finally, we display the list items which should be shown by displaying each item in our filteredItems list:
<div className="item-list">
{filteredItems.map((item) => (
<SingleItem key={item.id} item={item} />
))}
</div>
Here's a full working example on codesandbox:
Essentially I have a select dropdown that is being populated by an API.
If you look at the following code snippet, I essentially created an array of IDs called "first" and made it the value for the first select option above the map that I've done to populate the rest of the select.
Now in my handleChange when I log the value of a selected option it returns the value of the given option in an array of string numbers.
--> Example... if user selects the second option ['1']
When the user selects 'All IDs' that's where the issue is. When that option is selected whats logged is ---> ['1,2,6,8,15,16,17,20,22,23,24,25,26,27,30,32,33,34,36']
I understand that I could use the split method but it crashes for any other option that's selected.
How could I get around that?
const DropDown = ({ list = [], title, onChange }) => {
const handleChange = (e) => {
const { value } = e.target
const arr = [value]
console.log(arr)
// onChange(Number(value))
}
const first = list.map((list) => list.id)
return (
<>
<select onChange={handleChange}>
<option value={first}>{title}</option>
{list.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</select>
</>
)
}
e.target.value is a string, so you'll need to change this:
const arr = [value]
to
const arr = value.split(",").map(Number);
You can use whatever you want as the value (key) of the "All" option. For example, in the following, I've used "all" as the key and handled that specifically in the handleChange handler.
const DropDown = ({ list = [], title, onChange }) => {
const handleChange = (e) => {
const { value } = e.target;
if (value === "all") {
onChange(list.map((l) => l.id));
} else {
onChange([parseInt(value, 10)]);
}
};
return (
<>
<select onChange={handleChange}>
<option value="all">{title}</option>
{list.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</select>
</>
);
};
You can use value.split(",") as well, which would work for your specific example but would not be sufficient if you need to handle different types of items in the list (perhaps strings that could contain their own ,s).
Here's the above code in action
I have an array that has initially 2 values, when I click on the add button I want to add a new object but then it tells me map is not a function. Any help is greatly appreciated. This is not my entire component but what you would need to replicate:
const Equipments = (props) => {
const [equipment,setEquipment]=useState([1,2]);
const addEq = ( ) => {
console.log('add eq')
var id = equipment.length+1;
setEquipment(equipment.push(id));
console.log('this is the id',id, 'this is the array',equipment)
};
const renderEQForm = (equipment, index) => {
return (
<>
<label>{equipment.id}</label>
<Form.Item
name={['payments', 'method']}
label="Method:"
defaultValue = 'Credit Card'>
<Select defaultValue = 'Credit Card'>
<Option value='Cash'>Cash</Option>
<Option value='Check'>Check</Option>
<Option value='Credit Card'>Credit Card</Option>
<Option value='Google Pay'>Google Pay</Option>
</Select>
</Form.Item>
<Form.Item
name={["equipment", 'brand']}
rules={[{ required: false, message: 'Missing brand' }]}
label='Brand:'
>
<Input placeholder="Brand" />
</Form.Item>
</>
)}
return (
<>
{equipment.map(renderEQForm)}
<button onClick={()=>addEq()}>Add</button>
</>
);
};
export default Equipments
The problem is in this line
setEquipment(equipment.push(id));
equipment.push(id) returns a number NOT an array. Therefore, your state gets set to a Number which doest not have .map function in its prototype.
Your solution is
setEquipment([...equipment, id]);
Don't use push because that is mutating the array, when you want to change the state of your code copy the existing array and mutate that one, for example using the spread syntax will do what you want
const addEq = ( ) => {
console.log('add eq')
const id = equipment.length+1;
// This returns a new array with the current equipment items plus the new id.
setEquipment([...equipment, id]);
console.log('this is the id',id, 'this is the array',equipment)
};
Is worth to notice that the error you're getting is because push returns the new array length(a number) so setting that in the setEquipment would make your equipment a number and map is not a function included in the number object.
I'd like to know how to get the object properties from a json.
I got this fake API:
{
"Arreglo":[
{ "label": "Choose Wisely", "value": "Choose Wisely"},
{ "label": "Primer opción", "value": "1", "testvariable": [] },
{ "label": "Segunda opción"," value": "2" },
{ "label": "Tercer opción", "value": "3" }
]
}
This is my App.js :
import SelectComponent from './Components/SelectComponent';
import {Arreglo} from './FakeApis/SelectOptions';
function App() {
return (
<div className="App">
<SelectComponent items1 = {Arreglo}/>
</div>
);
}
And this is my form and how I set the state:
export default function SelectComponent(items1) {
console.log(items1.items1);
const[testVar, testFunction] = useState(false);
const [items] = React.useState(items1.items1);
<form>
<select onChange = {handleChange}>
{items.map(item => (
<option
key={item.value}
value={item.value}
label={item.label}
>
{item.label}
</option>
))}
</select>
</form>
In this function I'd like to know how to get the "testvariable"
const handleChange = event => {
console.log("The variable --->");
};
Hope I explained myself good.
regards!
Since your data doesn't have any unique identifying id, you can use the index of each item in the array as a reference point that you can use in your change handler.
To do this, first read your data into some piece of state. While your data is static now, this is good practice for when you start speaking to an actual API to fetch data.
Once you have that data available within your component (you can abstract this back into a separate Select Component, I just did it one file for ease of understanding), you can access the selected option by using the selectedIndex property of the event within the change event handler.
import {Arreglo} from './FakeApis/SelectOptions';
export default function App() {
// Read the data into state
const [data] = React.useState(Arreglo);
const handleChange = e => {
// The index of the selected option
const { selectedIndex } = e.currentTarget;
// Grab the element at that index
const selectedOption = data[selectedIndex];
// You can now access any property on that element, like testVariable
console.log(selectedOption.testvariable);
};
return (
<div className="App">
<select onChange={handleChange}>
{data.map(item => (
<option key={item.value} value={item.value} label={item.label}>
{item.label}
</option>
))}
</select>
</div>
);
}
To access the testVar your handleChange function must be inside the SelectComponent function just like the testVar:
export default function SelectComponent(items1) {
const[testVar, testFunction] = useState(false);
const [items] = React.useState(items1.items1);
const handleChange = event => {
// testVar can be called here
}
<form>
<select onChange = {handleChange}>
{items.map(item => (
<option
key={item.value}
value={item.value}
label={item.label}
>
{item.label}
</option>
))}
</select>
</form>
Something that you could do in order to solve your issue and have access to your data is to replace your select tag by the following:
<select onChange = {(event) => handleChange(event, items)}>
{items.map(item => (
<option
key={item.value}
value={item.value}
label={item.label}
>
{item.label}
</option>
))}
</select>
And let your handleChange function accept another parameter:
const handleChange = (event, items) => {
// items is now available in this block and you can get testvariable here.
console.log(`The variable ${items}`);
};
I have a react app that I am currently updating which involves switching to react 16.8 and updating all libraries. I have two drop down selects from material UI, and due to this new version the multi select one no longer allows multiple options to be selected and I can't work out why. Any ideas would be appreciated!
Code:
import React from 'react';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
const names = [
'Oliver Hansen',
'Van Henry',
'Kelly Snyder',
];
export default function MultipleSelect() {
const [personName, setPersonName] = React.useState([]);
const handleChange = event => {
console.log(event) //holds the selected option correctly
setPersonName(event.target.value);
console.log(personName)
};
return (
<div>
<FormControl className={classname}>
<Select
multiple //used to be isMulti but this also no longer works
value={personName}
onChange={handleChange}
placeholder = {"choose a name"}
options={names}
>
</Select>
</FormControl>
</div>
);
}
This is because your value always contains a single string value. When you select a second items, it overrides the first value with new one. You need to assign an array of values to value prop with selected values. Push selected item in previously selected values array and update the state and on removal, remove that name from that array.
export default function MultipleSelect() {
const [selectedNames, setSelectedNames] = React.useState([]);
const handleChange = event => {
console.log(event) //holds the selected option correctly
// if selection/addition
setSelectedNames([...selectedNames, event.target.value]);
// On removal,
// setSelectedNames(selectedNames.filter(name => name !== event.target.value));
};
return (
<div>
<FormControl className={classname}>
<Select
multiple //used to be isMulti but this also no longer works
value={selectedNames}
onChange={handleChange}
placeholder = {"choose a name"}
options={names}
>
</Select>
</FormControl>
</div>
);
}
Set true value to multiple attribute:
<Select
multiple="true"
// ...
>
// ...
</Select>
Your options are currently single string. Select expects something like this (notice the value and label properties):
[
{value: 1, label: "Oliver Hansen"},
{value: 2, label: "Van Henry"},
{value: 3, label: "Kelly Snyder"}
]
If you have that in order your select should be working as expected.