I have main component which has 4 subcomponents and I wish to pass states props and functions between them. The thing is that in subcomponent List I wish to be able to get access only to interior of the list with the class ".title" Is it any way to jump between same classes in react as it was possible in jQuery? something like this next(), prev() and find()? I tried to find any way but I have failed so far.
class List extends React.Component {
constructor(props) {
super(props)
this.state = {
title: ""
}
}
render() {
return (
<div>
<ul className="List">
<li>
<ul className="song">
<li className="time">
3:16
</li>
<li className="title" onClick= {this.handleSong}> Title I want to pass
</li>
</ul>
</li>
<ul className="List">
<li>
<ul className="song">
<li className="time">
4:16
</li>
<li className="title" onClick= {this.handleSong}> next Title I want to pass
</li>
</ul>
</li>
</div>
and here is function where I can get access to one element but don't know how to switch to next one
handleSong = (event) => {
this.setState({
title: event.currentTarget.textContent,
})
event.preventDefault()
}
UPDATED:
I changed my code as you suggested and now it looks like this:
Inside main component:
class Widget extends React.Component {
constructor(props) {
super(props)
this.state = {
isClicked: true,
playerDisplayed: true,
title: "Still Don't Know",
songs: []
}
}
componentDidMount() {
const url = "http://localhost:3000/ListOfSongs"
fetch(url)
.then(resp => resp.json())
.then(data => {
this.setState({
songs: data
})
console.log(data);
});
}
return (
<div className="container">
<div className="wrapperList">
<HeaderList
handleReturn={this.handleReturn.bind(this)}/>
<ul className="songsList">
{this.state.songs.map(e =>
<SongsList id={e.id} title={e.title} time={e.time}
handleChooseSong={this.handleChooseSong.bind(this)}
playerDisplayed={this.state.playerDisplayed}/>
)}
</ul>
</div>
</div>
)
}
SongList component :
<li id={this.props.id}>
<ul className="song">
<li className="timeAndBand">
{this.props.time} || {this.props.band}
</li>
<li className="title" onClick={this.props.handleChooseSong}>
{this.props.title}
</li>
<li className="icons">
<svg aria-hidden="true" data-prefix="fas" data-icon="share-alt"className="shareBtn" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
</svg>
<svg aria-hidden="true" data-prefix="fas" data-icon="heart"
className="heartBtn" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
</svg>
</li>
</ul>
</li>
Each song should be a component:
<Song id={song.id} title={song.title} time={song.time}
handleClick={someHandlerInParent} />
in <Song /> of course html and
onClick={ this.props.handleClick( this.props.id ) }
This way you can pass to List component (parent) information about which song was selected, filter by id, get title, time, whatever. Render list using map, you can tell Song (by prop) that is currently selected to render with another style/class ... NOT BY getting/selecting DOM node and adding/removing classes - it's a lot simpler and more powerfull with react - render fn can have logic to choose style, show additional options (components).
Search for more tutorials, even TODO examples - react is not another templating engine - you have to learn thinking in react. It's hard to just start writting code w/o some knowledge.
UPDATE: Look at this example - component can contain much more (complex) data w/o need to 'artificially pumping it into html' (each template engines needs it). You can show in html only part of data and still use all 'behind scene'! No more searching for id (next/prev/n-th child...), get value, prop, state ... or manually roll-back changes (grrr#$%). You can manipulate objects w/o hand tools, not step by step (error prone) - with react you have automation 'CNC' for this.
Related
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false)
if(shownavcontents){
document.getElementsByClassName("navbardivofmobiledevice").style.display = "none";
}else{
document.getElementsByClassName("navbardivofmobiledevice").style.display = "block";
}
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
<div className="navbardivofmobiledevice">
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
</>
);
}
As you see I am trying to make responsive navbar, in this case, for mobile devices. I've faced one problem. I've made button on top of navbar and some navbar contents which I want to display only whenever user will click this button and vice versa. So I tried using hooks to check if the user clicked the button which works perfectly, only thing that doesn't works is this if else statements it seems like document.getElementsByClassName("navbardivofmobiledevice").style.display = "none"; doesn't have an effect here. So my question is what is the alternative of this? What can I do here?
This is imperative code:
document.getElementsByClassName("navbardivofmobiledevice").style.display = "none";
With React, you rarely get references to DOM elements and update them manually, and in any case, you do it using Refs, not with the getElement... or querySelector... methods). Instead, you write declarative code and let React take care of the DOM updates for you.
In this case, simply add or remove a hidden attribute or CSS class that has display: none from your JSX:
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false);
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
<div className="navbardivofmobiledevice" hidden={ !shownavcontents }>
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
</>
);
}
If you prefer to use a class, assuming you have defined a CSS class .isHidden { display: none; } you would use this line instead:
<div className={ `navbardivofmobiledevice${ shownavcontents ? '' : ' isHidden' }` }>
Regarding what some comments are mentioning about rendering that conditionally like so:
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false);
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
{ shownavcontents && (
<div className="navbardivofmobiledevice">
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
) }
</>
);
}
I would avoid that, as hiding your main navigation from Google and other search engines will harm your SEO. You need to hide it visually but still have it in the DOM.
If you want to do better than that, add all the appropriate ARIA attributes and logic for a navigation menu with nested submenus, as explained here:
https://www.w3.org/WAI/ARIA/apg/example-index/menubar/menubar-navigation
I am trying to pass the data from Child > parent > child
Child
{this.state.data.map((item, index) => (
<li className='card' key={index}>
<span>{item.continent} </span>
<ul className="accordion-body">
{item.regions.map((c, i) => (
<li key={i} onClick={this.props.toggleContent}>
<img src={c.flag}/> {c.country}
</li>
))}
</ul>
</li>
))}
Basically I need to get selected country and some other values from the child and pass to parent
and pass those values to another child.
My Parent
<div className="modal-header">
<h2>Choose your {title}</h2>
<a href="#" className="model-close" data-dismiss="modal" aria-label="Close"><i
className="fa fa-times-circle"></i></a>
</div>
<div className="modal-body">
{showCountry && <CountryList toggleContent={this.toggleContent}/>}
{showLanguages && <RegionList country={country} flag={flag} languages={languages}
toggleContent={this.toggleContentRegion.bind(this)}/>}
</div>
and
toggleContent = () => {
this.setState({
...this.state,
showCountry: !this.state.showCountry,
showLanguages: !this.state.showLanguages,
title: 'language',
country: 'country',
languages: [],
flag: 'flag'
});
}
I tried to use below
<li key={i} onClick={this.props.toggleContent(c.country)}>
<img src={c.flag}/> {c.country}
</li>
and access it from parent
toggleContent = (country) => {
this.setState({
...this.state,
showCountry: !this.state.showCountry,
showLanguages: !this.state.showLanguages,
title: 'language',
country: country,
languages: [],
flag: 'flag'
});
}
But, my components not working correctly When do that and always shows the 2 child component.
Are there any proper way to pass the data to parent from a json array?
So the best way I would handle this would be to make the import your parent class components into the child , place it at the very top of the child JSX but hide it by default. The modal would be fixed, background covering the full page and at a z-index higher than the rest of the child components, so that way only the modal contents are the only accessible things . You would have a state that "toggles on" the modal for each click of the item list and a close button that toggles it off. You would update the modal content and toggle it on for every click
In terms of the second child, you can just show it on the same modal
Found a way to do this :)
render() {
var toggleContent = this.props.toggleContent;
return (
<div className="modal-wrapper">
<ul className="country-list">
{this.state.data.map((item, index) => (
<li className='card' key={index}>
<span>{item.continent} </span>
<ul className="accordion-body">
{item.regions.map((c, i) => (
**<li key={i} onClick={() => toggleContent(c.country,c.flag, c.languages, c.region)} >**
<img src={c.flag}/> {c.country}
</li>
))}
</ul>
</li>
))}
</ul>
</div>
);
}
Changed below line
onClick={() => toggleContent(c.country,c.flag, c.languages, c.region)
I want to change the list-items' background-color property, when the chosen NavLink is active. From the docs I learned, that for adding an additional class to a NavLink, when it is active, I can use activeClassName prop, for making what I want, I need a parent selector in css, which don't exist.
How to choose the parent element of the NavLink tag, when it has an active class?
I need the green background only on the list-item with an active child NavLink.
<ul className="navbar-nav">
<li className="navItem">
<NavLink className="navlink text-dark" activeStyle={{color: "#fff !important"}} to="/" exact>about</NavLink>
</li>
<li className="navItem">
<NavLink className="navlink text-dark" activeStyle={{color: "#fff !important"}} to="/services">services</NavLink>
</li>
<li className="navItem">
<NavLink className="navlink text-dark" activeStyle={{color: "#fff !important"}} to="/contacts">contacts</NavLink>
</li>
</ul>
The most straight forward way would probably be to apply a state to your parent element to achieve this effect, which i.e. could represent the name of a CSS class for your parent. You can change this state depending on the active route or the clicked item to apply a new CSS class.
See working example here
Your parent class could look like this:
import React from 'react'
import NavLink from './NavLink'
class Navbar extends React.Component {
constructor(props) {
super(props)
this.state = {
navbarClass: "navbar"
}
}
setBgClass (title) {
this.setState({
navbarClass: `navbar navbar-${title}`
})
}
render() {
return (
<div className={this.state.navbarClass}>
<NavLink
className="nav-item"
onClick={() => this.setBgClass('about')}
href='/about/'>
About
</button>
<NavLink
className="nav-item"
onClick={() => this.setBgClass('services')}
href='/services/'>
Services
</button>
<NavLink
className="nav-item"
onClick={() => this.setBgClass('contact')}
href='/contact/'>
Contact
</button>
</div>
);
}
}
export default Navbar
After that you only have to define appropriate CSS classes in your components stylesheet:
.navbar-about { background: green; }
.navbar-services { background: blue; }
.navbar-contact { background: orange; }
NOTE:
If you call actual routes within your application i.e. using react-router, you may want to change the state of your parent navbar depending on your active route on componentDidMount instead of onClick, since the component may remount when the route changes.
I am trying to build a navigation which staggers the appearance of the contained list items when the menu/navigation is shown.
I have a burger symbol, and when clicked it renders the navigation (fullscreen).
I would now like to have an animation, where the different list items (actual links) appear with some delay to each other, the top one being the first and the bottom one the last.
I thought I could do this with vue's <transition-group> and list transitions, but all the examples are about adding and removing list items, whereas I have all of them from the beginning.
I then read about this: Vue.js: Staggering List Transitions
And I thought that might be it.
Unfortunately I could not get it to work either.
Do you have any hints how I could do that?
So far, I render the navigation with v-if:
<transition name="u-anim-fade" mode="in-out">
<Navigation v-if="navMenuOpen" />
</transition>
Within the Navigation component:
<nav>
<ul class="Navigation__list">
<li
v-for="(item, key) in menu.items"
class="Navigation__item"
:key="`nav-item-${key}`">
<nuxt-link>
<span v-html="item.title" />
</nuxt-link>
</ul>
</nav>
(I left out some stuff simplify the code here)
When I add the enter/leave/onEnter functions which are suggested here: Vue.js: Staggering List Transitions
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
Like so:
<nav>
<transition-group
name="staggered-fade"
tag="ul"
class="Navigation__list"
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
<li
v-for="(item, key) in menu.items"
class="Navigation__item"
:key="`nav-item-${key}`">
<nuxt-link>
<span v-html="item.title" />
</nuxt-link>
</transition-group>
</nav>
The methods (which I add to the methods of course) would not even be executed when I render the Navigation component. Probably because the items are not added or removed.
Probably because the items are not added or removed.
You're right, and what you need is this:
Transitions on Initial Render
If you also want to apply a transition on the initial render of a
node, you can add the appear attribute:
<transition appear>
<!-- ... -->
</transition>
I just tried it and if not present, the listener functions aren't called on the initial render.
Note that it works as well with <transition-group> components.
For vue 3 user, with GSAP
<template>
<div>
<transition-group
appear
tag="ul"
#before-enter="beforeEnter"
#enter="enter"
>
<li v-for="(item, index) in items" :key="icon.name" :data-index="index">
<div>{{ item.text }}</div>
</li>
</transition-group>
</div>
</template>
<script>
import { ref } from 'vue'
import { gsap } from 'gsap'
export default {
setup() {
const items = ref([
{ text: 'by email' },
{ text: 'by phone' },
])
const beforeEnter = (el) => {
el.style.opacity = 0
el.style.transform = 'translateY(100px)'
}
const enter = (el, done) => {
gsap.to(el, {
opacity: 1,
y: 0,
duration: 0.8,
onComplete: done,
delay: el.dataset.index * 0.2
})
}
return { items, beforeEnter, enter }
}
}
</script>
More info here
I'm Using Two API Endpoints in my React.js App and Axios so that it must list posts and their comments below them.
The Problem is I Can't Excute Two map functions inside each other to do that, I Googled the issue but I had no lead, I also thought using other JavaScript Functions like Reduce instead of map but I dont think it will work out.
The 2 API Endpoints Are :
https://jsonplaceholder.typicode.com/posts
https://jsonplaceholder.typicode.com/comments?postId=1
The Component:
import React, { Component } from 'react';
import axios from 'axios';
import './Postlist.css';
class Postlist extends Component {
state = {
posts: [],
comments: []
};
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(res => {
const posts = res.data;
this.setState({ posts });
});
axios.get(`https://jsonplaceholder.typicode.com/comments`).then(res => {
const comments = res.data;
this.setState({ comments });
});
}
render() {
return (
<div className="container">
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.posts.map(post => (
<li
key={post.id}
className="collection-item left-align red lighten-3"
>
<h5>User ID: {post.id}</h5>
<p>Post: {post.body}</p>
</li>
))}
</ul>
</div>
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.comments.map(comment => (
<li
key={comment.id}
className="collection-item left-align purple lighten-2"
>
<p>User ID: {comment.id}</p>
<p>Post: {comment.body}</p>
</li>
))}
</ul>
</div>
</div>
);
}
}
export default Postlist;
In The Previous code snippet I made 2 requests to the api endpoints and set them to the states posts and comments, Inside render() function the list of posts and comments successfully renders but it listed all posts then all comments, and I need each post with its comments
You can nest 2 maps. Just if you want a comment inside a post you need to nest your HTML tags inside each other and then call the map from inside the other one.
class Postlist extends React.Component {
state = {
posts: [],
comments: []
};
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(res => {
const posts = res.data;
this.setState({ posts });
});
axios.get(`https://jsonplaceholder.typicode.com/comments`).then(res => {
const comments = res.data;
this.setState({ comments });
});
}
render() {
return (
<div className="container">
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.posts.map(post =>
(
<div>
<li
key={post.id}
className="collection-item left-align red lighten-3"
>
<h5>User ID: {post.id}</h5>
<p>Post: {post.body}</p>
</li>
<div className="jumbotron-div col s12">
<ul className="collection">
{
this.state.comments.map(comment => (
<li
key={comment.id}
className="collection-item left-align purple lighten-2"
>
<p>User ID: {comment.id}</p>
<p>Post: {comment.body}</p>
</li>
)
)
}
</ul>
</div>
</div>
))
}
</ul>
</div>
</div>
);
}
}
ReactDOM.render(
<Postlist name="World" />,
document.getElementById('container')
);
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Working JSFiddle