Apply style on specific react map item - javascript

I would like to apply a class on a specific element of loop map.
const items = [
{
key: 'Menu1key',
content: 'Menu 1',
},
{
key: 'Menu2',
content: 'Menu2key'
},
{
key: 'Menu3Key',
content: 'Menu3',
children: [{
key: 'SubMenu3Key',
content: 'SubMenu3',
},
{
key: 'SubMenu5Key',
content: 'SubMenu5'
}]
},
{
key: 'Menu4Key',
content: 'Meu4',
children: [{
key: 'SubMenu4Key',
content: 'SubMenu4',
},
{
key: 'SubMenu5Key',
content: 'SubMenu5'
}]
}
]
const StackedMenu = (props) => {
let [chevronRotation, setChevronRotation] = useState('')
let [itemClicked, setItemClicked] = useState(props.itemClicked);
let [depth, setDepth] = useState(props.depth)
return (<Menu primary defaultActiveIndex={0} activeIndex={itemClicked} vertical pointing style={{ width: '100%' }}>
{props.items.map((item, index) => {
return (<>
<MenuItem onClick={(e, data) => { setItemClicked(data.index); setChevronRotation(`rotate`) }} index={index} key={index} pointing="start" >
<Menu.ItemContent style={depth > 0 ? { paddingLeft: '0.5em' } : null} >{item.content}</Menu.ItemContent>
{item.children ?
(<Menu.ItemIcon id={index} style={{ right: "0px", position: "absolute" }} className={props.itemClicked === index && item.children ? 'rotate' : ''}>
<ChevronEndMediumIcon />
</Menu.ItemIcon>) : null}
</MenuItem>
{itemClicked === index && item.children ? <div><StackedMenu items={item.children} depth={depth + 1} itemClicked={0} /></div> : null}
</>)
})}
</Menu>)
}
I using a recursive component to create children sub menu.
Actually when I open a menu all chevrons get expanded , what I'm looking for is that only the chevron of the clicked item get expanded.

Your problem is that you're setting a local state to control whether it should be open, but then checks on the props for the clicked item.
Change this line:
// I removed props from props.itemClicked
<Menu.ItemIcon id={index} style={{ right: "0px", position: "absolute" }} className={itemClicked === index && item.children ? 'rotate' : ''}>

You should use the component's state to check if menu item is opened or not.
Also, I've removed unnecessary depth state. Hope this could help.
const StackedMenu = ({items, depth, ...otherProps}) => {
let [chevronRotation, setChevronRotation] = useState('');
let [itemClicked, setItemClicked] = useState(otherProps.itemClicked);
return (
<Menu
primary
defaultActiveIndex={0}
activeIndex={itemClicked}
vertical
pointing
style={{width: '100%'}}>
{items.map((item, index) => {
return (
<>
<MenuItem
onClick={(e, data) => {
setItemClicked(data.index);
setChevronRotation(`rotate`);
}}
index={index}
key={index}
pointing="start">
<Menu.ItemContent
style={depth > 0 ? {paddingLeft: '0.5em'} : null}>
{item.content}
</Menu.ItemContent>
{item.children ? (
<Menu.ItemIcon
id={index}
style={{right: '0px', position: 'absolute'}}
className={itemClicked === index ? 'rotate' : ''}>
<ChevronEndMediumIcon />
</Menu.ItemIcon>
) : null}
</MenuItem>
{itemClicked === index && item.children ? (
<div>
<StackedMenu
items={item.children}
depth={depth + 1}
itemClicked={0}
/>
</div>
) : null}
</>
);
})}
</Menu>
);
};

Related

Splice deleting last element of an array of object in react

