In my React project, I'm passing in an array of objects and wondering how I can set default parameter for the nested array. I've already set it on the main array const Nav = ({ navItems = [] }) but couldn't figure out how to set for nested array. Logically, I tried const Nav = ({ navItems = [], navItems.subMenu = [] }) however seems like syntax is incorrect.
JSX:
const Nav = ({ navItems = [] }) => {
const subItems = navItems.map(el => el.subMenu)
return (
{navItems.map(item => (
<li>
{item.label}
<ul>
{subItems[0].map(subItem => (
<li>{subItem.item}</li>
))}
</ul>
</li>
))}
)
}
Props getting passed in:
<Nav
navItems={[
{
label: "About",
subMenu: [
{
id: 1,
item: "About Sub Item 1",
},
{
id: 2,
item: "About Sub Item 2",
},
{
id: 3,
item: "About Sub Item 3",
},
],
},
{
label: "Blog",
subMenu: [
{
id: 1,
item: "Blog Sub Item 1",
},
{
id: 2,
item: "Blog Sub Item 2",
},
{
id: 3,
item: "Blog Sub Item 3",
},
],
},
]}
/>
Here you go :
const Nav = ({ navItems = [{subMenu : []}] })
// I think you also need to set for label also, else will throw error while render
const Nav = ({ navItems = [{ label: "Default label" ,subMenu : []}] })
Suggestion , you can change the code block to something like this :
const Nav = ({ navItems = [] }) => {
// there is no need of below line
// const subItems = navItems.map(el => el.subMenu)
return (
{navItems.map((item) => (
<li>
{item.label}
<ul>
{item.subMenu.map(subItem => ( //<---- you can use it like this
<li>{subItem.item}</li>
))}
</ul>
</li>
))}
)
}
I think the issue might be that it can't deconstruct the array you're passing. If you do ({obj}), it's being deconstructed and it accesses its properties. If it can't be deconstructed, it fires up an error.
It should work like this
const Nav = ( navItems = [] ) => {
const subItems = navItems.map(el => el.subMenu)
return (
{navItems.map(item => (
<li>
{item.label}
<ul>
{subItems[0].map(subItem => (
<li>{subItem.item}</li>
))}
</ul>
</li>
))}
)
}
Related
I have a window where a group of 3 HTML combo box(select box) are generated on every button click.
lets say category,subcategory and data. options in sub category and data are dynamically rendered based on its previous selected value.
i am storing the selected values as an array of object like
const [exerciseData, setExerciseData] = useState([[]]);
Sample data:
exerciseData : [
//first group
[
category:"",
subcategory:"",
name:""
],
//second group
[
category:"",
subcategory:"",
name:""
]
]
So literally first group of input can be indicated by exerciseData[0] and next by exerciseData[1].category etc..
The problem is i want there groups like to be reordered by the user like moving up 1 group or moving down by 1.So i need the select boxes to be a controlled element. But how can i set that?
I tried like this :
<select name="category" value={exerciseData[i].category} ... > {options} </select>
when i give like this the select box value cannot be changed.it always stays at its default value.
any suggestion will be appreciated
Thanks in advance
To make an input controlled, you need to manually handle the input value via state. In your example, the input value seems to be fixed/static.
The following snippet is an example of how to create a controlled select input in React.
Update:
Made a full demo snippet. Please let me know if that is what you was looking for.
const {useState, useCallback, Fragment} = React;
const categories = [
{
id: 'CAT.1',
data: [
{
id: 'CAT.1 SUB.A',
data: [
{id: 'CAT.1 SUB.A OPT.1'},
{id: 'CAT.1 SUB.A OPT.2'},
{id: 'CAT.1 SUB.A OPT.3'}
]
},
{
id: 'CAT.1 SUB.B',
data: [
{id: 'CAT.1 SUB.B OPT.1'},
{id: 'CAT.1 SUB.B OPT.2'},
{id: 'CAT.1 SUB.B OPT.3'}
]
}
]
},
{
id: 'CAT.2',
data: [
{
id: 'CAT.2 SUB.A',
data: [
{id: 'CAT.2 SUB.A OPT.1'},
{id: 'CAT.2 SUB.A OPT.2'},
{id: 'CAT.2 SUB.A OPT.3'}
]
},
{
id: 'CAT.2 SUB.B',
data: [
{id: 'CAT.2 SUB.B OPT.1'},
{id: 'CAT.2 SUB.B OPT.2'},
{id: 'CAT.2 SUB.B OPT.3'}
]
}
]
},
];
function Select(props) {
const {
name,
options = [],
onIndex
} = props;
const [value, setValue] = useState(options[0] && options[0].id);
const onChangeHandler = useCallback((e) => {
onIndex && onIndex(options.findIndex((item) => item.id === e.target.value));
setValue(e.target.value);
}, [onIndex, options]);
return (
<label>
{name}:
<select value={value} onChange={onChangeHandler}>
{
options.map((item) => {
const {id} = item;
return <option value={id}>{id}</option>;
})
}
</select>
</label>
);
}
function Field() {
const [catIndex, setCatIndex] = useState(0);
const [subIndex, setSubIndex] = useState(0);
return (
<div>
<Select name='Category' options={categories} onIndex={setCatIndex}/>
<Select name='Sub Category' options={categories[catIndex].data} onIndex={setSubIndex}/>
<Select name='Option' options={categories[catIndex].data[subIndex].data}/>
</div>
);
}
function App() {
const [fields, setFields] = useState(() => [<Field />]);
const addField = useCallback(() => {
setFields((prevFields) => {
const index = prevFields.lenght;
return [...prevFields, <Field key={index} />];
});
}, []);
return (
<Fragment>
<form>
{fields}
</form>
<button type='button' onClick={addField}>Add Field</button>
</Fragment>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
form {
display: flex;
flex-direction: column;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id='root'></div>
Please let me know if you have any doubt.
I'm trying to render data from a variable / function into an antD Statistic Component. It appears it can only take a raw string or number, but I need to pass data from props into it.
Is there a way around this? See code below - I would like to pass scenarios[0].investment into "Statistic title="Investment" value={scenarios[0].investment}" but it doesn't allow it. Current code below works AS-IS but breaks when I replace it with scenarios[0].investment
class RenderSummary extends Component {
state = {
clicked: false,
hovered: false,
};
hide = () => {
this.setState({
clicked: false,
hovered: false,
});
};
handleHoverChange = visible => {
this.setState({
hovered: visible,
clicked: false,
});
};
handleClickChange = visible => {
this.setState({
clicked: visible,
hovered: false,
});
};
render() {
const scenarios = this.props.scenarios;
const data = [
{
title: 'Title 1', content: <Statistic title="Investment" value={0}/>,
},
{
title: 'Title 2', content: <Statistic title="T Savings" value={0}/>,
},
{
title: 'Title 2', content: <Statistic title="E Savings" value={0}/>,
},
];
const hoverContent = <div>This is hover content.</div>;
const clickContent = <div>This is click content.</div>;
const onClick = () => console.log("Works!");
return(
<div className="container">
<div className="site-card-wrapper">
<Row gutter={16}>
<Col span={12}>
<Card title="User Scenario 1" bordered={true}>
<List
grid={{ gutter: 16, column: 3 }}
dataSource={data}
renderItem={item => (
<List.Item>
{item.content}
</List.Item>
)}
/>
</Card>
</Col>
</Row>
</div>
</div>
);
}
}
scenarios in props as follows
"scenarios : [0: {id: 0, investment: 0, tSavings: 0, eSavings: 0 …},1: {id: 0, investment: 1, tSavings: 1, eSavings: 1 …}]"
I think the way you have structured your scenarios array or the way you are passing it is incorrect.
An example of how you could pass scenarios to RenderSummary:
const App = () => {
const scenarios = [
{investment: 1}, // Add your other properties
{investment: 2},
{investment: 3},
]
return <RenderSummary scenarios={scenarios}/>
}
If you pass scenarios like above, you can pass it in the way you wanted to:
const data = [
{
title: "Title 1",
content: <Statistic title="Investment" value={scenarios[0].investment} />,
},
{
title: "Title 2",
content: <Statistic title="T Savings" value={scenarios[1].investment} />,
},
{
title: "Title 2",
content: <Statistic title="E Savings" value={scenarios[2].investment} />,
},
];
I have a simple state:
const INITIAL_PEOPLE = {
name: 'Friends',
list: [
{
id: 1,
name: 'Alexander',
child: [
{
id: 2,
name: 'Romuald'
},
{
id: 3,
name: 'Vanessa'
}
]
},
{
id: 4,
name: 'Alex',
child: [
{
id: 5,
name: 'Jessica'
}
]
}
]
}
Now I want to print that. Look at my code
{prople.list.map(person =>
<li key={person.id}>{person.name}
{person.child.map(child =>
{child.name}
)}
</li>
)}
First loop works correctly but if I added second loop (child), console catch an error that person.child is undefined. Why it doesn't work?
You were not returning any value from your map function.
{INITIAL_PEOPLE.list.map((person) => {
return (
<li key={person.id}>
{person.name}
{person.child.map((child) => {
return child.name;
})}
</li>
);
})}
Your code should be written like this with a check if person have a child array.
{prople.list.map(person =>
<li key={person.id}>{person.name}
{person.child && person.child.length && person.child.map(child =>
{return child.name }
)}
</li>
)}
Here before calling the map function for child, we are checking if person has an attribute named child and if child is an array by checking if child has a property length by person.child.length. Notice the return part in the nested map.
I have a scrolling menu items, and the titles of each item is hardcoded into a const, along side with the id
const list = [
{ name: "category1", id: 0 },
{ name: "category2", id: 1 },
{ name: "category3", id: 2 },
{ name: "category4", id: 3 },
{ name: "category5", id: 4 },
{ name: "category6", id: 5 },
{ name: "category7", id: 6 },
{ name: "category8", id: 7 }
];
I have a json file that contains the category name for each child:
{
"results": [
{
"category": "category1",
"name": {
"title": "mr",
"first": "ernesto",
"last": "roman"
},
"email": "ernesto.roman#example.com",
"id": {
"name": "DNI",
"value": "73164596-W"
},
"picture": {
"large": "https://randomuser.me/api/portraits/men/73.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/73.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/73.jpg"
}
},
{
"category": "category2",
"name": {
"title": "mr",
"first": "adalbert",
"last": "bausch"
},
"email": "adalbert.bausch#example.com",
"id": {
"name": "",
"value": null
} etc....
I want to show these categories "category": "category1", as the titles of my menu, I now that I need to start stateless and add them from the JSON, the fetching part from the JSON is done locally in componentDidMount, but I am not sure how can I map them into appearing as menu names to make the menu dynamic, I basically want the same output but from the json not hardcoded. here is a sandbox snippet, would appreciate the help.
https://codesandbox.io/s/2prw4j729p?fontsize=14&moduleview=1
Just convert the JSON output to an object like list with a map function from the results and then set is as MenuItems on the state, which is what you pass to the function on render(). Like that.
import React, { Component } from "react";
import ScrollMenu from "react-horizontal-scrolling-menu";
import "./menu.css";
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return (
<div>
<div className="menu-item">{text}</div>
</div>
);
};
// All items component
// Important! add unique key
export const Menu = list =>
list.map(el => {
const { name, id } = el;
return <MenuItem text={name} key={id} />;
});
const Arrow = ({ text, className }) => {
return <div className={className}>{text}</div>;
};
export class Menucat extends Component {
state = {
selected: "0",
MenuItems: []
};
componentDidMount() {
fetch("menu.json")
.then(res => res.json())
.then(result => {
const items = result.results.map((el, idx) => {
return { name: el.category, id: idx };
});
this.setState({
isLoaded: true,
MenuItems: items
});
});
}
render() {
const { selected, MenuItems } = this.state;
// Create menu from items
const menu = Menu(MenuItems, selected);
return (
<div className="App">
<ScrollMenu
data={menu}
selected={selected}
onSelect={this.onSelect}
alignCenter={true}
tabindex="0"
/>
</div>
);
}
}
export default Menucat;
Cheers!
Looks like you don't have to hard code your category list at all. In your componentDidMount() fetch the json and group the results into separate categories like this:
const json = {
"results": [
{
category: "category1",
name: "Fred"
},
{
category: "category1",
name: "Teddy"
},
{
category: "category2",
name: "Gilbert"
},
{
category: "category3",
name: "Foxy"
},
]
}
const grouped = json.results.reduce((acc, cur) => {
if (!acc.hasOwnProperty(cur.category)) {
acc[cur.category] = []
}
acc[cur.category].push(cur)
return acc;
}, { })
// parent object now has 3 properties, namely category1, category2 and category3
console.log(JSON.stringify(grouped, null, 4))
// each of these properties is an array of bjects of same category
console.log(JSON.stringify(grouped.category1, null, 4))
console.log(JSON.stringify(grouped.category2, null, 4))
console.log(JSON.stringify(grouped.category3, null, 4))
Note that this json has 4 objects in result array, 2 of cat1, and 1 of cat 2 and cat3. You can run this code in a separate file to see how it works. Ofcourse you will be fetching the json object from server. I just set it for demonstration.
Then set teh state:
this.setState({ grouped })
Then in render() you only show the categories that have items like:
const menuBarButtons = Object.keys(this.state.grouped).map((category) => {
/* your jsx here */
return <MenuItem text={category} key={category} onClick={this.onClick} blah={blah}/>
/* or something , it's up to you */
})
I'm assuming you're showing the items based on the currently selected category this.state.selected. So after you have rendered your menu, you would do something like:
const selectedCatItems = this.state.grouped[this.state.selected].map((item) => {
return <YourItem name={item.name} key={item.id} blah={blah} />
})
Then render it:
return (
<div className="app">
<MenuBar blah={blah}>
{menuBarButtons}
</Menubar>
<div for your item showing area>
{selectedCatItems}
</div>
</div>
)
Also, don't forget to change your onClick() so that it sets this.state.selected state properly. I believe you can figure that out yourself.
Hope it helps.
PS: I didn't write a whole copy/paste solution to your problem simply because I'm reluctant to read and understand your UI details and the whole component to component data passing details..
I have a list of items that users can click to add an item to their array. Rather than updating the value in the array, it is pushing a new value with the same number. I am getting map from Lodash FP.
This is what I am using to map through:
{map((item) => (<Item {...item} key={btoa(Math.random()).substring(0, 12)} />), items)}
If I am to click on an item in the array, the result I would get is:
0: {id: "item1", quantity: 1}
1: {id: "item1", quantity: 1}
Yet the result I would expect from this is:
0: {id: "item1", quantity: 2}
Implementation:
Component that allows you to add an item:
const Product = ({add, id, title, image}) => (
<div className={styles.product} onClick={() => add(id)}>
<img src={image} alt={title} className={styles.productImage}/>
{title}
</div>
);
export default connect(() => ({}), {add})(Product);`
Component that loops through the results:
const Cart = connect(
() => ({}),
{clear}
)(({items, clear, total}) => {
return (
<div>
<Heading><FontAwesomeIcon icon={faShoppingCart} /> Cart</Heading>
{items.length ? <button onClick={clear}>Clear all items</button> : null }
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{/* The original version, with a randomly generated key */}
{items.length ? map((item) =>
(<Item {...item} key={btoa(Math.random()).substring(0, 12)} />),
items) : <tr><td>Your cart is empty!</td></tr>}
</tbody>
</table>
{items.length ? <div className={styles.total}>${total}</div> : null }
</div>);
});
export default connect((state) => {
return {
items: state.cart.items,
total: reduce(
(sum, {id, quantity}) => sum + products[id].price * quantity,
0,
state.cart.items
).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'),
};
})(Cart);
Action that is being called:
[ADD_ITEM]: (state, {payload: id}) => ({
...state,
items: [
...state.items,
{id, quantity: 1},
],
}),
If you want to update state via id, then you'll want to map through the items array and find the matching id to update the matching quantity. The example below utilizes React state, but is no different for redux reducer state (return {...state, items: state.items.map(...etc)}).
Working example: https://codesandbox.io/s/rjmx8vw99p
import React, { Component } from "react";
export default class App extends Component {
state = {
items: [
{ id: "Apples", quantity: 1 },
{ id: "Strawberries", quantity: 1 },
{ id: "Grapes", quantity: 1 },
{ id: "Apricots", quantity: 1 }
]
};
handleClick = id => {
this.setState(prevState => ({
// ...prevState (not needed for this example, but needed for redux)
items: prevState.items.map(
item =>
id === item.id
? { id, quantity: item.quantity + 1 }
: { ...item }
)
}));
};
render = () => (
<div className="container">
<h1>Updating Values Inside Array</h1>
{this.state.items.map(({ id, quantity }) => (
<button
style={{ marginRight: 10 }}
className="uk-button uk-button-primary"
key={id}
onClick={() => this.handleClick(id)}
>
{id} ({quantity})
</button>
))}
</div>
);
}
What's happening in your code above is simply appending the array with a new object:
items: [
{ id: "item1", quantity: 1 }, // ...spread out previous objects in array
{ id: "item2", quantity: 1 },
{ id: "item3", quantity: 1 },
{ id: "item4", quantity: 1 },
{ id: "item1", quantity: 1 } // add another object
]
Also, this:
export default connect(() => ({}), {add})(Product);
should be this:
export default connect(null, {add})(Product);