How to toggle to show and hide in elements in Reactjs? - reactjs

how to expand the elements present inside li . I used toggle on selected
I tried to add a default propertied selected false then onClick of the li I ensures if the values matches then selected should be true and displays that particular section ,and onClick of same it should collapse
Is it possible to open always the first dropdown remaining should be closed and when clicked other it open another li dropdown.
Currently onClick is not working
Here is my code
Demo
export default function App() {
const filterAddition = X.map((item) => ({
...item,
menus: item.menus.map((items) => ({
...items,
selected: false
}))
}));
const handleOnClick = (event) => {
filterAddition.map((item) => {
item.menus.map((items) => {
if (items.El=== event.target.value) {
return {
...items,
selected: !item.selected
};
}
});
});
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<nav className="animated bounceInDown">
{filterAddition.map(({ menus }, idx) => {
return menus.map(({ El, subSection, selected }, idx) => (
<ul key={idx}>
<li className="sub-menu" value={El} onClick={handleOnClick}>
<a href="#settings">
{El}
<div class="fa fa-caret-down right"></div>
</a>
{selected && (
<ul>
{subSection.map(({ E }, i) => (
<li key={E}>
{E}
</li>
))}
</ul>
)}
</li>
</ul>
));
})}
</nav>
</div>
);
}

So much to change.
First, you have to use useState to make component reactive when data is change.
Second, onClick have to change.
Third, try to read documentation especially about react hooks, state and lifecycle.
see this https://codesandbox.io/s/pensive-cloud-9mco1?file=/src/App.js
import React, { useState, useEffect } from "react";
import "./styles.css";
const X = [
{
detail1: "FirstJob",
menus: [
{
Order: 1,
El: " Record Management",
subSection: [
{
E: "Check Notification",
Order: "CheckNotification"
},
{
E: "Check Record",
Order: "CheckRecord"
}
]
},
{
Order: 2,
El: "Management",
subSection: [
{
E: "Notification",
Order: "Notification"
},
{
E: "Check",
Order: "Check"
}
]
}
]
}
];
export default function App() {
const [state, setState] = useState(X);
useEffect(() => {
const filterAddition = X.map((item) => ({
...item,
menus: item.menus.map((items) => ({
...items,
selected: false
}))
}));
setState(filterAddition);
}, []);
const handleOnClick = (label) => {
const temp = JSON.parse(JSON.stringify(state));
for(let i in temp) {
const menus = temp[i].menus;
for(let j in menus) {
const item = temp[i].menus[j];
if(item.El === label) {
item.selected = !item.selected;
}
}
}
setState(temp);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<nav className="animated bounceInDown">
{state.map(({ menus }, idx) => {
return menus.map(({ El, subSection, selected }, idx) => (
<ul key={idx}>
<li className="sub-menu" value={El} onClick={() => handleOnClick(El)}>
<a href="#settings">
{El}
<div className="fa fa-caret-down right"></div>
</a>
{selected && (
<ul>
{subSection.map(({ E }, i) => (
<li key={E}>
{E}
</li>
))}
</ul>
)}
</li>
</ul>
));
})}
</nav>
</div>
);
}

Related

React vertical Nested List open and close sub List

i have a react.js component in which i am displaying states and under i am displaying districts. To display this list it's working fine. The problem i want when i press any sates only that particular state sublist should display not all states sublist.
import React,{useState} from "react"
Const [open,,setOpen]=useState(false);
//Wrapper component
</div>
{states.map((city, index) => {
return <StateList state={state} key={index} />;
})}
</div>
//state component
<div onClick={()=>setOpen(!open)}>
<span >{state.name}</span>
<svg
viewBox="0 0 24 24"
className={`
${open ? "rotate-180" : ""}
`}
>
</svg>
</h2>
{open && <AreaList area={city} />}
</div>
//district component
const AreaList = ({ state }) => {
return state.districts.map((district) => (
<li>
<span className="ml-2 text-outer-space">
{district.name}
</span>
</li>
));
};
Here is working solution (without styles):
Codesandbox
import { useState } from "react";
const data = [
{
name: "Fujairah",
districts: [
{ name: "Al Buthna" },
{ name: "Al Bedia" },
{ name: "Town Center" },
{ name: "Wadi Al Sedr" }
]
},
{
name: "Abu Dhabi",
districts: [{ name: "Al Aman" }, { name: "Al Bahya" }]
}
];
const App = () => {
return (
<div>
{data.map((city) => {
return <City key={city.name} city={city} />;
})}
</div>
);
};
const City = ({ city }) => {
const [open, setOpen] = useState(false);
return (
<div onClick={() => setOpen(!open)}>
<h2>
<span>{city.name}</span>
</h2>
{open && <DistrictList city={city} />}
</div>
);
};
const DistrictList = ({ city }) => {
return city.districts.map((district) => (
<li key={district.name}>
<span>{district.name}</span>
</li>
));
};
export default App;