I have text fields that are being added dynamically on button click and mapping the items depending on state variable [inputList]. The problem is when I'm trying to delete a field using the splice or filter option it is always deleting the last item but in the console it is showing that it has deleted the specific item that I wanted but in UI it's removing the last field no matter which field I delete. For example, if there are 3 fields named 'A', 'B'& 'C'. If I try to delete B, it is deleting B but not affecting in the UI. In the UI it is showing only the last item is deleted that is C has been deleted but in console B has been deleted.
Here is my code:
<FormControl style={{ display: "flex", flex: 1 }}>
<FormGroup>
{inputList.map((x, i) => {
return (
<div key={i}>
<div className={styles.options}>
{!distributePoints ? (
<FormControlLabel
key={i}
value={x.isCorrect}
control={<Checkbox />}
checked={x.isCorrect}
onChange={(e) => handleIsCorrect(e, i)}
/>
) : (
""
)}
<div className="editor">
<BubbleEditor
handleAnswer={handleInputChange}
index={i}
theme="bubble"
/>
</div>
{distributePoints ? (
<TextField
variant="standard"
name="points"
InputProps={{
disableUnderline: true,
type: "number",
}}
value={x.points}
onChange={(e) => handlePointChange(e, i)}
className={styles.inputCounter}
/>
) : (
""
)}
{inputList.length !== 1 && (
<IconButton
onClick={() => handleRemoveClick(i)}
className={styles.icon}
>
<DeleteIcon
sx={{ fontSize: 24, color: "#3699FF" }}
/>
</IconButton>
)}
</div>
{inputList.length - 1 === i && (
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<button
onClick={handleAddClick}
type="button"
className={styles.addButton}
>
<img src={PlusSign} alt="" /> Add another answer
</button>
<div className={styles.label}>
<Checkbox
checked={shuffle}
onChange={handleShuffle}
style={{ color: "#00A3FF" }}
/>
Shuffle answer
</div>
</div>
)}
</div>
);
})}
</FormGroup>
</FormControl>
Remove function:
const handleRemoveClick = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
Add function:
const handleAddClick = () => {
setInputList([...inputList, { answer: "", isCorrect: false, points: 0 }]);
};
State array of object that is being used:
const [inputList, setInputList] = useState([
{ answer: "", isCorrect: false, points: 0 },
]);
While updating a state property, using its previous value, the callback argument should be used.
This is due to the possible asynchronous nature of state updates
var handleRemoveClick = (index)=> {
setInputList((list)=> (
list.filter((o,i)=> (index != i))
);
};
var handleAddClick = ()=> {
setInputList((list)=> (
list.concat({answer:'', isCorrect: false, points: 0})
);
};

Keep accordion items open when clicked on another

So I'm having this accordion component, which currently opens and close on click. But when I click on e.g. first item and it opens, when I click on second item the first close while I want to keep it open. Not sure how should I approach this.
const [activeTab, setActiveTab] = React.useState(props.tabIndex);
const tabs = props.tabs.map((tab) => {
return (
<>
<TabContainer>
<Tab
key={tab.index}
onClick= {() => {activTab == tab.index ? setActiveTab(-1) : setActiveTab(tab.index)})
className={typeof tab.content === 'string' ? '' : 'unactive-tab'}
>
{tab.name}
</Tab>
</TabContainer>
{tab.index === activeTab ?
<div
id="content"
style={{ width: '100%', margin: '2rem 0' }}
dangerouslySetInnerHTML={{ __html: tab.content as string }}
/>
: null}
</>
);
});
The best approach is to separate the Accordion item in its own component based on what you want and each of them would be able to have a "opened" state.
I won't give you a full solution, but I would do something similar to this:
const AccordionItem = (props) => {
// Each item has it's own state.
const [isOpened, setIsOpened] = React.useState(false);
return (YOUR COMPONENT);
};
And in your main component, you just do this:
const tabs = props.tabz.map((tab) => {
return (
<AccordionItem {...tab} /≥
);
});
change props.tabIndex to be an array of tabs indexes rather than just 1 index.
Assuming you now send an array of indexes through props. e.g
// props.tabIndexes = [1,5,2]
const [activeTabs, setActiveTabs] = React.useState(props.tabIndexes);
const tabs = props.tabs.map((tab) => {
return (
<>
<TabContainer>
<Tab
key={tab.index}
onClick= {() => {activeTabs.includes(tab.index) ? setActiveTabs(activeTabs.filter(tabIndex => tabIndex !== tab.index ) ) : setActiveTabs(activeTabs.push(tab.index)})
className={typeof tab.content === 'string' ? '' : 'unactive-tab'}
>
{tab.name}
</Tab>
</TabContainer>
{activeTabs.includes(tab.index) ?
<div
id="content"
style={{ width: '100%', margin: '2rem 0' }}
dangerouslySetInnerHTML={{ __html: tab.content as string }}
/>
: null}
</>
);
});

Finding children elements that are missing unique keys? (reactJS)

I currently have this bit of code, that is working, but it is throwing a Warning: Each child in a list should have a unique "key" prop.
Check the render method of `TopMenuDropdown`. See https://reactjs.org/link/warning-keys for more information.
MenuItem#http://localhost:3000/protis-react/static/js/vendors~main.chunk.js:2815:110
TopMenuDropdown#http://localhost:3000/protis-react/static/js/main.chunk.js:2432:5
div
div
App#http://localhost:3000/protis-react/static/js/main.chunk.js:63:5
Router#http://localhost:3000/protis-react/static/js/vendors~main.chunk.js:174391:30
BrowserRouter#http://localhost:3000/protis-react/static/js/vendors~main.chunk.js:174011:35
at me. I am not even sure where to track down what is missing a key, or where I missed putting it in. Any way for me to track it, or is someone here able to find the missing elements that need the unique keys. I tried to put a key on almost anything I generated originally, but I'm not sure what went wrong where, and this has been bothering me.
import React from 'react';
import {Menu, MenuButton, MenuDivider, MenuItem, SubMenu} from '#szhsin/react-menu';
import '#szhsin/react-menu/dist/index.css'
const tooltipStyle = {
position: "absolute",
pointerEvents: "none",
backgroundColor: "#D7D7A6",
border: "1px solid",
padding: "1px 8px",
whiteSpace: "nowrap",
zIndex: 200
};
class TopMenuDropdown extends React.Component {
state = {
pos: {x: 0, y: 0},
tooltip: '',
show: false
};
createTooltip = (tooltip) => ({
onMouseEnter: ({clientX, clientY}) => {
this.setState(
{
pos: {x: clientX, y: clientY},
tooltip: tooltip,
show: true
}
);
},
onMouseLeave: () => {
this.setState(
{
show: false
}
)
}
});
handleSelect = ({value}) => {
this.props.onClick(value);
}
renderMenuItem(menuAnchor, menuItems, isSubMenu) {
if (isSubMenu) {
return (
<SubMenu key={menuAnchor.id}
label={
<div
key={menuAnchor.id}
{...this.createTooltip(menuAnchor.attributes.title)}
>
{menuAnchor['#text']}
</div>
}
>
{this.menuGeneration(menuItems, menuAnchor)}
</SubMenu>
);
}
return (
<>
<Menu
style={{display: 'flex', float: 'left'}}
key={menuAnchor.id}
menuButton={
<MenuButton
key={menuAnchor.id}
{...this.createTooltip(menuAnchor.attributes.title)}>
{menuAnchor['#text']}
</MenuButton>}
onChange={({open}) => !open && this.setState({show: false})}
>
{this.menuGeneration(menuItems, menuAnchor)}
</Menu>
{this.state.show && (
<div
key={menuAnchor.id}
style={{
...tooltipStyle,
left: this.state.pos.x,
top: this.state.pos.y
}}
>
{this.state.tooltip}
</div>
)}
</>
);
}
menuGeneration(menuItems, menuAnchor) {
if (menuItems === undefined) {
return <></>;
}
if (!Array.isArray(menuItems)) {
menuItems = [menuItems];
}
return (
menuItems.map(({a, attributes, ul}) => {
if (ul !== undefined && ul.li !== undefined) {
return (
this.renderMenuItem(a, ul.li, true)
);
}
if (a === undefined) {
return (
<MenuDivider key={menuAnchor.id} />
)
}
return (
<MenuItem
key={menuAnchor.id}
value={a.attributes.id}
onClick={(id) => this.handleSelect(id)}
{...this.createTooltip(a.attributes.title)}
>
{a['#text']}
</MenuItem>)
}
)
)
}
render() {
if (!this.props.topMenu.hasOwnProperty('ul')) {
return null;
}
const menuItemRendering = this.props.topMenu.ul.li.map(({a, ul}) => {
return this.renderMenuItem(a, ul.li, false);
});
return (
<div style={{display: 'flex'}}>
{menuItemRendering}
</div>
)
}
}
export default TopMenuDropdown;
Issue is in renderMenuItem in the "else" branch when you render Menu and a div into a Fragment. When mapping JSX a React key needs to be on the outermost returned mapped element, the Fragment in this case.
renderMenuItem(menuAnchor, menuItems, isSubMenu) {
if (isSubMenu) {
return (
<SubMenu
...
>
{this.menuGeneration(menuItems, menuAnchor)}
</SubMenu>
);
}
return (
<Fragment key={menuAnchor.id}> // <-- add missing React key here
<Menu
...
>
{this.menuGeneration(menuItems, menuAnchor)}
</Menu>
{this.state.show && (
<div
...
>
{this.state.tooltip}
</div>
)}
</Fragment>
);
}
Try replacing key={menuAnchor.id} with key={a.id} for items directly in menuGeneration:
if (a === undefined) {
return <MenuDivider key={a.id} />
}
return (
<MenuItem
key={a.id}
value={a.attributes.id}
onClick={(id) => this.handleSelect(id)}
{...this.createTooltip(a.attributes.title)}
>
{a['#text']}
</MenuItem>
)

How to manage multiple navigation items states?

I'm trying to implement a horizontal menu with antd components.
When clicking the nav items the submenu is not showing correctly.
Codesandbox demo.
const MenuList = [
{
name: "Navigation two - Submenu",
subMenuRoutes: [
{
name: "A- item1",
url: "/item1Url1"
},
{
name: "A - item2",
url: "/item1Url2"
}
]
},
{
name: "Navigation Three - Submenu",
subMenuRoutes: [
{
name: "B- item1",
url: "/item1Url1"
},
{
name: "B - item2",
url: "/item1Url2"
}
]
}
];
function TextAreaManager() {
const [showMenu, setShowMenu] = useState(false);
return (
<Tabs onTabClick={() => setShowMenu(prev => !prev)}>
{MenuList.map(item => {
return (
<TabPane
tab={
<>
<Icon type="setting" />
{item.name}
<Icon
type={showMenu ? "up" : "down"}
style={{ marginLeft: "10px" }}
/>
</>
}
>
{showMenu && (
<Menu>
{item.subMenuRoutes.map(childItem => {
return (
<Menu.Item key={childItem.url}>{childItem.name}</Menu.Item>
);
})}
</Menu>
)}
</TabPane>
);
})}
</Tabs>
);
There are a few issues that need to be handled:
Assign unique key for every array item in order to render components correctly.
menuList.map(item => <TabPane key={item.name}></TabPane>);
You need to manage every menu's state in order to show menus correctly with the corresponding icon showMenuManager[item.name]:
<Tabs
onTabClick={e =>
setShowMenuManager(prev => {
const newState = { ...initMenuState, [e]: !prev[e] };
console.log(newState);
return newState;
})
}
/>;
const initMenuState = {
"Navigation two - Submenu": false,
"Navigation Three - Submenu": false
};
function TopMenuManager() {
const [showMenuManager, setShowMenuManager] = useState(initMenuState);
return (
<Tabs ... >
{menuList.map(item => (
<TabPane
key={item.name}
tab={
<>
...
<Icon
type={showMenuManager[item.name] ? "up" : "down"}
/>
</>
}
>
{showMenuManager[item.name] && ...}
</TabPane>
))}
</Tabs>
);
}
Check the final example and forked sandbox:

React Native Android change scene navigator

I'm trying to build a tabview and I can't find out how to change and render scenes. My main view is this one (App.js) :
<View style={{flex: 1}}>
<TabView
ref="tabs"
onTab={(tab) => {
this.setState({tab});
}}
tabs={[
{
component: List,
name: 'Découvrir',
icon: require('../assets/img/tabs/icons/home.png')
},
{
component: Friends,
name: 'Amis',
icon: require('../assets/img/tabs/icons/friend.png'),
pastille: this.state.friendsPastille < 10 ? this.state.friendsPastille : '9+'
},
{
component: RecoStep1,
icon: require('../assets/img/tabs/icons/add.png'),
hasShared: MeStore.getState().me.HAS_SHARED
},
{
component: Notifs,
name: 'Notifs',
icon: require('../assets/img/tabs/icons/notif.png'),
pastille: this.state.notifsPastille < 10 ? this.state.notifsPastille : '9+'
},
{
component: Profil,
name: 'Profil',
icon: require('../assets/img/tabs/icons/account.png')
}
]}
initialSkipCache={!!this.notifLaunchTab}
initialSelected={this.notifLaunchTab || 0}
tabsBlocked={false} />
</View>
The TabView component is this one and it works fine. Only the navigator renders a blank screen only...
renderTab(index, name, icon, pastille, hasShared) {
var opacityStyle = {opacity: index === this.state.selected ? 1 : 0.3};
return (
<TouchableWithoutFeedback key={index} style={styles.tabbarTab} onPress={() => {
if (this.props.tabsBlocked) {
return;
}
this.resetToTab(index);
}}>
<View style={styles.tabbarTab}>
<Image source={icon} style={opacityStyle} />
{name ?
<Text style={[styles.tabbarTabText, opacityStyle]}>{name}</Text>
: null}
</View>
</TouchableWithoutFeedback>
);
}
resetToTab(index, opts) {
this.setState({selected: index});
}
renderScene = (route, navigator) => {
var temp = navigator.getCurrentRoutes();
return temp[this.state.selected].component;
}
render() {
return (
<View style={styles.tabbarContainer}>
<Navigator
style={{backgroundColor: '#FFFFFF', paddingTop: 20}}
initialRouteStack={this.props.tabs}
initialRoute={this.props.tabs[this.props.initialSelected || 0]}
ref="tabs"
key="navigator"
renderScene={this.renderScene}
configureScene={() => {
return {
...Navigator.SceneConfigs.FadeAndroid,
defaultTransitionVelocity: 10000,
gestures: {}
};
}} />
{this.state.showTabBar ? [
<View key="tabBar" style={styles.tabbarTabs}>
{_.map(this.props.tabs, (tab, index) => {
return this.renderTab(index, tab.name, tab.icon, tab.pastille, tab.hasShared);
})}
</View>
] : []}
</View>
);
}
I know I'm doing something wrong, but I can't figure out what ... Changing tabs doesn't display anything as shown below..
I used NavigatorIOS for ans iOS version that worked fine with the following navigator in the render method in TabView (I don't know how to go from the NavigatorIOS to Navigator) :
<Navigator
style={{backgroundColor: '#FFFFFF', paddingTop: 20}}
initialRouteStack={this.props.tabs}
initialRoute={this.props.tabs[this.props.initialSelected || 0]}
ref="tabs"
key="navigator"
renderScene={(tab, navigator) => {
var index = navigator.getCurrentRoutes().indexOf(tab);
return (
<NavigatorIOS
style={styles.tabbarContent}
key={index}
itemWrapperStyle={styles.tabbarContentWrapper}
initialRoute={tab.component.route()}
initialSkipCache={this.props.initialSkipCache} />
);
}}
configureScene={() => {
return {
...Navigator.SceneConfigs.FadeAndroid,
defaultTransitionVelocity: 10000,
gestures: {}
};
}} />
Try adding a
flex:1
property to the navigator. If that doesn't work, check to see that the tabbarContainer also has a
flex:1
property.
OK, I found the answer : I changed my renderScene method to the following :
renderScene = (route, navigator) => {
var temp = navigator.getCurrentRoutes();
return React.createElement(temp[this.state.selected].component, _.extend({navigator: navigator}, route.passProps));
}
Works fine now.

Categories

Resources