React AntDesign Menu Sub Item ... make it work like component - javascript

For the AntD menu ... we utilise <Menu>, <Menu.Item>, <SubMenu>.
But I don't want to use these for navigation but rather for representation. I want to display the attributes of an object using a dropdown as such.
For eg. Apple -> red, fruit; Cucumber -> green, vegetable; would be displayed as a menu with Apple and Cucumber as the Submenu headings, and the dropdowns for each would be red, fruit and green, vegetable respectively.
But I don't want to predefine attributes and Submenu headings. If it was a component (cards for example), I could've made the component render per object, so that if there were 10 objects, 10 cards (for example) would be rendered.
Is it possible to do the same for <SubMenu> and <Menu.Item> where I send the data and it first looks at the key 'Name' and renders as a Submenu Heading and renders attributes individually as Menu Items within the Submenu?
Are there any alternatives I can make use of?
Not sure If my question is very clear ... I'm happy to clarify anything if confused.

Not sure if this is what you want
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Menu } from "antd";
const { SubMenu } = Menu;
const data = [
{
Name: "Apple",
Colour: "red",
Type: "fruit"
},
{
Colour: "green",
Type: "vegetable",
Name: "Cucumber",
Season: "spring"
},
{
Name: "Book",
Title: "hello",
Author: "nick"
}
];
const Sider = () => {
const [openKeys, setOpenKeys] = React.useState(["sub1"]);
const onOpenChange = (keys) => {
const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1);
if (data.indexOf(latestOpenKey) === -1) {
setOpenKeys(keys);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
}
};
return (
<Menu mode="inline" onOpenChange={onOpenChange} style={{ width: 256 }}>
{data.map((each) => (
<SubMenu key={each.Name} title={each.Name}>
{Object.entries(each).map(
([key, value]) =>
key !== "Name" && (
<Menu.Item key={each.Name + "-" + key}>{value}</Menu.Item>
)
)}
</SubMenu>
))}
</Menu>
);
};
ReactDOM.render(<Sider />, document.getElementById("container"));
CodeSandbox Demo
Let me know if this works for you

Related

Calling dispatch function of useReducer (used in useContext ) causes the subscriber components to re-evaluate

The problem is that when I call the dispatch function of the useReducer hook declared in the Context SelectedMealContext , the<Item/> gets re-evaluated even if I am not changing the state of useReducer in the context.
My <MealLists/> component has array of objects and renders <Item/> by map() from mealsData .
The component tree is <MealLists/> > <Item/> > <ItemAmount/> > <Input/>
Only the <Item/> has called the Context as useContext(SelectedMealContext)
Note : <Input/> component is export default React.memo(Input) thats why it is re-evaluated only when <App/> loads .
When the app loads first the all of logged messages get shown ,Now on first click of Add button I get the re-evaluated messages again but after the first click and so on I don't get further re-evaluation as shown below in images:
The Console is cleared after App loaded and each call of dispatch function by click Add button
**
When app loads :
**
**
At first click :
**
**
At second click :
**
import Item from './item';
import Card from '../UI/card'
import styles from './mealists.module.css'
const mealsData = [
{
id: 'm1',
name: 'Sushi',
description: 'Finest fish and veggies',
price: 22.99,
},
{
id: 'm2',
name: 'Schnitzel',
description: 'A german specialty!',
price: 16.5,
},
{
id: 'm3',
name: 'Barbecue Burger',
description: 'American, raw, meaty',
price: 12.99,
},
{
id: 'm4',
name: 'Green Bowl',
description: 'Healthy...and green...',
price: 18.99,
},
]
const MealLists =()=>{
console.log("Meals Lists components");
return (
<>
<Card card={styles.meal} style={{marginTop: '200px'}}>
<ul >
{mealsData.map((mealItem)=>{
return <Item
key={mealItem.id}
id={mealItem.id}
mealName={mealItem.name}
mealDescription={mealItem.description}
mealPrice={mealItem.price}/>
})}
</ul>
</Card>
</>
)
}
export default MealLists;
My Context SelectedContext is as :
import React,{useReducer} from 'react'
export const SelectedMealContext = React.createContext();
const SelectedDishesReducer =(state,action)=>{
if(action.type ==='increment'){
console.log("Increment")
}else if(action.type === 'decrement'){
console.log("Decrement")
}else if(action.type === "new_meal"){
console.log("New Meal")
console.log(action.data)
}
}
const SelectedDishes = []
const SelectedMealContextProvider= (props) => {
console.log("Selected Meals Context evaluated");
const [SelectedMeals , dispatchAction ] = useReducer(SelectedDishesReducer,SelectedDishes);
console.log(SelectedMeals);
return (
<SelectedMealContext.Provider
value={{
SelectedMeals : SelectedMeals,
onIncrement : dispatchAction,
onDecrement : dispatchAction,
onAdd : dispatchAction
}}
>{props.children}
</SelectedMealContext.Provider>
)
}
export default SelectedMealContextProvider ;
and the subscriber component is <Item/>
import React ,{useContext} from 'react'
import styles from './item.module.css'
import ItemAmount from './ItemAmount'
import {SelectedMealContext} from '../DataContext/SelectedContext'
const Item =(props )=>{
console.log(`Item component for : ${props.id}`)
const Add = useContext(SelectedMealContext).onAdd;
const AddSelectedItems =(amount)=>{
console.table(props.id , props.mealName ,props.mealPrice)
let selectedDish = {
mealId: props.id,
mealName: props.mealName,
price: props.mealPrice,
number_of_meal : amount}
Add({type: "new_meal",data: selectedDish})
// console.log(`SelectedDish : ${selectedDish.number_of_meal}`)
}
return(
<li key={props.id} className={styles.meal}>
<div>
<h1 className={styles.mealName}>{props.mealName}</h1>
<h3 className={styles.description}>{props.mealDescription}</h3>
<h2 className={styles.price}>{props.mealPrice}</h2>
</div>
<ItemAmount AddSelectedItems={AddSelectedItems}/>
</li>
)
}
export default Item ;

