I am trying to populate select in [stateless component of] React/Redux app as:
const FillSelect = ({team}) => <option value={team.onboardingOrder}>{team.name}</option>
const TeamSelector = ({teams}) => {
return (
<select>
<option value="">All</option>
{
teams ? (teams => teams.map(team => <FillSelect team={team} />)) : null
}
</select>
)
}
Teams looks like:{0:{id: 1, name: "Program Management", onboardingOrder: 0, …}, 1: {id: 2, name: "Value Capture", onboardingOrder: 1, …}…}.
It returns an error: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
in select ...
Is this happening because I am using map()? What is the correct way of doing this?
You're not calling map directly; if teams is truthy, you return a function (teams => ...). You probably wanted the line to look like this:
teams ? teams.map(team => <FillSelect team={team} />) : null
Which would yield an array of <FillSelect />'s.
This works fine:
return (
<select>
<option value="">All</option>
{
Object.values(teams).map((team, i) => <option value={team.onboardingOrder} key={i}>{team.name}</option> )
}
</select>
)
Related
I have a select component that I would like to use to update the values of an array of objects based on the index.
I use the hook like this:
const [areas, setAreas] = useState(product.areas);
This returns the following for "areas":
[
0: {de: "Getraenke", en: "Drinks"},
1: {de: "Snacks", en: "Snacks"}
]
My select component looks like this:
{areas?.map((area: {de: string, en: string}, index: number) => (
<Wrapper key={index}>
<Select
label="Area"
name="product-area"
value={area[lang] || ''}
onChange={e => setArea([...area, { [lang]: e.target.value }])}
>
{areaOptions.map(option => (
<option value={option} key={option}>
{option}
</option>
))}
</Select>
</InputWrapper>
))}
In this way I unfortunately get the following for "area" after selecting an option (here "Sweets"):
[
0: {de: "Getraenke", en: "Drinks"},
1: {de: "Snacks", en: "Snacks"}
2: {en: "Sweets" }
]
So the first object of the array is not updated as intended, but a further object is appended, which unfortunately also lacks the "de" language.
My goal is to update the first object (or the object based on the index of the select component) of the array so that instead of:
0: {de: "Getraenke", en: "Drinks"}
..the updated object looks like this:
0: {de: "Suessigkeiten", en: "Sweets"}
The objects within the array should therefore be updated based on the index of the current select component and also take into account the selected language (default "en").
The easiest way to do this is to clone the array, update the specific array item by index and then replace the old array with it using useState, like this.
const updateArea = (e, lang, index) => {
const updatedAreas = [...areas];
updatedArea[index][lang] = e.target.value;
setAreas(updatedAreas);
}
...
{areas?.map((area: {de: string, en: string}, index: number) => (
<Wrapper key={index}>
<Select
label="Area"
name="product-area"
value={area[lang] || ''}
onChange={e => updateArea(e, lang, index)}
>
{areaOptions.map(option => (
<option value={option} key={option}>
{option}
</option>
))}
</Select>
</InputWrapper>
))}
Let's solve this step by step
1. A further object is appended
That's because you're telling it to do so :)
onChange={e => setArea([...area, { [lang]: e.target.value }])}
This means, copy the entire array ...area and add a new object at the end { [lang]: e.target.value }
Another mistake you're making is not using a callback function, proper way to access current ...area should be this:
onChange={e => setArea((currentArea) => [...currentArea, { [lang]: e.target.value }]}
This way you always have the most up-to-date version of area.
Now, to update the specific object by the index, you would do the following:
onChange={e => setArea((currentArea) => {
const { value } = e.target;
const newArea = currentArea.map((singleArea, areaIndex) => {
if (areaIndex === index) {
return { [lang]: value }
}
return singleArea;
});
return newArea;
}}
2. Lacks the "de" language
Again, you're explicitly only adding one language to it :)
{ [lang]: value } // { en: 'something' }
Not sure how to fix this one for you, but at least you can understand why its wrong, It should be something like (just a concept, this won't work):
{ [langDe]: value, [langEn]: value } // { de: 'Guten Tag', en: 'something' }
I have a select such as :
<Select id='1' list={[...this.state.dictionnary]}/>
where this.state.dictionnary is like this :
state = {
dictionnary: [
{value: "a", name: "ab"},
]}
And the component select is like this :
class Select extends Component {
handleChange()
{
// I would like to show 1, a and ab
// 1 is the id of the select
// a and ab are value and name of
};
render() {
return (
<select onChange={this.handleChange} className="custom-select">{this.props.list.map(option => (
<option key={option.value} value={option.value}>{option.name}</option>))}
</select>
)
}
}
export default Select;
I would like to show some informations using the handleChange() function like the id, name and value.
Could you help me please ?
Thank you very much
You should change the option so the value has the entire object to get it later in the handleChange. Also, to access the same this with the props in the handleChange method you need to use an arrow function:
<select onChange={(e) => this.handleChange} className="custom-select">
{this.props.list.map((option) => (
<option key={option.value} value={option}>
{option.name}
</option>
))}
</select>
By default, the onChange handler gets the event param so you can get the target and the props:
handleChange(event) {
const { value, name } = event.target.value;
console.log(this.props.id, value, name);
}
Adding to #Alavaro answer, you need to split after getting the value to remote ,
handleChange = e => {
const [value, name] = e.target.value.split(',')
console.log({ id: this.props.id, value, name})
}
render() {
return (
<select onChange={this.handleChange} className="custom-select">
{this.props.list.map((option, index) => (
<option key={option.value} value={[option.value, option.name]} >
{ option.name }
</option>
))}
</select>
);
}
}
I want this select to have an object value, this works but I can't show the title of the selected object, does anyone know why?
my code is:
const [categoria, setCategoria] = useState("");
const litaCategorias = [
{
"id": 2,
"titulo": "test",
"descripcion": "descripcion",
}];
const handleChangeCategoria = (value) => {
setCategoria(value)
console.log(value)
}
<Select
fullWidth
variant="outlined"
value={categoria.titulo}
onChange={e => handleChangeCategoria(e.target.value)}
labelWidth={0}
placeholder={"Seleccione un destino"}
>
{litaCategorias.map(categoria => {
return (
<option
value={categoria}
key={categoria.titulo}
>
{categoria.titulo}
</option>
);
})}
</Select>
I have double checked the Select component in material-ui. It provides props api value with description:
If the value is an object it must have reference equality with the option in order to be selected. If the value is not an object, the string representation must match with the string representation of the option in order to be selected.
You you should memorize you array litaCategorias. to make reference equality.
Should use useMemo to memorized array.
const litaCategorias = useMemo(() => [ { "id": 2, "titulo": "test", "descripcion": "descripcion", }], []);
I think it's because you're setting the param in your callback in the .map function to categoria which is defined in the state and is initialized to an empty string. This line here - {litaCategorias.map(categoria => {. The option value in the select dropdown is not getting the correct object because of this.
Try something like this instead:
import React, { useState } from "react";
export default function App() {
const [categoria, setCategoria] = useState("");
const litaCategorias = [
{ id: 1, titulo: "test", descripcion: "descripcion" },
{ id: 2, titulo: "test2", descripcion: "descripcion2" }
];
const handleChangeCategoria = (value) => {
setCategoria(value);
console.log(value);
};
return (
<select
value={categoria.titulo}
onChange={(e) => handleChangeCategoria(e.target.value)}
>
{litaCategorias.map((cat) => {
return (
<option value={cat.titulo} key={cat.id}>
{cat.titulo}
</option>
);
})}
</select>
);
}
https://codesandbox.io/s/wild-mountain-0r4p6?file=/src/App.js
Data inside options(JSON) is
const options = {"option":"Male","value":"M"};
Raect code is
{props.options.map((option) => {
return (
<option key={option.value} value={option.option}>
<Translate>{option.option}</Translate>
</option>
);
})}
You are interpreting the map in the wrong way. Please follow this and use it in a similar way. Make sure the options are in an array.
const values = [{option: 'male': value: 'm'}, {option: 'female': value: 'f'}, {option: 'others': value: 'o'}];
function App() {
return (
<div>
{values.map(val => (
<option key={val.value} value={val.option}>
<Translate>{val.option}</Translate>
</option>
))}
</div>
);
}
You don't need to map through an object just to use its keys.
Instead simply do:
const options = {option: 'male': value: 'm'} // this is your parsed JSON
function App() {
return (
<div>
<option key={options.value} value={options.option}>
<Translate>{options.option}</Translate>
</option>
</div>
);
}
I have an array that looks like this:
const teamsAndPlayers = [
{
team: 'Liverpool',
players: ['Salah', 'Henderson']
},
{
team: 'Man Utd',
players: ['Rashford', 'De Gea']
},
{
team: 'Chelsea',
players: ['Hazard', 'Willian']
}
];
I have 2 select boxes, the second of which is dynamic based on what the user selects. The issue I am having is that I am not sure the best way to find the related array based on the user choice. I know I could use find and other methods, but is there anyway I can do it all in the map function in the return statement?
My code looks like the following:
const Menu = () => {
const [selectedTeam, setSelectedTeam] = useState(null);
return (
<div>
<select
onChange={e => setSelectedTeam(e.target.value)}
id="team"
name="team"
>
{teamsAndPlayers(item => (
<option key={item.team} value={item.team}>
{item.team}
</option>
))}
<select id="players" name="players">
{teamsAndPlayers(item => (
// how can I get the related players in here
// based on first select choice?
))}
</div>
)
}
I would use object instead of array to define the input for the selects (if you really want to avoid using find).
So the teamsAndPlayers would look like this:
const teamsAndPlayers = {
liverpool: {
name: 'Liverpool',
players: ['Salah', 'Henderson']
},
manUtd: {
name: 'Man Utd',
players: ['Rashford', 'De Gea']
},
chelsea: {
name: 'Chelsea',
players: ['Hazard', 'Willian']
}
};
Then the options inside the fisrt select would look like this:
{Object.keys(teamsAndPlayers).map(key => (
<option key={key} value={key}>
{teamsAndPlayers[key].name}
</option>
))}
Then the options inside the second select would look like this:
{teamsAndPlayers[selectedTeam].players.map(player => (
<option key={player} value={player}>
{player}
</option>
))}
Simply combine find and map (if you don't want to change your data Array structure):
const playersByTeam = teamsAndPlayers.find(({ team }) => team === selectedTeam).players
<select id="players" name="players">
{ playersByTeam.map(player => <option key={player>{ player }</option>) }
</select>