I'm doing a menu with a submenu with React. Clicking on the main menu (Example: CRM) creates the submenu (Example Hijo: Argumentarios) but when clicking on the following menu item (Example Padre: Permisos) "Argumentarios" is not deleted and the submenu of "Permisos" is also created.
I need that when pressing in the menu it shows its submenu and eliminates those of the other siblings.
This is a part of my json file
[
{
"Id":"114",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"115",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"116",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"44",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"45",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"47",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"50",
"Padre":"Telefonia",
"Hijo":"Audio"
},
{
"Id":"51",
"Padre":"Telefonia",
"Hijo":"Audio"
}
]
For confidential reasons I can not show the actual file.
In my next code I change the fetch for a fake json url
import React, { Component } from 'react';
import Icon from './Icon';
class Nav extends Component{
constructor(props){
super(props)
this.state = {
menuSubmenu:[],
}
}
componentWillMount(){
fetch('fake/json_menu.php')
.then(response => response.json())
.then(menuSubmenu =>{
menuSubmenu.forEach(datos => {
let data = {
menu:datos.Padre,
submenu:datos.Hijo,
id:datos.Id,
descripcion:datos.Description,
url:datos.URL
}
this.setState({
menuSubmenu:this.state.menuSubmenu.concat([data])
})
})
})
}
render() {
if (this.state.menuSubmenu.length > 0) {
return(
<nav className="nav">
<div className="menu">
<ul className="list">
{this.state.menuSubmenu.map(datos => <Menu key={datos.id} menu={datos.menu} submenu={datos.submenu} descripcion={datos.descripcion} url={datos.url}/>)}
</ul>
<div className="content-bnt">
<button id="desplegar" className='btn btn--rounded'>
<Icon icon="flecha" className='ico-flecha'/>
</button>
</div>
</div>
</nav>
);
}
return (<p>Cargando usuarios...</p>);
}
}
class Menu extends Component{
constructor(props){
super(props)
this.state = {
abrirSubmenu: false,
}
this.submenuClick = this.submenuClick.bind(this);
}
submenuClick() {
this.setState(state => ({
abrirSubmenu: !state.abrirSubmenu
}));
//alert('Click!')
}
render(){
return (
<>
<li className="list__item">
<button title={this.props.menu} id={"mn-" + this.props.menu} className="desplegable" onClick={this.submenuClick.bind(this)}><Icon icon="auriculares" className='ico-auriculares'/>{this.props.menu}</button>
{
this.state.abrirSubmenu
? (
<div id="sb-crm" className="submenu">
<h3 className="nav--title"><Icon icon="descargar" className='ico-descargar'/>{this.props.submenu}</h3>
<ul className="list">
<li className="list__item">
<a href={this.props.url} title={this.props.descripcion}>{this.props.descripcion}</a>
</li>
</ul>
</div>
)
: (
null
)
}
</li>
</>
)
}
}
export default Nav;
I hope to show only one of the menu items at the same time.
¡Thanks a lot!
Related
I have a react class component which produces a product showcase using react-masonry. I now want to add filtering functionality with filter options displayed as buttons and on each click, the page elements should be filtered and the masonry display rerendered.
The filter functions work fine but I was not able to get the masonry to rerender.
import React from 'react'
import PropTypes from 'prop-types'
import Masonry from 'react-masonry-component'
import Product from 'components/Product'
const masonryOptions = {
transitionDuration: 0
};
class Gallery extends React.Component {
constructor (props){
super(props);
this.state ={
filter:props.filter,
elements:props.elements,
type:props.type,
}
this.handleFilterClick=this.handleFilterClick.bind(this)
}
handleFilterClick(filter){
console.log(filter)
this.setState({filter:filter})
console.log(this.state.filter)
}
filterProducts (elements,filter){
const filteredArray=elements.filter(function(el){
for (let i in el.tags) {
if (el.tags[i].slug===filter){
return true;
}
}
return false
})
return filteredArray;
}
renderGallery(){
const type=this.state.type
const elements=this.state.elements
var filter=this.state.filter
const filteredElements = elements
if (filter !=="*"){
const filteredElements = this.filterProducts(elements,filter)
}
const childElements = filteredElements.map(function(element,key){
if (element.mainPhoto!=null && element.isDogFood==type){
return (
<Product key={key} element={element}/>
);} else{
return null;
}
});
return (
<Masonry
className={'my-gallery-class'} // default ''
options={masonryOptions} // default {}
disableImagesLoaded={false} // default false
updateOnEachImageLoad={false} // default false and works only if disableImagesLoaded is false
>
<div className="col-md-12">
<ul className="filter text-center text-inline">
<li>
<button data-filter="*" className="selected">Tüm Ürünler</button>
</li>
<li>
<button filter="nograin">Tahılsız</button>
</li>
<li>
<button filter="seafood">Deniz Mahsülleri</button>
</li>
<li>
<button filter="poultry">Beyaz Et</button>
</li>
<li>
<button filter="redmeat">Kırmızı Et</button>
</li>
<li>
<button filter="pate" onClick={() =>{this.handleFilterClick("pate")}}>Püre</button>
</li>
</ul>
</div>
{childElements}
</Masonry>
);
}
render() {
return (
this.renderGallery()
)
}
}
Gallery.propTypes={
type: PropTypes.bool
}
export default Gallery
Use getDerivedStateFromProps instead of using props in constructor
import React from 'react'
import PropTypes from 'prop-types'
import Masonry from 'react-masonry-component'
import Product from 'components/Product'
const masonryOptions = {
transitionDuration: 0
};
class Gallery extends React.Component {
constructor (props){
super(props);
this.state = {
// Don't assign direct value in constructor as constructor function
// calls only at initialization
filter:props.filter,
elements:props.elements,
type:props.type,
}
this.handleFilterClick=this.handleFilterClick.bind(this)
}
getDerivedStateFromProps (props, prevState) {
if(//Put a valid condition) {
return {
filter: props.filter,
elements: props.elements,
type: props.type
}
}
return {}
}
handleFilterClick(filter){
console.log(filter)
this.setState({filter:filter})
console.log(this.state.filter)
}
filterProducts (elements,filter){
const filteredArray=elements.filter(function(el){
for (let i in el.tags) {
if (el.tags[i].slug===filter){
return true;
}
}
return false
})
return filteredArray;
}
renderGallery(){
const type=this.state.type
const elements=this.state.elements
var filter=this.state.filter
const filteredElements = elements
if (filter !=="*"){
const filteredElements = this.filterProducts(elements,filter)
}
const childElements = filteredElements.map(function(element,key){
if (element.mainPhoto!=null && element.isDogFood==type){
return (
<Product key={key} element={element}/>
);} else {
return null;
}
});
return (
<Masonry
className={'my-gallery-class'} // default ''
options={masonryOptions} // default {}
disableImagesLoaded={false} // default false
updateOnEachImageLoad={false} // default false and works only if disableImagesLoaded is false
>
<div className="col-md-12">
<ul className="filter text-center text-inline">
<li>
<button data-filter="*" className="selected">Tüm Ürünler</button>
</li>
<li>
<button filter="nograin">Tahılsız</button>
</li>
<li>
<button filter="seafood">Deniz Mahsülleri</button>
</li>
<li>
<button filter="poultry">Beyaz Et</button>
</li>
<li>
<button filter="redmeat">Kırmızı Et</button>
</li>
<li>
<button filter="pate" onClick={() =>{this.handleFilterClick("pate")}}>Püre</button>
</li>
</ul>
</div>
{childElements}
</Masonry>
);
}
render() {
return (
this.renderGallery()
)
}
}
Gallery.propTypes = {
type: PropTypes.bool
}
export default Gallery
I am new to react. I am fetching github user info on search. I unable
to fetch data in my child component. this is my code below.
whats the problem , cant i use this.state.userList.map
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
errorMessage: '',
userList: [],
isOpen: false,
userName:''
};
this.toggle = this.toggle.bind(this);
this.getUsers = this.getUsers.bind(this);
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
// componentWillMount(){
// this.getUsers();
// }
getUsers(e) {
console.log('get users called='+e.target.value);
fetch('https://api.github.com/search/users?q='+ e.target.value)
.then(res => res.json())
.then(
userList =>{
this.setState({userList: userList})
console.log(userList);
}
);
}
render() {
return (
<div>
<nav className="navbar navbar-expand-lg navbar-light bg-primary navbar-inner">
<div className="collapse navbar-collapse navbar-inner navb" >
<ul className="navbar-nav bg-light mr-auto">
<li className="nav-item dropdown">
<a className="nav-link dropdown-toggle auto" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Sort
</a>
<div className="dropdown-menu" aria-labelledby="navbarDropdown">
<a className="dropdown-item" href="#">Sort by Name (ascending)</a>
<a className="dropdown-item" href="#">Sort by Name (descending)</a>
<div className="dropdown-divider"></div>
<a className="dropdown-item" href="#">Sort by Rank (ascending)</a>
<a className="dropdown-item" href="#">Sort by Rank (descending)</a>
</div>
</li>
</ul>
<form className="form-inline my-2 my-lg-0 auto" onSubmit={this.getUsers}>
<div className="form-group">
<input className="form-control mr-sm-2" type="Search" placeholder="Search"
aria-label="Search"
id="userName"
onKeyUp={this.getUsers} >
</input>
</div>
</form>
</div>
</nav>
<div >
<UserList userList={this.state.userList}/>
</div>
</div>
);
}
}
export default SearchHeader;
This is my child component below where I am fetching data from parent
component
This is my child component below where I am fetching data from parent
component
class UserList extends Component {
constructor(props) {
super(props)
this.state ={
users:this.props.userList
}
}
render() {
return (
<div className="container-fluid">
<br />
{
this.state.users.map((user)=>
<div className="jumbotron container">
{user.login}
</div>
)
}
</div>
);
}
}
export default UserList;
You have several problems in your components:
do not copy parent's state into chilren states: users:this.props.userList. Use this.props directly instead and React will know it must re-render children
do not rely on current state to set new state. Use function with prevState instead of isOpen: !this.state.isOpen.
make a copy of event's value before passing it to setState like this const {value} = e.target;
assign unique key to each user in your list (not indexes!), or it won't re-render correctly on list update
So your code would look like this:
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
errorMessage: '',
userList: [],
isOpen: false,
userName:''
};
}
toggle = () => {
this.setState( (prevState) => ({
isOpen: !prevState.isOpen
}));
}
getUsers = (e) => {
const {value} = e.target;
console.log('get users called='+value);
fetch('https://api.github.com/search/users?q='+ value)
...
}
}
and:
class UserList extends Component {
// Use default constructor
render() {
const users = this.props.userList.map( (user) => (
<div className="jumbotron container" key={user.login}>
{user.login}
</div>
));
return (
<div className="container-fluid">
<br />
{users}
</div>
);
}
}
parent component change should be.
getUsers(e) {
console.log('get users called='+e.target.value);
fetch('https://api.github.com/search/users?q='+ e.target.value)
.then(res => res.json())
.then(
userList =>{
this.setState({userList: userList.items})
console.log(userList);
}
);
}
Change your user list and check initially values are there or not and you dont need to user state in userList component.
that is all because initially there are no values also there can be an case when you are setting state for userList value after fetching data that might be coming as null undefined or something else so put an console log there and check that too.
class UserList extends Component {
render() {
return (
{
this.props.userList && this.props.userList.length && this.props.userList.map((user)=>
{user.login}
)
}
</div>
);
}
}
export default UserList;
Good Morning! Why does my map content stay outside the "blog--div" div?
It's getting loose on Body and I do not know why. Help-me, please!
I try to put a border around the contents of the "blog--div" but the content becomes loose, making it impossible to apply styles.
imports[...]
class Blog extends Component {
constructor(props) {
super(props)
this.state = {
post: [],
}
}
componentDidMount() {
this.setState({ isLoading: true })
fetch(`${API}`)
.then(res => res.json())
.then(res => {
this.setState({
post: [res],
isLoading: false,
})
})
}
render() {
const { isLoading } = this.state
if (isLoading) {
return <Loading />
}
return (
<div className="blog">
<p className="blog__title">Blog</p>
{this.renderBlog()}
</div>
)
}
renderBlog() {
const page = this.state.post.map((post, key) => {
return (
<div className="blog--div" key={key}>
<div className="blog__post">
<div className="blog__post--title">
<p><a target="_blank" rel="noopener noreferrer" href={post[0].link}>{post[0].title.rendered.replace('Visit.Rio', 'Projeto 1')}</a></p>
</div>
<div className="blog__post--description">
<p>{post[0].excerpt.rendered.replace('Visit.Rio', 'Projeto 1')}</p>
</div>
</div>
</div>
)
})
return page
}
}
export default Blog
I'm making a collapsible list with React. So far it works but now I want to implement a button that expands/collapses everything. Therefore the button need to adjust the state of all elements. I'm not sure what's the best way to tackle this problem though. This is what I have:
import React, {Component} from 'react';
class CollapsibleList extends Component {
constructor(props) {
super(props);
this.state = {
collapsed: true
};
this.subLists = [];
this.papers = [];
if (this.props.subtitles) {
for (let subList of this.props.subtitles) {
this.subLists.push(
<CollapsibleList level={this.props.level + 1} subtitles={subList.subtitles} title={subList.title}/>
);
}
}
this.toggleCollapse = this.toggleCollapse.bind(this);
this.expandAll = this.expandAll.bind(this);
this.collapseAll = this.collapseAll.bind(this);
}
expandAll() {
this.setState({collapsed: false});
this.subLists.forEach(subList => subList.expandAll());
}
collapseAll() {
this.setState({collapsed: true});
this.subLists.forEach(subList => subList.collapseAll());
}
toggleCollapse() {
this.setState(prevState => {
return {collapsed: !prevState.collapsed};
});
}
render() {
return (this.state.collapsed ?
<li className={'collapsibleListItem'}>
<div onClick={this.toggleCollapse}>
{this.props.title}
</div>
<img title={'Expand all'} className={'icon'} alt={'Expand all'} src={require('../expand_all.png')} onClick={this.expandAll}/>
<img title={'Collapse all'} className={'icon'} alt={'Collapse all'} src={require('../collapse_all.png')} onClick={this.collapseAll}/>
</li> :
<li className={'collapsibleListItem'}>
<div onClick={this.toggleCollapse}>
{this.props.title}
</div>
<img title={'Expand all'} className={'icon'} alt={'Expand all'} src={require('../expand_all.png')} onClick={this.expandAll}/>
<img title={'Collapse all'} className={'icon'} alt={'Collapse all'} src={require('../collapse_all.png')} onClick={this.collapseAll}/>
<ul className={'collapsibleList'}>
{this.subLists}
</ul>
</li>
);
}
}
export default CollapsibleList;
Unfortunately, that doesn't seem to work though.
I can't understand what you are trying to do in your code but you should have 2 different components; one for the list and one for the list item. It should be something like this:
// Parent component
import React from 'react';
import ListItem from './ListItem';
class List extends React.Component {
constructor() {
super();
this.state = {
collapsed: false
}
}
render() {
const data = ['abc', 'def', 'ghi']; // whatever you want to have
return(
<div>
<button onClick={() => this.setState({collapsed: !this.state.collapsed})}>
Collapse
</button>
<ul>
{
this.state.collapsed &&
data.map((val, key) => {
return(
<li>
<ListItem value={val} key={key} />
</li>
)
})
}
</ul>
</div>
)
}
}
And this is the child component
// child component
import React from 'react';
class ListItem extends React.Component {
constructor() {
super();
}
render() {
return(
<div>
{/*// render anything you want*/}
<p>{this.props.value}</p>
</div>
)
}
}
export default ListItem;
This code is just to give you an insight.
I'm bringing a API s' content based on a dynamic list and I'm trying to apply a mouserEnter on each li. The event results by toggling content in the each list item. The event is working but it is toggling content in all the list items all at once, but I want it to toggle only the content that matches with the list item that is receiving the mouseEnter.
import _ from 'lodash';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
export default class Dribbble extends React.Component {
constructor(props) {
super(props);
this.state = {
work: [],
hover: false
};
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
}
handleMouseEnter(){
this.setState({ hover: true })
}
handleMouseLeave(){
this.setState({ hover: false })
}
componentDidMount() {
this.ShotList();
}
ShotList() {
return $.getJSON('https://api.dribbble.com/v1/shots?per_page=3&access_token=41ff524ebca5e8d0bf5d6f9f2c611c1b0d224a1975ce37579326872c1e7900b4&callback=?')
.then((resp) => {
this.setState({ work: resp.data.reverse() });
});
}
render() {
const works = this.state.work.map((val, i) => {
return <li key={i} className="box"
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
{!this.state.hover ?
<div>
<img className="cover" src={val.images.normal} />
<div className="bar">
<h2>{val.title}</h2>
<span>{val.views_count}</span>
<i className="fa fa-eye fa-2x" aria-hidden="true"></i>
</div>
</div>
: null}
{this.state.hover ?
<div>
<h3>{val.user.name}</h3>
<img className="avatar img-circle" src={val.user.avatar_url}/>
<p>{val.description}</p>
</div>
:
null
}
</li>
});
return <ul>{works}</ul>
}
}
Here is my code:
There are couple of issues in your example, firstly as #aherriot states you should move the ul outside the map.
Next i would set this.state.hover to be the id of the item being hovered over on onMouseEnter.
The below snippet shows a basic example of this working that should be easy enough to adapt to your code.
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
items: [{id: 1, name: 'Fred'}, {id: 2, name: 'Albert'}, {id: 3, name: 'Jane'}],
hover: false,
}
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.renderItem = this.renderItem.bind(this);
}
handleMouseEnter(id){
console.log(`handleMouseEnter this.setState({ hover: ${id} })`);
this.setState({ hover: id })
}
handleMouseLeave(){
console.log('handleMouseLeave this.setState({ hover: false })');
this.setState({ hover: false })
}
renderItem(item, index) {
let content = [];
content.push(
<span>ID: {item.id}, Name: {item.name}</span>
);
if(this.state.hover === item.id) {
console.log('display " - hovering" for item id: ' + item.id);
content.push(
<span> - hovering</span>
);
}
return (
<li key={item.id}
onMouseEnter={() => this.handleMouseEnter(item.id)}
onMouseLeave={this.handleMouseLeave}
>
{content}
</li>
)
}
render() {
return <ul>
{this.state.items.map(this.renderItem)}
</ul>
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div id="root"></div>
Maybe you should move the <ul> tag outside of this.state.work.map You only want one <ul> to show up, not one for each element.
You can place it at the bottom inside your div tag instead: return (<div><ul>{works}</ul></div>)