React, add onClick event on dynamically loaded div element. - javascript

Currently on my react app, I am loading many div's which is being dynamically loaded with info from a database. I am trying to make it so when I click on one of these div's a Pop-up emerges, with more in depth data related to that div. However, it does not seem to work as expected. The onClick does not work with this dynamically loaded div. I tried testing the pop-up on a standard button element on my main App component and it worked. Here is my code:
class ResultBox extends Component {
constructor(props){
super(props);
this.state = {
showPopup: false,
posts: []
};
}
togglePopup() {
this.setState({
showPopup: !this.state.showPopup
});
}
componentDidMount() {
axios.get('http://localhost:3001/api/events')
.then(res => {
let posts = res.data.map(obj => obj);
this.setState({posts});
console.log(posts);
});
}
render() { ********** Below here is where my issue is *****
return (
this.state.posts.map(events =>
<div key={events.key}
className='result_box'
onClick={this.togglePopup.bind(this)}>
<p>{events.name}</p>
{events.place && events.place.location && <p>
{events.place.location.city}</p>}
</div>
)
{this.state.showPopup ?
<Result
text='Close Me'
closePopup={this.togglePopup.bind(this)}
/>
: null
}
);
}
}
And this ResultBox is being rendered in App
class App extends Component {
render() {
return (
<div className="App">
<NavBar className="navbar-body"/>
<div className="spacer"></div>
<p className="App-intro">
Find a jam
</p>
<ResultBox />
</div>
);
}
}
The Result is simply the pop-up box component. If anyone knows how I can get this to work it would be much appreciated.

Yes, you need to have your posts data in a div, this is how I would structure it.
class ResultBox extends Component {
constructor(props){
super(props);
this.state = {
showPopup: false,
posts: []
};
this.togglePopup = this.togglePopup.bind(this);
}
togglePopup() {
this.setState({
showPopup: !this.state.showPopup
});
}
componentDidMount() {
axios.get('http://localhost:3001/api/events')
.then(res => {
let posts = res.data.map(obj => obj);
this.setState({posts});
console.log(posts);
});
}
buildRows() {
return this.state.posts.map( (events, index) =>
<div key={index} className='result_box' onClick={this.togglePopup}>
<p>{events.name}</p>
{events.place && events.place.location &&
<p>{events.place.location.city}</p>
}
</div>
);
}
render() {
let rows = this.buildRows();
return (
<div>
{rows}
{this.state.showPopup &&
<Result text='Close Me'closePopup={this.togglePopup.bind(this)}/>
}
</div>
);
}
}
export default ResultBox;

Related

How to pass argument from functional component to class component