My state open all of my submenu but i just want one submenu open

I have a left-menu and when you click on a element, the sub-menu of the element appear.
But with my actual code, when a click on a element, all of my submenu appear.
I know my method is not right, but i don't know how to do :(
My example code :
import { useState } from 'react'
export default function Menu(){
const [visibleSubCategorie, setVisibleSubCategorie] = useState(false)
const Menu = [{
name: 'Homme', link : '/homme-fr', subCategory: false
}, {
name: 'Femme', link : '/femme-fr', subCategory: [{
name: 'haut', link : '/femme-haut-fr'
},{
name: 'bas', link : '/femme-bas-fr'
}]
},{
name: 'Enfant', link : '/enfant-fr', subCategory: [{
name: 'haut', link : '/enfant-haut-fr'
},{
name: 'bas', link : '/enfant-bas-fr'
}]
}]
console.log("Menu -> Menu", Menu)
return(
<>
{Menu.map(item=>
<div>
{item.subCategory ?
<>
<button type="button" onClick={() => setVisibleSubCategorie(!visibleSubCategorie)}>{item.name}</button>
{visibleSubCategorie && item.subCategory.map(subCategorys=>
<>
<p>{subCategorys.name}</p>
</>
)}
</>
:
<a href={item.link}>{item.name}</a>
}
</div>
)}
</>
)
}``
example : when i click at the button "Femme" to see the sub-category of femme, it's like i click too on the button "Enfant".
I can create a composant and make the usestate "const [visibleSubCategorie, setVisibleSubCategorie] = useState(false)" inside and this composant inside the map but i know another method exist.
You are using the same piece of state to control all of your subCategories. A possible solution would be to useState as an array of string values for each subcategory.
const [visibleSubCategorie, setVisibleSubCategorie] = useState([])
setVisibleSubCategorie([...visibleSubCategorie, subCategorys.name])
Then check to see if that name exists in the array to know if you should show the subcategory.
{visibleSubCategorie.includes(subCategorys.name) && item.subCategory.map(subCategorys=>
You will then have to remove that item from the array when closing.
You could solve this using a method similar to what #Kyler suggested.
I suggest using a HOC, like this:
const setOpen = (setOpen, opened) => () => setOpen(!opened);
And then in your JSX:
onClick={setOpen(setVisibleSubCategorie, visibleSubCategorie)}
Note that in order for this to work, you'd have to have state for each of your sections.

Switching between dynamic Pickers crashes app

I'm new to React Native and need to dynamically populate a Picker depending on the value of a selected Radio Button. For example, in the following code if you select "Animals" the Picker would contain "Dog", "Cat", and "Fish" and if you selected "People" the Picker would contain "Will Smith".
However, if I select "Cat" or "Fish" from Animals and then click on the People radio button, the app crashes. When debugging I saw I was getting an Array Index Out of Bounds Exception, and I'm guessing it's probably because I'm selecting index 1 or 2 in the Animals array, but there is only 1 item in the People array. It must be trying to get index 1 or 2 from the People array, but that is obviously out of bounds.
The behavior I need is for the Picker to go back to the default option "Select one" whenever I change to a different radio button.
import React, { Component } from 'react';
import { View, Picker } from 'react-native';
import RadioGroup from 'react-native-radio-buttons-group';
// Constants for currently selected radio button
const ANIMALS = 0
const PEOPLE = 1
// Constant for when nothing is selected in the Picker
const NOTHING = 'nothing'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
selectedVal: NOTHING, // Currently selected item
radioType: ANIMALS, // Currently selected radio button
radioValues: [
{
type: ANIMALS,
label: "Animals",
},
{
type: PEOPLE,
label: "People",
},
]
}
}
getCategories = (type) => {
if (type === ANIMALS) {
return [
"Dog", "Cat", "Fish"
]
} else if (type === PEOPLE) {
return [
"Will Smith"
]
}
}
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<RadioGroup
flexDirection="row"
radioButtons={this.state.radioValues}
onPress={(data) => {
this.setState({
radioValues: data
})
let selected = this.state.radioValues.find(e => e.selected === true)
if (selected.type === ANIMALS) {
this.setState({ radioType: ANIMALS })
} else if (selected.type === PEOPLE) {
this.setState({ radioType: PEOPLE })
}
}} />
<Picker
selectedValue={this.state.selectedVal}
style={{ height: 50, width: 250 }}
onValueChange={(itemValue) =>
this.setState({ selectedVal: itemValue })
}>
<Picker.Item label="<Select one>" value={NOTHING} />
{
this.getCategories(this.state.radioType).map((categoryName, index) => {
return <Picker.Item label={categoryName} value={categoryName} key={index} />
})
}
</Picker>
</View>
)
}
}
This is what the app looks like:
I tried manually setting this.state.selectedVal back to NOTHING inside of componentDidUpdate() but the app still crashes if I select "Cat" or "Fish" from the Animals view and then switch to People. Strangely it works when I run it from a new Expo project, but not with this project where I used the react-native CLI.
Does anyone know how to fix the array index out of bounds exception?

