Event Propagation in React - reactjs

I have a modal. If I click on the background, I want the modal to go away. If I click on the modal form, I do not want it to go away. Having an issue where trigger events on the form are bubbling up to the container background and closing the div. I want the modal to NOT close if the inner div is clicked.
(clearQuiz makes the modal disappear)
Here is the component:
import React from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { clearQuiz } from '../../actions/quiz';
import './Quiz.css';
export class Quiz extends React.Component {
handleClose(e) {
e.stopPropagation();
this.props.dispatch(clearQuiz());
}
render() {
let answers = this.props.answers.map((answer, idx) => (
<li key={idx}>{answer}</li>
));
if (this.props.usingQuiz) {
return (
<div className="quiz-backdrop" onClick={e => this.handleClose(e)}>
<div className="quiz-main">
<h2>{this.props.title} Quiz</h2>
<h3>{this.props.currentQuestion}</h3>
<ul>
{answers}
</ul>
</div>
</div>
)
}
else {
return <Redirect to="/" />;
}
}
}
const mapStateToProps = state => ({
usingQuiz: state.currentQuestion,
answers: state.answers,
currentQuestion: state.currentQuestion,
title: state.currentQuiz,
});
export default connect(mapStateToProps)(Quiz);

Don't pass the event at all! Try onClick={()=>this.handleClose()}.

Related

Redirect happens before onClick() on REACT { Link }

Redirect happens before onClick() on REACT { Link }
Hello. I'm trying the whole day to play a sound onClick Button Event. It works when I remove the Link (URL), but with the Link, the page goes to the new URL without firing the onClick event.
I already tried like this: onClick={() => this.playAudioHandler}.
I really appreciate if someone can fix my code in order to fire the “playAudioHandler” before going to the new URL.
This is my actual code:
import React, { Component } from "react";
import { Link } from "react-router-dom";
import classes from "./Button.module.css";
import clickSound from "../assets/click.mp3";
class Button extends Component {
playAudioHandler = (event) => {
const audioEl = document.getElementsByClassName("audio-element")[0];
audioEl.play();
// event.preventDefault();
};
render() {
return (
<>
<audio className="audio-element">
<source src={clickSound}></source>
</audio>
<Link to={this.props.linkToGo}>
<button
type="submit"
onClick={this.playAudioHandler}
className={[classes.Button, classes[this.props.btnType]].join(" ")}
// join() to transform the array in a string
>
{this.props.children}
</button>
</Link>
</>
);
}
}
export default Button;
Link is firing each click because is the way it works. In your case, button is wrapped by Link, so Link gets fired.
An easy way to achieve that is to avoid using Link and, since looks like your are using react-router-dom.
<button
type="submit"
onClick={this.playAudioHandler}
className={[classes.Button,
classes[this.props.btnType]].join(" ")}
>
{this.props.children}
</button>
And your handler will redirect after sound was played:
playAudioHandler = (event) => {
event.preventDefault();
const audioEl = document.getElementsByClassName("audio-element")[0];
audioEl.play();
const waitSecs = 3000 // 3 secs
setTimeout(() => {
history.push('/url');
OR
window.location.href = 'url'
}, waitSecs);
};
How about trying setting a state that defines where to redirect and setting that state inClick the button:
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import classes from "./Button.module.css";
import clickSound from "../assets/click.mp3";
class Button extends Component {
constructor(super) {
super(props);
this.state = {
redirect: null
}
}
playAudioHandler = (event) => {
const audioEl =
document.getElementsByClassName("audio-element")[0];
audioEl.play();
// event.preventDefault()
this.setState({ redirect: this.props.linkToGo })
};
render() {
if (this.state.redirect) {
return <Redirect to={redirect} />
}
return (
<>
<audio className="audio-element">
<source src={clickSound}></source>
</audio>
<button
type="submit"
onClick={ this.playAudioHandler}
className={[classes.Button,
classes[this.props.btnType]].join(" ")}
// join() to transform the array in a string
>
{this.props.children}
</button>
</>
);
}
}
export default Button;
Let me know if it works!

Why state in parent component doesnt change when child invoke parent function?