repeater field repeating incorrectly

I want to click a button to add a tab and then have content inside the tab that can also be repeated. Currently the part where I click on the button to add a new tab is working however entering content in tab one duplicates it in tab 2 once I create another tab ie: the inside content of tab 1 and 2 seems linked when it shouldn't be. I should be able to create multiple tabs and then enter unique data inside each tab.
If I have explained this poorly please let me know and I will elaborate further. I think it perhaps needs to be an array of objects within an array of objects.
registerBlockType("blocks/tabs", {
title: __("Tabbed Blocks", "custom-blocks"),
description: __("Tabbed content blocks", "custom-blocks"),
icon: "smiley",
category: "custom-category",
keywords: [__("tabs", "custom-blocks"), __("repeat", "custom-blocks")],
attributes: {
tabs: {
type: "array",
default: [""],
},
tiles: {
type: "array",
default: [""],
},
},
edit: (props) => {
const {
attributes: { tabs, tiles },
setAttributes,
} = props;
const [showTab, setShowTab] = useState("");
const handleTabClick = (index) => {
console.log(index);
setShowTab(index);
};
return (
<>
<div className="tabs">
<ul id="tabs-nav">
{tabs.map((t, index) => (
<li>
<span onClick={() => handleTabClick(index)} id={`#tab${index}`}>
<RichText
placeholder="Tab title"
onChange={(tabTitle) => {
const newObject = Object.assign({}, t, {
tabTitle: tabTitle,
});
setAttributes({
tabs: [
...tabs.filter((item) => item.index != t.index),
newObject,
],
});
}}
value={t.tabTitle}
/>
</span>
</li>
))}
</ul>
{tabs.map((t, index) => (
<div
id={`tab${index}`}
className={
showTab == index ? `tab-content show` : `tab-content hide`
}
>
<div className="home-tabs">
{tiles.map((tile, index) => (
<div
className="tab-block"
>
<div className="copy">
<RichText
tagName="h3"
placeholder="Tab Content Title"
onChange={(tabTileTitle) => {
const newObject = Object.assign({}, tile, {
tabTileTitle: tabTileTitle,
});
setAttributes({
tiles: [
...tiles.filter(
(item) => item.index != tile.index
),
newObject,
],
});
}}
value={tile.tabTileTitle}
/>
<p>
Some content...
</p>
</div>
</div>
))}
</div>
</div>
))}
<Button
onClick={() => {
setAttributes({
tiles: [...tiles, { index: tiles.length }],
});
}}
>
Add Tile
</Button>
</div>
<Button
onClick={() => {
setAttributes({
tabs: [...tabs, { index: tabs.length }],
});
console.log(tabs);
}}
>
Add Tab
</Button>
</>
);
},
save: (props) => {
return null;
},
});

Optional array.map inside array.map