EDIT - I fixed this and posted the working code.
I'm working on a project and I am having a specific issue I can't figure out how to fix. I am displaying a list of champions images and when the user clicks on one of them (s) then it will change the page to display that champions name. Currently I can console.log any of the names without any issues which means my functional component Newchamp() is working! However I am having trouble passing an argument from NewChamp to the class component SpecificChamp. When I add the last line in Newchamp return and try to display it in SpecificChamp using {s} its undefined!
Is it possible to pass an argument from my functional class to my component class? if not how can I get the page to change to the specific image that is clicked? I am new to react and appreciate any help!
Can anyone please help me out with this
import React, { Component } from 'react';
import './Champions.css';
class AllChamps extends Component {
render() {
let champion = this.props.champion;
return(
<div className='champions'>
<h1> all champions</h1>
{Object.keys(this.props.champions).map((s) => (
<div className='champs' onClick={() => this.props.NewChamp({s, champion})}>
<img
alt='Champion Images'
src={`http://ddragon.leagueoflegends.com/cdn/10.16.1/img/champion/${s}.png`}
onClick={this.props.onClick}
></img>
{s}
</div>
))}
</div>
)}}
class SpecificChamp extends Component {
render() {
let champion = this.props.champion
let Spec = champion[champion.length - 1];
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{Spec}
</div>
</div>
)}
}
class Champions extends Component {
constructor(props) {
super(props);
this.handleAllChamps = this.handleAllChamps.bind(this);
this.handleSpecificChamp = this.handleSpecificChamp.bind(this);
this.NewChamp = this.NewChamp.bind(this);
this.state = {
champions: [],
champion: [],
clickedChamp: false,
thisChamp: 'ahri'
}}
NewChamp = (props) =>
{
let s = props.s;
props.champion.push(s);
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
Object.keys(response.data).map((a) => (s = a
))})
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
console.log(s)
console.log(response.data)
console.log(props.champion)
})
console.log(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`);
}
handleAllChamps = (props) => {
this.setState({ clickedChamp: true,
})};
handleSpecificChamp = () => {
this.setState({ clickedChamp: false,
})};
componentDidMount(props) {
const apiUrl = `http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion.json`;
fetch(apiUrl)
.then(response => { return response.json() })
.then((response) => {
this.setState({
champions: response.data
}, () => (this.state.champions))
return
})
}
render() {
const clickedChamp = this.state.clickedChamp;
let display;
if (clickedChamp ) {
display = <SpecificChamp champion={this.state.champion} onClick={this.handleSpecificChamp} s={this.state.thisChamp}/>;
} else {
display = <AllChamps champions={this.state.champions} onClick={this.handleAllChamps} NewChamp={this.NewChamp} thisChamp={this.state.thisChamp} champion={this.state.champion} />;
}
return (
<div>
<div className='champions'></div>
{display}
</div>
);
}
}
export default Champions;
The render function in class component does not has any props. You should use props from this like what you have done with handle click.
class SpecificChamp extends Component {
render() {
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{this.props.s}
</div>
</div>
)}
}

React - Show loader on Click that already has function assigned to it

I have already a a click event within a ternary operator that does a GET request from my API. When the button is clicked, the button disappears and the data text replaces the button (button disappears). But there is a small gap of time between the get request and the text reveal. I want to put a loading message of some kind at that moment of time so the user knows something is happening. But can't seem to figure it out. Here is my code:
import React, {Component} from "react";
import axios from "axios";
export default class FoodData extends React.Component {
constructor(props) {
super(props);
this.state = {
meal: '',
clicked: false,
isLoaded: false,
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
clicked: true,
});
}
fetchData() {
axios.get('api/menu/food')
.then(res => {
const meal= `${res.data.starters},${ res.data.price}`;
this.setState({
meal: meal,
isLoaded: true
})
console.log(meal)
})
};
combinedFunction() {
this.fetchData();
this.handleClick();
}
render(){
const {isLoaded, meal} = this.state;
return (
<div >
Dish: {
this.state.clicked ? (
this.state.menu
) : (
<button onClick={() => { this.combinedFunction() }}>Find Dish</button>
)}
</div>
);
}
}
Appreciate the help.
What you can do is add a "isLoading" state and put the values before and after your API call like so:
fetchData() {
this.setState({isLoading: true});
axios.get('api/menu/food')
.then(res => {
const meal= `${res.data.starters},${ res.data.price}`;
this.setState({
meal: meal,
isLoaded: true
isLoading: false,
})
console.log(meal)
})
};
And use that on your render to show the "loading icon"
render(){
const {isLoaded, meal, isLoading } = this.state;
return (
<div >
{isLoading ? <div>loading</div> :
Dish: {
this.state.clicked ? (
this.state.menu
) : (
<button onClick={() => { this.combinedFunction() }}>Find Dish</button>
)}
}
</div>
);
}
}
This is a working demo which shows loading when api call starts and disables button to prevent multiple api calls. I added a 2sec time out to show the demo. Check the stackblitz sample
This is the updated code, here I used a fake api (https://jsonplaceholder.typicode.com/users) to show the demo
import React, {Component} from "react";
import axios from "axios";
export default class FoodData extends React.Component {
constructor(props) {
super(props);
this.state = {
meal: '',
clicked: false,
isLoaded: false,
}
this.handleClick = this.handleClick.bind(this);
this.combinedFunction = this.combinedFunction.bind(this)
}
handleClick() {
this.setState({
clicked: true,
});
}
fetchData() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then(res => {
this.setState({
meal: res.data,
isLoaded: false
})
})
};
combinedFunction =()=> {
this.setState({isLoaded: true})
setTimeout(()=>{
this.fetchData();
},2000)
this.handleClick();
}
render(){
const {isLoaded, meal} = this.state;
return (
<>
<div >
Users:
<button onClick={this.combinedFunction } disabled={isLoaded ? true : false}>{isLoaded ? 'Loading...':'Find User'}</button>
</div>
<div>
{meal && meal.map(item =>(
<div key={item.id}>
<p>{item.id} - {item.name}</p>
</div>
))}
</div>
</>
);
}
}

React-JS How do I make a preloader icon appear before the image gallery is loaded?

I have a lightbox gallery coming through dependency react-lightbox-component.
Everything is working fine but before the gallery pop up on the screen I would like to show a preloader icon coming from the font-awesome:
<i className="fa fa-spinner"></i>
The font-awesome is already installed in my application and working fine. How do I imnplement the preloader with the lightbox gallery?
React-lightbox-Component documentation:
https://www.npmjs.com/package/react-lightbox-component
My component:
import React, { Component } from 'react'
import Lightbox from 'react-lightbox-component';
class PortfolioPage extends Component {
constructor(props) {
super(props);
this.state = {
resultPhotos: []
}
}
componentDidMount() {
this.setState({
resultPhotos: this.props.data.photos
})
}
render() {
const { resultPhotos } = this.state;
const renderImages = resultPhotos && resultPhotos.map((photo, index) => {
return (
{
src: `../images/${photo}`,
key: index
}
)
})
return (
<div>
<Lightbox images={renderImages} />
</div>
)
}
}
export default PortfolioPage
Try this way:
I changed the componentDidMount in order to set the photos already parsed.
I also changed the return section with two render possibilities.
Hope it helps.
import React, { Component } from 'react'
import Lightbox from 'react-lightbox-component';
class PortfolioPage extends Component {
constructor(props) {
super(props);
this.state = {
resultPhotos: []
}
}
componentDidMount() {
const parsePhotos = arr =>
arr.map((photo, index) => ({
src: `../images/${photo}`,
key: index
}));
const { data } = this.props;
if (data && data.photos) {
this.setState({
resultPhotos: [...parsePhotos(data.photos)]
})
}
}
render() {
const { resultPhotos } = this.state;
return (
<div>
{ !!resultPhotos.length
? (<Lightbox images={resultPhotos} />)
: (<i className="fa fa-spinner"></i>)
}
</div>
)
}
}
export default PortfolioPage
UPDATE
After a chat with #claudiobitar we found that it was a problem with the dependency react-lightbox-component.
It is not a problem of the PortfolioPage.jsx, but the Lightbox component.
If it is a dependency issue there is no much to do, sorry, just try another one.
If a dependency has less than 1000 downloads per week is a bad sign.
componentDidMount() {
this.setState({
resultPhotos: this.props.data.photos,
isReady = false,
})
}
render() {
const { resultPhotos, isReady } = this.state;
const renderImages = resultPhotos && resultPhotos.map((photo, index) => {
return (
{
src: `../images/${photo}`,
key: index
}
)
})
if (!isReady) return (<i className="fa fa-spinner"></i>);
return (
<div>
<Lightbox images={renderImages} />
</div>
)
}
Here you almost have everything you need, just find the right place where to put this.setState({isReady: true}).

Show and Hide specific component in React from a loop

I have a button for each div. And when I press on it, it has to show the div with the same key, and hide the others.
What is the best way to do it ? This is my code
class Main extends Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", key: "1" },
{ message: "message2", key: "2" }
]
};
}
handleClick(message) {
//something to show the specific component and hide the others
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<Button key={message.key} onClick={e => this.handleClick(message)}>
{message.message}
</Button>
)
});
let messageNodes2 = this.state.messages.map(message => {
return <div key={message.key}>
<p>{message.message}</p>
</div>
});
return <div>
<div>{messageNodes}</div>
<div>{messageNodes2}</div>
</div>
}
}
import React from "react";
import { render } from "react-dom";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", id: "1" },
{ message: "message2", id: "2" }
],
openedMessage: false
};
}
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<button key={message.id} onClick={e => this.handleClick(message.id)}>
{message.message}
</button>
);
});
let messageNodes2 = this.state.messages.map(message => {
return (
<div key={message.key}>
<p>{message.message}</p>
</div>
);
});
const { openedMessage } = this.state;
console.log(openedMessage);
return (
<div>
{openedMessage ? (
<div>
{openedMessage.map(item => (
<div>
{" "}
{item.id} {item.message}{" "}
</div>
))}
</div>
) : (
<div> Not Opened</div>
)}
{!openedMessage && messageNodes}
</div>
);
}
}
render(<Main />, document.getElementById("root"));
The main concept here is this following line of code.
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}`
When we map our messageNodes we pass down the messages id. When a message is clicked the id of that message is passed to the handleClick and we filter all the messages that do not contain the id of the clicked message. Then if there is an openedMessage in state we render the message, but at the same time we stop rendering the message nodes, with this logic {!openedMessage && messageNodes}
Something like this. You should keep in state only message key of visible component and in render method you should render only visible component based on the key preserved in state. Since you have array of message objects in state, use it to render only button that matches the key.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
//My array messages: [],
visibleComponentKey: '',
showAll: true
};
handleClick(message) {
//something to show the specific component and hide the others
// preserve in state visible component
this.setState({visibleComponentKey : message.key, showAll: false});
};
render() {
const {visibleComponentKey, showAll} = this.state;
return (
<div>
{!! visibleComponentKey && ! showAll &&
this.state.messages.filter(message => {
return message.key == visibleComponentKey ? <Button onClick={e => this.handleClick(message)}>{message.message}</Button>
) : <div /> })
}
{ !! showAll &&
this.state.messages.map(message => <Button key={message.key} onClick={e => this.handleClick(message)}>{message.message}</Button>)
}
</div>
);
}
}
I haven't tried it but it gives you a basic idea.
I cannot reply to #Omar directly but let me tell you, this is the best code explanation for what i was looking for! Thank you!
Also, to close, I added a handleClose function that set the state back to false. Worked like a charm!
onCloseItem =(event) => {
event.preventDefault();
this.setState({
openedItem: false
});
}