I am using bootstrap modal and on click event on an element its opened but cant closed it when i click the "x" on the right corner of the modal.
The problem is that i do succeed to pass the state by props from parent to child but when i invoke the function "lgClose" inside the child, its goes to the parent component but doesnt actually changing the state position to "false".
I can see its go inside the function because i put "console.log('im in the function')" and i can see it. Why the state doesnt changed to false in parent component ?
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import { connect } from 'react-redux';
import { getAllUsers,getMessages } from './../actions';
import { bindActionCreators} from 'redux';
import { Button, Modal } from 'react-bootstrap';
import ContainerModal from './../components/popup_modal';
// parent component
class MainContainer extends Component {
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
}
componentWillMount(){
this.props.getAllUsers();
}
renderList = ({list}) => {
if(list){
return list.map((item)=>{
return(
<div key={item._id} className="item-list" onClick={() => this.setState({lgShow: true})}>
<div className="title">{item.firstName} {item.lastName}</div>
<div className="body">{item.age}</div>
<div>
<ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/>
</div>
</div>
)
})
}
}
lgClose = () => {
this.setState({lgShow:false});
console.log('im in the function');
}
render() {
console.log(this.state);
return (
<div className="App">
<div className="top">
<h3>Messages</h3>
<Link to="/form">Add</Link>
</div>
<div className="messages_container">
{this.renderList(this.props.users)}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
messages:state.messages,
users:state.users
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({getAllUsers,getMessages},dispatch);
}
export default connect(mapStateToProps,mapDispatchToProps)(MainContainer);
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
size="lg"
show={props.lgShow}
onHide={ props.lgClose }
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
Seems that this in the lgClose function is not pointing to MainContainer.
If that is the case, one way of solving it is to bind the lgClosefunction to MainContainer in the constructor:
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
this.lgClose = this.lgClose.bind(this); // Add this line
}
Hope this helps :)
In your ContainerModal try this:
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
{...props}
size="lg"
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
And in renderList, do this:
<ContainerModal show={this.state.lgShow} onHide={this.lgClose} />
Here is the reason why your lgShow remains true.
This explanation would be little longer. So please bear with me.
Here is what you have done.
You have added your ContainerModal as a child of div (className="item-list") inside renderList method which has an onClick listener attached to itself.
When you click on the list item it works fine and the modal appears but when you click on the close button on the modal things become fishy.
This is because of the onClick listener of the div (className="item-list") getting called after the
ContainerModal's click listener is called setting the lgShow to true again. This behavior is termed as event bubbling where both parent and child receive event in a bottom-up way (i.e child receiving the first event followed by the parent).
To verify this behavior add a console.log to the onClick listener of the div.
Here is a link to a github question for the explanation on event bubbling
Solution
First and foremost suggestion would be to take out the ContainerModal from the renderList method to the root div. This is because right now for every list item of yours a ContainerModal is being created which is unnecessary. For example if you have 10 users in the list you are creating 10 ContainerModal (To verify this increase the number of users and you could see the shade of the modal getting darker.)
The solution to the above problem is to take the ContainerModal to the root level where it's onClick will not coincide with the onClick of the div (className="item-list") in the renderList and your problem would be solved.
Here is the modified solution.
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import { connect } from 'react-redux';
import { getAllUsers,getMessages } from './../actions';
import { bindActionCreators} from 'redux';
import { Button, Modal } from 'react-bootstrap';
import ContainerModal from './../components/popup_modal';
// parent component
class MainContainer extends Component {
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
}
componentWillMount(){
this.props.getAllUsers();
}
renderList = ({list}) => {
if(list){
return list.map((item)=>{
return(
<div key={item._id} className="item-list" onClick={() => this.setState({lgShow: true})}>
<div className="title">{item.firstName} {item.lastName}</div>
<div className="body">{item.age}</div>
<div>
{/* <ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/> */}
</div>
</div>
)
})
}
}
lgClose = () => {
this.setState({lgShow:false});
console.log('im in the function');
}
render() {
console.log(this.state);
return (
<div className="App">
<div className="top">
<h3>Messages</h3>
<Link to="/form">Add</Link>
</div>
<div className="messages_container">
{this.renderList(this.props.users)}
</div>
<ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/>
</div>
);
}
}
function mapStateToProps(state) {
return {
messages:state.messages,
users:state.users
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({getAllUsers,getMessages},dispatch);
}
export default connect(mapStateToProps,mapDispatchToProps)(MainContainer);
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
size="lg"
show={props.lgShow}
onHide={ props.lgClose }
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
Look out for the changed position of Container Modal. Rest of the code is fine.
I have also uploaded a working solution to code sandbox. Have a look at it.
Hope this helps.

React JS thumbnail gallery

I am building a component which is a gallery that consists of a main gallery image and a list of thumbnails under the main image. This component will be used across multiple pages so each page will have it own thumbnails/images. I have worked out how to get the correct images into the gallery depending on the page using redux and a store. However I cant figure out the functionality in getting the main image to change when the corresponding thumbnail is clicked, any ideas or suggestion how I could approach this?
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Gallery extends Component {
render() {
let img = this.props.bouq.map((el, index) => {
return(
<img src={"/images/" + el + ".jpg"} alt="." key={index}/>
);
})
return(
<section className="gallery">
<div className="mainImage">
<img src='/images/bouquets.jpg' alt="."/>
</div>
<div className="thumbnails">
{img}
</div>
</section>
);
}
}
const mapStateToProps = state => {
return {
bouq: state.bouquets
};
}
export default connect(mapStateToProps)(Gallery);
You can leverage the access to every individual element inside the map, onClick on an element, you can update you mainImage in your redux store.
You'll also need to create an action that updates the mainImage (depending on how you manage your redux actions)
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { updateMainImage } from './actions'
class Gallery extends Component {
updateMainImage = tumbnail => () => {
this.props.dispatch(updateMainImage(thumbnail))
}
render() {
const { mainImg, bouq } = this.props
return (
<section className="gallery">
<div className="mainImage">
<img src={`/images/${mainImage}.jpg`} alt={mainImage} />
</div>
<div className="thumbnails">
{bouq.map(thum => (
<img
key={thumb}
src={`/images/${thumb}.jpg`}
alt={thumb}
onClick={this.updateMainImage(thumb)}
/>
))}
</div>
</section>
)
}
}
const mapStateToProps = state => {
return {
bouq: state.bouquets,
mainImage: state.mainImage,
}
}
export default connect(mapStateToProps)(Gallery)

React. Redirect by button click in React with react-router-dom

So, I try to understand how can I make right redirection in my app with event clicks? I put the react-router-dom redirect logic into the button event handler, but it does not work.
What is I'm making wrong?
import React, { Component, Fragment } from 'react';
import Preloader from '../Preloader/Preloader'
import preloaderRunner from '../../Modules/PreloaderRunner'
import { Redirect } from 'react-router-dom';
import axios from 'axios';
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false
}
}
handleClick = () => {
console.log('Button is cliked!');
return <Redirect to="/employers" />
}
render() {
return (
<Fragment>
<Preloader/>
<h1>This is the Auth Page!</h1>
{this.state.navigate === true
? <div>
<div>You already loggined!</div>
<button onClick={this.handleClick}>Go to the Employers List!</button>
</div>
: <div>
<form>
// some code...
</form>
</div>}
</Fragment>
)
}
}
export default LoginPage;
Things returned by a click handler will not be rendered by your component. You have to introduce a new state property that you can set and then render the <Redirect> component when that property contains a path to redirect to:
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false,
referrer: null,
};
}
handleClick = () => {
console.log('Button is cliked!');
this.setState({referrer: '/employers'});
}
render() {
const {referrer} = this.state;
if (referrer) return <Redirect to={referrer} />;
// ...
}
}
Alternatively instead of rendering your own button with a click handler you could render a <Link> component as suggested by #alowsarwar that will do the redirect for you when clicked.
I believe on click you want to take the user to '/employers' . Then you need to use Link from the react-router-com. Ideally in React events like 'handleClick' should change the state not return a JSX (this is the wrong approach)
import React, { Component, Fragment } from 'react';
import Preloader from '../Preloader/Preloader'
import preloaderRunner from '../../Modules/PreloaderRunner'
import { Redirect, Link } from 'react-router-dom';
import axios from 'axios';
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false
}
}
handleClick = () => {
this.setState({ navigate: true});
}
render() {
return (
<Fragment>
<Preloader/>
<h1>This is the Auth Page!</h1>
{this.state.navigate === true
? <div>
<div onClick="this.handleClick">If you want to enable link on some event (Sample test case fyr)</div>
{this.state.navigate ? <Link to='/employers'/> : null}
</div>
: <div>
<form>
// some code...
</form>
</div>}
</Fragment>
)
}
}
export default LoginPage;