I have a menu, rendered due to an array.map BUT I want to have a submenu rendered too but I can not find the way of achieving this
for instance I have this correctly done
import React from 'react'
export const Menu = () => {
const item = [
'option1',
'option2',
'option3',
'option4',
'opction5',
]
const subMenuItem = [
'2.option1',
'2.option2',
'2.option3',
'2.option4',
'2.option5',
'2.option6',
]
return (
<>
<div>
Hola
</div>
<ul>
{
item.map((x, index) => (
<li key={index}>
{x}
</li>
))
}
</ul>
</>
)
}
but now I want to have a submenu on option2 mapping the subMenuItem array if there is a condition in which some variable is equal to an option from item
something inside the above mapping like
if (x === 'option2') {
<ul>
subMenuItem.map((y,i)=>(
<li key={i*1000}
{y}
</li>
))
</ul>
something like that
thanks in advance
Assuming you have subMenu field inside the menu item such as :
const menu = [
{"name": 'option1'},
{"name": 'option2', "subMenu": [...]},
{"name": 'option3'},
]
You can render conditionally:
return (
<>
<div>
Hola
</div>
<ul>
{
item.map((x, index) => (
<li key={index}>
<>
{x.name}
{x.subMenu && x.subMenu.map((subMenuItem, subIndex) => (
<li key ={index + subIndex}>
{subMenuItem.name}
</li>
)}
</>
</li>
))
}
</ul>
</>
)
import React from 'react'
export const Menu = () => {
const item = [
'option1',
'option2',
'option3',
'option4',
'opction5',
]
const subMenuItem = [
'2.option1',
'2.option2',
'2.option3',
'2.option4',
'2.option5',
'2.option6',
]
return (
<>
<div>
Hola
</div>
<ul>
{
item.map((x, index) => {
return ( <li key={index}>
{x}
</li>
{if (x === 'option2') {
<ul>
{
subMenuItem.map((y,i)=>(
<li key={i*1000}>
{y}
</li>
))
}
</ul>})
})
}
</ul>
</>
)
}
Try this
this is what I did
import React from 'react'
import styled from 'styled-components'
export const Menu = () => {
const LI = styled.li`
list-style: '- ';
`
const item = [
'option1',
'option2',
'option3',
'option4',
'option5',
]
const subMenuItem = [
'2.option1',
'2.option2',
'2.option3',
'2.option4',
'2.option5',
'2.option6',
]
return (
<>
<ul>
{
item.map((x, index) => (
<LI key={index}>
{x}
<ul>
{(x === 'option2') &&
subMenuItem.map((y, i) => (
<li key={i * 1000}>
{y}
</li>
))
}
</ul>
</LI>
))
}
</ul>
</>
)
}
just put the condition inside of it as it is...first time doing this , seems tricky though :P

Error in Component Output by Button Click

I have 2 buttons and information about div. When I click on one of the buttons, one component should appear in the div info. Where is the error in the withdrawal of the component div info?
import React, { Component } from 'react';
import Donald from '/.Donald';
import John from '/.John';
class Names extends Component {
state = {
array:[
{id:1,component:<Donald/>, name:"Me name Donald"},
{id:2,component:<John/>, name:"My name John"},
],
currComponentId: null
changeComponentName = (idComponent) => {
this.setState({currComponentId:idComponent});
};
render() {
return(
<table>
<tbody>
<tr className="content">
{
this.state.array.map(item=> item.id===this.element.id).component
}
</tr>
<button className="Button">
{
this.state.array.map( (element) => {
return (
<td key={element.id}
className={this.state.currComponentId === element.id ? 'one' : 'two'}
onClick={ () => this.changeComponentName(element.id)}
>{element.name}
</td>
)
})
}
</button>
</tbody>
</table>
)
}
}
export default Names;
You have several problems here, the first being that you are missing the closing curly bracket on your state. this.element.id is also undefined, I assume you are meaning this.state.currComponentId.
Your html is also fairly badly messed up, for example you are inserting multiple <td>s into the content of your button. I also don't see where this.changeComponentName() is defined, so I am assuming you mean this.showComponent()
The primary issue is probably in this.state.array.map(item=> item.id === this.element.id).component, as map() returns an array. An array.find() would be more appropriate, though you still need to check to see if there is a match.
I might re-write your component like this (I have swapped out the confusing html for basic divs, as I'm not sure what you are going for here)
class Names extends Component {
state = {
array: [
{ id: 1, component: <span>Donald</span>, name: "Me name Donald" },
{ id: 2, component: <span>John</span>, name: "My name John" },
],
currComponentId: null,
};
showComponent = (idComponent) => {
this.setState({ currComponentId: idComponent });
};
render() {
//Finding the selected element
const selectedElement = this.state.array.find(
(item) => item.id === this.state.currComponentId
);
return (
<div>
<div className="content">
{
//Check to see if there is a selected element before trying to get it's component
selectedElement ? selectedElement.component : "no selected."
}
</div>
{this.state.array.map((element) => {
return (
<button
className="Button"
key={element.id}
className={
this.state.currComponentId === element.id ? "one" : "two"
}
onClick={() => this.showComponent(element.id)}
>
{element.name}
</button>
);
})}
</div>
);
}
}
Errors:- (1) You are showing list inside tag, instead show as <ul><li><button/></li></ul>(2)You are not displaying content after comparison in map()This is a working solution of your question.
class Names extends React.Component {
state = {
array: [
{ id: 1, component: <Donald />, name: "Me name Donald" },
{ id: 2, component: <John />, name: "My name John" }
],
currComponentId: null
};
clickHandler = idComponent => {
this.setState({ currComponentId: idComponent });
};
render() {
return (
<div>
<ul>
{this.state.array.map(element => {
return (
<li key={element.id}>
<button
className="Button"
onClick={() => this.clickHandler(element.id)}
>
{element.name}
</button>
</li>
);
})}
</ul>
{this.state.array.map(data => {
if (this.state.currComponentId === data.id)
return <div>{data.component}</div>;
})}
</div>
);
}
}
const Donald = () => <div>This is Donald Component</div>;
const John = () => <div>This is John Component</div>;
ReactDOM.render(<Names />, document.getElementById('root'));
<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='root' />

Update list of displayed components on deletion in React

in the beginning on my path with React I'm creating simple to-do app where user can add/remove task which are basically separate components.
I create tasks using:
addTask(taskObj){
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
I render list of components (tasks) using following method:
showTasks(){
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
taskObj={item}
removeTask = {(id) => this.removeTask(id)}
key = {index}/>;
})
);
}
method to remove specific task takes unique ID of task as an argument and based on this ID I remove it from the tasks list:
removeTask(uID){
this.setState(prevState => ({
tasksList: prevState.tasksList.filter(el => el.id != uID )
}));
}
But the problem is, when I delete any item but the last one, it seems like the actual list of components is the same only different objects are passed to those components.
For example:
Lets imagine I have 2 created componentes, if I set state.Name = 'Foo' on the first one, and state.Name='Bar' on the second one. If I click on remove button on the first one, the object associated to this component is removed, the second one becomes first but it's state.Name is now 'Foo' instead of 'Bar'.
I think I'm missing something there with correct creation/removing/displaying components in react.
Edit:
Method used to remove clicked component:
removeCurrentTask(){
this.props.removeTask(this.props.taskObj.id);
}
SingleTask component:
class SingleTask extends Component{
constructor(props) {
super(props);
this.state={
showMenu : false,
afterInit : false,
id: Math.random()*100
}
this.toggleMenu = this.toggleMenu.bind(this);
}
toggleMenu(){
this.setState({showMenu : !this.state.showMenu, afterInit : true});
}
render(){
return(
<MDBRow>
<MDBCard className="singleTaskContainer">
<MDBCardTitle>
<div class="priorityBadge">
</div>
</MDBCardTitle>
<MDBCardBody className="singleTaskBody">
<div className="singleTaskMenuContainer">
<a href="#" onClick={this.toggleMenu}>
<i className="align-middle material-icons">menu</i>
</a>
<div className={classNames('singleTaskMenuButtonsContainer animated',
{'show fadeInRight' : this.state.showMenu},
{'hideElement' : !this.state.showMenu},
{'fadeOutLeft' : !this.state.showMenu && this.state.afterInit})}>
<a
title="Remove task"
onClick={this.props.removeTask.bind(null, this.props.taskObj.id)}
className={
classNames(
'float-right btn-floating btn-smallx waves-effect waves-light listMenuBtn lightRed'
)
}
>
<i className="align-middle material-icons">remove</i>
</a>
<a title="Edit title"
className={classNames('show float-right btn-floating btn-smallx waves-effect waves-light listMenuBtn lightBlue')}
>
<i className="align-middle material-icons">edit</i>
</a>
</div>
</div>
{this.props.taskObj.description}
<br/>
{this.state.id}
</MDBCardBody>
</MDBCard>
</MDBRow>
);
}
}
Below visual representation of error, image on the left is pre-deletion and on the right is post-deletion. While card with "22" was deleted the component itself wasn't deleted, only another object was passed to it.
Just to clarify, the solution was simpler than expected.
In
const showTasks = () => taskList.map((item, index) => (
<SingleTask
taskObj={item}
removeTask ={removeTask}
key = {item.id}
/>
)
)
I was passing map index as a key, when I changed it to {item.id} everything works as expected.
In short, in the statement tasksList.push(<SingleTask taskObj={taskObj} removeTask ={this.removeTask}/>);, removeTask = {this.removeTask} should become removeTask = {() => this.removeTask(taskObj.id)}.
However, I would reconsider the way the methods addTask and showTasks are written. While the way you have written isn't wrong, it is semantically unsound. Here's what I would do:
addTask(taskObj){
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
showTasks(){
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
taskObj={item}
removeTask ={() => this.removeTask(item.id)}/>;
})
);
}
const SingleTask = (task) => {
const { taskObj } = task;
return <div onClick={task.removeTask}>
{ taskObj.title }
</div>
}
// Example class component
class App extends React.Component {
state = {
tasksList: [
{ id: 1, title: "One" },
{ id: 2, title: "Two" },
{ id: 3, title: "Three" },
{ id: 4, title: "Four" }
]
}
addTask = (taskObj) => {
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
showTasks = () => {
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
key={index}
taskObj={item}
removeTask ={() => this.removeTask(item.id)}/>;
})
);
}
removeTask(id) {
this.setState(prevState => ({
tasksList: prevState.tasksList.filter(el => el.id != id )
}));
}
render() {
return (
<div className="App">
<div> {this.showTasks()} </div>
</div>
);
}
}
// Render it
ReactDOM.render(
<App />,
document.body
);
<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>

Resources