React.JS - multiple elements sharing a state ( How do I modify only one of the elements without affecting the others? )

class App extends Component {
constructor(props) {
super(props);
this.state = { Card: Card }
}
HandleEvent = (props) => {
this.SetState({Card: Card.Active}
}
render() {
return (
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
)
}
}
const Card = props => {
return (
<div style={props.state.Card} onClick={
props.HandleEvent}>Example</div>
)
}
Every time I click on one of the cards all of my elements change states, how do I program this to only change card that I clicked?
Here's a working example
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
0: false,
1: false
};
}
handleEvent(idx) {
const val = !this.state[idx];
this.setState({[idx]: val});
}
render() {
return (
<div>
<Card state={this.state[0]} handleEvent={()=>this.handleEvent(0) } />
<Card state={this.state[1]} handleEvent={()=>this.handleEvent(1) } />
</div>
);
}
}
const Card = (props) => {
return (<div onClick={() => props.handleEvent()}>state: {props.state.toString()}</div>);
}
You can also see it in action here
Obviously this is a contrived example, based on your code, in real world application you wouldn't store hardcoded state like {1: true, 2: false}, but it shows the concept
It's not completely clear from the example what is the Card in the constructor. But here the example of how you can modify clicked element.
Basically you can keep only index of clicked element in parent's state, and then pass it as some property to child component, i.e. isActive here:
const cards = [...arrayOfCards];
class App extends Component {
constructor(props) {
super(props);
this.state = { activeCardIndex: undefined }
}
HandleEvent = (index) => {
this.SetState({
activeCardIndex: index
});
}
render() {
return ({
// cards must be iterable
cards.map((card, index) => {
return (
<Card
key={index}
Card={Card}
isActive={i === this.state.activeCardIndex}
HandleEvent={this.HandleEvent.bind(this, index)}
/>
);
})
});
}
}
const Card = props => {
// style active card
const style = Object.assign({}, props.Card, {
backgroundColor: props.isActive ? 'orange' : 'white',
});
return (
<div style={style} onClick={
props.HandleEvent}>Example</div>
)
}

Categories

Resources