mapping a dispatch to props when using class

I am trying to dispatch a redux action when an element is clicked. Here is how I currently have things set up
the action
export function removeItem(idx) {
// remove the item at idx
return {
type: "DESTROY_ITEM",
payload: {idx: idx}
}
}
container component
import ItemUi from '../ui/Item';
import { connect } from 'react-redux'
import { removeItem } from '../../actions'
const mapDispatchToProps = dispatch => {
return({
destroyItem: (index) => {
dispatch(
removeItem(index)
)
}
})
}
export default connect(null, mapDispatchToProps)(ItemUi)
ui component
import React, { Component, PropTypes } from 'react';
class Item extends Component {
// ... methods removed for length
render() {
return (
<div>
<span
className="btn btn-primary btn-xs glyphicon glyphicon-trash"
onClick={() => {this.props.destroyItem(this.props.index)}}></span>
</div>
)
}
}
DestroyItem.propTypes = {
onRemoveItem: PropTypes.func
}
export default Item
the top level component
import React, { Component } from 'react';
import Item from './Item'
class App extends Component {
render() {
return(
<div className="container">
<NewItem/>
<ClearAll/>
<div className="panel-group" id="accordion">
{this.props.item.map(this.eachItem)} // <-- renders each item
</div>
</div>
)
}
}
export default App;
When the user clicks the span element shown in the Item ui component I want to fire the destroyItem action passing in the component's index prop. Instead I get the error
TypeError: _this2.props.destroyItem is not a function
Can you please provide some guidance on how I should do this? Let me know if there is any other useful context I can provide.
Try:
const mapDispatchToProps = {
destroyItem: removeItem
}

Resources