I have a react native app and I want to know how to flag pictures for use in another screen

I have an app that cycles through pictures on the press of a button. I want to add another button that flags a picture when pressed for use in another screen, but I don't know the proper way to do this.
I have a few ideas, but I don't know the proper way to do this and I want to learn. I am very flexible as far as changing my objects etc.
import React, {useState} from 'react';
import { StyleSheet, Text, View, Image, Button } from 'react-native';
import { type ImageHolder } from './ImageHolder'
import OuterComponent from "./OuterComponent"
const imageholder2: ImageHolder[] = [
{
id: "1",
actualimage: require("./images/image1.jpeg"),
},
{
id: "2",
actualimage: require("./images/image2.jpg"),
},
{
id: "3",
actualimage: require("./images/image3.jpg"),
},
{
id: "4",
actualimage: require("./images/image4.jpg"),
},
];
export default function App() {
const [currentImageIndex, setCurrentImageIndex] = useState(0)
const scopeSpecificFunction = () =>
setCurrentImageIndex(currentImageIndex == imageholder2.length - 1 ?
0 :
currentImageIndex + 1 )
return (
<View>
<View>
{
imageholder2[currentImageIndex] &&
<Image
key={imageholder2[currentImageIndex].id}
style = {{
width: 300,
height: 300,
}}
source={imageholder2[currentImageIndex].actualimage}
/>
}
</View>
<View>
<OuterComponent callbackfunction ={()=>scopeSpecificFunction()}/>
</View>
</View>
);
}

React ES6: Get selected value in dropdown list using semantic UI

Given the following data, how can I get the birds name and push it (Using the add button) to a new array to be displayed in another div (Using react es6)? So basically I want a user to click a bird from the semantic dropdown and display it in a different div for example shown below. This is probably simple but I can't seem to find a way to it when I'm using Semantic elements. Do I need to use onChange?
I need to to do this in a class I am exporting (react) (just havent shown the class/constructor/state definitions)
<div>
<p>How can i display 'Bird_Name' here?<p>
</div>
addClick = () => {
}
const {
Button,
Container,
Divider,
Dropdown,
Header,
Message,
Segment,
} = semanticUIReact
const birds = [
{
"value": "001",
"Bird_Name": "Eurasian Collared-Dove"
},
{
"value": "002",
"Bird_Name": "Bald Eagle"
},
{
"value": "003",
"Bird_Name": "Cooper's Hawk"
},
];
const options = birds.map(({ ID, Bird_Name }) => ({ value: ID, text: Bird_Name }))
const App = () => (
<Container>
<Divider hidden />
<Header as='h1'>Semantic-UI-React</Header>
<Dropdown
placeholder='Select...'
selection
search
options={options}
renderLabel={({ Bird_Name }) => 1}
/>
<button className="ui primary button add" onClick={this.addClick}>Add</button>
</Container>
)
// ----------------------------------------
// Render to DOM
// ----------------------------------------
const mountNode = document.createElement('div')
document.body.appendChild(mountNode)
ReactDOM.render(<App />, mountNode)
So, what you basically want is the onChange function which will display.
<Dropdown
placeholder='Select...'
selection
search
options={options}
renderLabel={({ Bird_Name }) => 1}
onChange={this.getBird}
/>
and make a getBird function
getBird = (event, {value}) => {
console.log(value);
let bird_name = event.target.textContent;
console.log(bird_name);
}
The value and text variable in the getBird function are basically the value and bird_name of the selected bird from the dropdown.

Categories

Resources