I have a simple list where I'm displaying a looped Select using .map.
Everything is working fine, but the initial default value of each select is not what I have in my array list.
How do I set a defaultValue for each select in the list?
https://codesandbox.io/s/festive-robinson-847oj?file=/src/App.js:0-1435
import React, { useState } from "react";
import Select from "react-select";
function SelectItem(props) {
const [value, setValue] = React.useState(props.list[0]);
return (
<Select
options={props.list}
onChange={(newValue) => {
setValue(newValue);
props.onChange(newValue.value);
}}
value={value}
/>
);
}
export default () => {
const animationsList = [
{ value: "animation1", label: "Dance" },
{ value: "animation2", label: "Flip" },
{ value: "animation3", label: "Salsa" }
];
const reactList = [
{
id: "1",
animation: "animation1"
},
{
id: "2",
animation: "animation2"
},
{
id: "3",
animation: "animation3"
}
];
const [links, setLinks] = useState(reactList);
const onChange = (index, animation) => {
const cloneLinks = [...links];
cloneLinks[index].animation = animation;
setLinks(cloneLinks);
console.log(cloneLinks);
};
return (
<div>
<ul>
<div>
{links.map((url, indexurl) => (
<li key={url.id}>
<div>
<SelectItem
onChange={(animation) => onChange(indexurl, animation)}
list={animationsList}
defaultValue={url.animation.value}
/>
</div>
</li>
))}
</div>
</ul>
</div>
);
};
A new state in the SelectItem component is not needed.
The value or defaultValue must be an object from the list that is used for options.
Here's the updated CSB.
function SelectItem(props) {
return (
<Select
options={props.list}
onChange={(newValue) => {
props.onChange(newValue.value);
}}
value={props.value}
/>
);
}
export default () => {
const animationsList = [
{ value: "animation1", label: "Dance" },
{ value: "animation2", label: "Flip" },
{ value: "animation3", label: "Salsa" }
];
const reactList = [
{
id: "1",
animation: "animation1"
},
{
id: "2",
animation: "animation2"
},
{
id: "3",
animation: "animation3"
}
];
const [links, setLinks] = useState(reactList);
const onChange = (index, animation) => {
const cloneLinks = [...links];
cloneLinks[index].animation = animation;
setLinks(cloneLinks);
console.log(cloneLinks);
};
return (
<div>
<ul>
<div>
{links.map((url, indexurl) => (
<li key={url.id}>
<div>
<SelectItem
onChange={(animation) => onChange(indexurl, animation)}
list={animationsList}
value={animationsList.find(
(aL) => aL.value === url.animation
)}
/>
</div>
</li>
))}
</div>
</ul>
</div>
);
};
Related
I have some nested data that needs to generate a form of checkboxes dynamically. The "Tasks" data, needs a parent checkbox, as per MaterialUI's docs under "Indeterminate" in their Checkboxes example . I'm struggling to understand how to apply their example in conjunction with my code.
Current data used to generate dynamic checkboxes:
const availableFilters = useMemo(
() => [
{
title: "Status",
filterOptions: [
{ label: "Ready for Review"},
{ label: "Ready for Techcheck"},
],
},
{
title: "Offices",
filterOptions:
[
{ label: "London" },
{ label: "Berlin"},
}],
},
{
title: "Tasks",
filterGroups:
[
{
title: "3D"
filterOptions: [
{label: "Animation"},
{label: "Lighting"},
],
},
{
title: "Comp"
filterOptions: [
{label: "Compositing"},
{label: "Prep"}
],
},
],
},
{
title: "Creator",
filterOptions: [{ label: "Alex"}, { label: "John"}],
},
],
[filterInfo, taskGroups]
);
Getting confused rather easily with the nesting and some recursive typescript stuff.
This is the handlerFunction in the parent component(AddtoReviewMenu) with "Lodash":
const [checkedValues, setCheckedValues] = useState<{[key: string]: string[];}>({});
const handleCheckboxChange = useCallback(
(checked: boolean, title: string, value: string) => {
if(checked) {
if (Object.keys(checkedValues).includes(title)) {
setCheckedValues({
...checkedValues,
[title]: [...checkedValues[title], value],
});
} else {
setCheckedValues({
...checkedValues,
[title]: [value],
});
} else {
setCheckedValues({
...checkedValues,
[title]: _(checkedValues[title])
.filter((c) => c !== value)
.value(),
});
}
},
[checkedValues]
);
Here is the child component that populates the the checkboxes based on the data:
import React from "react";
import IconButton, { IconButtonProps } from "#mui/material/IconButton";
import Box, { FormControl, Stack } from "#mui/material/";
interface ExpandMoreProps extends IconButtonProps {
expand: Boolean;
}
const ExpandMore = styled((props: ExpandMoreProps) => {
const { expand, ...other } = props;
return <IconButton {...other} />;
});
interface Props extends FilterGroup {
handleCheckboxChange: (
checked: boolean,
title: string,
value: string
) => void;
}
export default function FilterOptionGroup(props: Props) {
const { filterGroups, title, handleCheckboxChange } = props;
const [expanded, setExpanded] = useState(true);
const handleExpandClick = () => {
setExpanded(!expanded);
};
return (
<Box>
<FormControl>
<Stack>
<ExpandMore expand={expanded} onClick={handleExpandClick}>
<ArrowUpIcon />
</ExpandMore>
<FormLabel> {title} </FormLabel>
</Stack>
<Collapse in={expanded}>
{filterOptions
? filterOptions?.map((item) => (
<FormControlLabel
control={
<Checkbox
onChange={(event, checked) =>
handleCheckboxChange(checked, title, item.label)
}
/>
}
label={item.label}
value={item.label}
/>
))
: filterGroups?.map((item) => (
<FilterOptionGroup
title={item.title}
filterOptions={item.filterOptions}
filterGroups={item.filterGroups}
handleCheckboxChange={handleCheckboxChange}
/>
))}
</Collapse>
</FormControl>
</Box>
);
}
And a "Types.ts" file:
export interface FilterGroup {
title: string;
filterOptions?: FilterOption[];
filterGroups?: FilterGroup[];
}
export interface FilterOption {
label: string;
}
I am able to map 3 objects as a normal list however how can I map it under the correct heading?
One way is to push each object to it's own array e.g. const employed = [] but it looks messy. Any suggestions for a better approach?
export const App = () => {
const [list, setList] = useState([
{ name: "foo", status: "student" },
{ name: "bar", status: "employed" },
{ name: "foo", status: "unemployed" },
])
const items = list.map(({name, status}, index) => {
<Profile ... />
})
return (
<div>
<div>
<h1>Students</h1>
</div>
<div>
<h1>Employed</h1>
</div>
<div>
<h1>Unemployed</h1>
</div>
</div>
)
}
Keep another mapping to your statuses to header values and filter the list according to the statuses.
This would also work.
import { useState } from "react";
const STATUSES = {
student: "Studnets",
employed: "Employed",
unemployed: "Unemployed",
retired: "Retired"
};
const App = () => {
const [list, setList] = useState([
{ name: "foo", status: "student" },
{ name: "bar", status: "employed" },
{ name: "foo", status: "unemployed" },
{ name: "baz", status: "student" }
]);
return (
<div>
{Object.entries(STATUSES).map(([statusKey, displayValue]) => {
const data = list
.filter(({ status }) => status === statusKey)
.map(({ name, status }, index) => <div>{name}</div>);
if (data.length > 0) {
return (
<div>
<h1>{displayValue}</h1>
{data}
</div>
);
}
})}
</div>
);
};
export default App;
Making three seperate lists using filter() would be one way to do it. Then you can show them as you need:
export const App = () => {
const [list, setList] = useState([
{ name: "foo", status: "student" },
{ name: "bar", status: "employed" },
{ name: "foo", status: "unemployed" },
])
const students = list.filter(x => x.status === 'student' ).map(x => <Profile... />);
const employed = list.filter(x => x.status === 'employed' ).map(x => <Profile... />);
const unemployed = list.filter(x => x.status === 'unemployed' ).map(x => <Profile... />);
return (
<div>
<div>
<h1>Students</h1>
{students}
</div>
<div>
<h1>Employed</h1>
{employed}
</div>
<div>
<h1>Unemployed</h1>
{unemployed}
</div>
</div>
)
}
i need to make like reaction icon , when someone clicks in one icon the counter increase one and when he click to other icons it will decrease the previous icons then increase what wh click on .
so this is my code
it's will look like emojis with conuter for each one and you need to click to one of these emoji , then increase the count one .
import logo from './logo.svg';
import React, { useState } from 'react';
import './App.css';
let emojis = [
{
"id":"0",
"reactionName": "disLike",
"pic": "logo",
"PreCounter":20,
},
{
"id":"1",
"reactionName": "like",
"pic": "logo",
"PreCounter":2,
},
{
"id":"2",
"reactionName": "disLike",
"pic": "logo",
"PreCounter":0,
},
{
"id":"3",
"reactionName": "like",
"pic": "logo",
"PreCounter":20,
},]
function App() {
return (
<div className="App">
{
emojis.map(({id,reactionName, pic,PreCounter}) => {
return <Emoji
key={id}
reactionName={reactionName}
pic={pic}
PreCounter={PreCounter}
/>
})
}
</div>
);
}
export default App;
function Emoji (props){
const { key,reactionName, pic,PreCounter } = props;
const [count, setCounter] = useState(PreCounter);
const [selectedName, setSelectedName] = useState("noReaction");
const [selected, setSelected] = useState(false);
const handleClick = (e) => {
setSelectedName(e.target.getAttribute("name"));
if (selected) {
setCounter(count - 1);
setSelected(false);
}else {
setCounter(count + 1);
setSelected(true);
}
};
return(
<button onClick={handleClick} name={reactionName} value={count} id={key}>
<img src={pic} alt="logo" width="20"/>
{count}
</button>
);
}
I couldn't know how I can change the value of the previous click ,
I don't know if this is what you want
function Emoji(props) {
const { id, reactionName, pic, PreCounter, handleClick } = props;
return (
<button onClick={handleClick} name={reactionName+id} value={PreCounter} id={id}>
{/* <img src={pic} alt="logo" width="20"/> */}
{PreCounter}
</button>
);
}
let emojis = [
{
id: '0',
reactionName: 'disLike',
pic: 'logo',
PreCounter: 20,
},
{
id: '1',
reactionName: 'like',
pic: 'logo',
PreCounter: 2,
},
{
id: '2',
reactionName: 'disLike',
pic: 'logo',
PreCounter: 0,
},
{
id: '3',
reactionName: 'like',
pic: 'logo',
PreCounter: 20,
},
];
function useAppCount() {
const [list, listCallBack] = useState(emojis)
return [list, listCallBack]
}
function App() {
const [list, listCallBack] = useAppCount()
const handleClick = e => {
const id = e.target.getAttribute('id')
const data = list.map(r => {
if (r.isClick) {
r.isClick = false
r.PreCounter -= 1
}
if (r.id === id) {
r.isClick = true
r.PreCounter += 1
}
return r
})
listCallBack(() => data)
}
return (
<div className="App">
{list.map(({ id, reactionName, pic, PreCounter }) => {
return <Emoji key={id} id={id} reactionName={reactionName} pic={pic} PreCounter={PreCounter} handleClick={handleClick} />;
})}
</div>
);
}
export default App;
I just want to show you the value it received
handleChange = (selectedOption, e) => {
e.preventDefault();
this.setState({
selectedOption
});
console.log(selectedOption);
}
render() {
let options = [];
if (this.state.cityName && this.state.cityName.length > 0) {
options = this.state.cityName.map((cityName) => {
return {
value: cityName.AdministrativeArea.LocalizedName,
label: cityName.AdministrativeArea.LocalizedName,
id: cityName.Key
};
})
}
return (
<div className="container">
<h1 htmlFor="Search">Search</h1>
<Select
name="htmlForm-field-name"
value={this.state.value}
onChange={(e) => this.handleChange}
defaultValue='Jerusalem'
options={options}
/>
<div>
<ul>
{this.state.value}
</ul>
How to do it with selectedOption
And another question is how do I do defaultValue within the API?
You are using this.state.value but updating this.state.selectedOption. Both of them must be the same. I think you are using Material UI Select so I do the same:
import React, { Component } from "react";
import { Select, MenuItem } from '#material-ui/core';
class Test extends Component {
state = {
selectedOption: ""
};
handleChange = selectedOption => {
this.setState({
selectedOption
});
console.log(selectedOption);
};
render() {
let options = [];
if (this.state.cityName && this.state.cityName.length > 0) {
options = this.state.cityName.map(cityName => {
return {
value: cityName.AdministrativeArea.LocalizedName,
label: cityName.AdministrativeArea.LocalizedName,
id: cityName.Key
};
});
}
const { selectedOption } = this.state;
return (
<div className="container">
<h1 htmlFor="Search">Search</h1>
<Select
name="htmlForm-field-name"
value={selectedOption}
onChange={e => this.handleChange(e.target.value)}
>
{options.map(o => <MenuItem value={o.value}>{o.label}</MenuItem>)}
</Select>
<div>
<ul>{selectedOption}</ul>
</div>
</div>
);
}
}
export default Test;
Working example:
import React, { Component } from "react";
import Select from "react-select";
class App extends Component {
handleChange = el => {
console.log(el.value);
};
render() {
return (
<div>
<Select
style={{ width: 100 }}
onChange={this.handleChange}
options={[
{ value: "green", label: "Green", color: "#36B37E" },
{ value: "forest", label: "Forest", color: "#00875A" },
{ value: "slate", label: "Slate", color: "#253858" },
{ value: "silver", label: "Silver", color: "#666666" }
]}
/>
</div>
);
}
}
export default App;
See example:
Expected effect:
click button -> call function save -> pass object p to function update
update second object{a: 'purple', desc: 'grt', date: '12 -10-2019 '} in colors array, which is in theproducts array
Before update: {a: 'purple', desc: 'grt', date: '12 -10-2019 '}
After update: {a: 'violet', desc: 'gt', date: '12 -12-1980 '}
Error in console.log:
Uncaught TypeError: this.props.product.colors.map is not a function
App
class App extends Component {
constructor (props) {
super(props);
this.state = {
products: [
{
colors: [{a:'orange', desc: 'grtrt', date: '02-12-2019'}, {a:'purple', desc: 'grt', date: '12-10-2019'}]
desc: 'gfgfg',
},
{
colors: [{a:'black', desc: 'g', date: '12-12-2019'}, {a: 'white', {a:'black', desc: 'grtrt', date: '12-12-2119'}, }, {a:'gray', desc:'', date: '01-01-2000'}],
desc: 'gfgfgfg',
}
],
selectProductIndex: 0 //It is first object in products array
index: 1 //It is second object in colors array
}
}
update = (item) => {
const {selectProductIndex} = this.state;
this.setState(prevState => {
return {
products: [
...prevState.products.slice(0, selectProductIndex),
Object.assign({}, prevState.products[selectProductIndex], {colors: item}),
...prevState.products.slice(selectProductIndex + 1)
]
};
});
}
render () {
return (
<div>
<Items
product={this.state.products[this.state.selectProductIndex]}
update = {this.update}
/>
</div>
)
}
Items
class Items extends Component {
render () {
return (
<ul>
{
this.props.product.colors
.map((item, index) =>
<Item
key= {index}
index = {index}
time = {item}
update = {this.props.update}
/>
)
}
</ul>
</div>
);
}
}
Item
class Item extends Component {
save = () => {
const p = {
a:'violet', desc: 'gt', date: '12-12-1980'
}
this.props.update(p)
}
render() {
return (
<div>
<button onClick={this.save}>Save</button>
</div>
)
}
}
You need to pass the index of the colors item and then update it accordingly
class Item extends Component {
save = () => {
const p = {
a:'violet', desc: 'gt', date: '12-12-1980'
}
this.props.update(p, this.props.index)
}
render() {
return (
<div>
<button onClick={this.save}>Save</button>
</div>
)
}
}
and then in the topmost parent
update = (item, colorIndex) => {
const {selectProductIndex} = this.state;
this.setState(prevState => {
return {
products: [
...prevState.products.slice(0, selectProductIndex),
Object.assign({}, prevState.products[selectProductIndex], {colors: prevState.products[selectProductIndex].colors.map((it,idx) => {
if(idx === colorsIndex) { return item}
return it;
})}),
...prevState.products.slice(selectProductIndex + 1)
]
};
});
}
Working demo
const { Component } = React;
class App extends Component {
constructor (props) {
super(props);
this.state = {
products: [
{
colors: [{a:'orange', desc: 'grtrt', date: '02-12-2019'}, {a:'purple', desc: 'grt', date: '12-10-2019'}],
desc: 'gfgfg',
},
{
colors: [{a:'black', desc: 'g', date: '12-12-2019'}, {a:'black', desc: 'grtrt', date: '12-12-2119'}, {a:'gray', desc:'', date: '01-01-2000'}],
desc: 'gfgfgfg',
}
],
selectProductIndex: 0,
index: 1
}
}
update = (item, colorIndex) => {
const {selectProductIndex} = this.state;
this.setState(prevState => {
return {
products: [
...prevState.products.slice(0, selectProductIndex),
Object.assign({}, prevState.products[selectProductIndex], {colors: prevState.products[selectProductIndex].colors.map((it,idx) => {
if(idx === colorIndex) { return item}
return it;
})}),
...prevState.products.slice(selectProductIndex + 1)
]
};
});
}
render () {
return (
<div>
<Items
product={this.state.products[this.state.selectProductIndex]}
update = {this.update}
/>
</div>
)
}
}
class Items extends Component {
render () {
return (
<ul>
{
this.props.product.colors
.map((item, index) =>
<Item
key= {index}
index = {index}
time = {item}
update = {this.props.update}
/>
)
}
</ul>
);
}
}
class Item extends Component {
save = () => {
const p = {
a:'violet', desc: 'gt', date: '12-12-1980'
}
this.props.update(p, this.props.index)
}
render() {
return (
<div>
<pre>{JSON.stringify(this.props.time)}</pre>
<button onClick={this.save}>Save</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />