React onClick event does not fire setState - javascript

I am trying to build an image gallery in React.js, everything went smoothly until now.In gallery I am creating Thumbnail objects - on click this will fire "mini gallery" with all pictures from particular project and description for project. However to get back to main gallery I am creating "CLOSE" button within "mini gallery" with an attached handler.Thumbnail click works, however Close Button does not. Please see code attached below.I will be very grateful for any help!
This is Main Gallery:
import React from 'react';
import Thumbnail from '../components/Thumbnail';
export default class Drawings extends React.Component {
render () {
const linkPrefix = "./life/";
const imageS = ".800.jpg";
const imageL = ".jpg";
const lifePics = [
{
name: "One",
filename: [
"lifedrawing1",
],
descr: "one",
},
{
name: "Two",
filename: [
"lifedrawing2",
"lifedrawing2ed",
"lifedrawing2ed2",
],
descr: "two",
},
{
name: "Three",
filename: [
"lifedrawing3",
],
descr: "three",
},
]
return (
<div id="Drawings" className="container row around wrap">
{lifePics.map(
(picture, i) =>
<Thumbnail
key={i}
linkPrefix={linkPrefix}
filename={picture.filename}
imageS={imageS}
imageL={imageL}
/>
)}
</div>
);
}
}
This is each Thumbnail:
import React from 'react';
export default class Thumbnail extends React.Component {
constructor(props) {
super(props);
this.state = {
viewerDisplay: "hidden",
};
}
thumbnailClick(event) {
this.setState({
viewerDisplay: "visible",
});
}
closeViewer(event) {
this.setState({
viewerDisplay: "hidden",
});
}
render () {
const thumbnailStyle = {
width: '45%',
height: '300px',
backgroundImage: 'url('+this.props.linkPrefix + this.props.filename[0]+this.props.imageS+')',
backgroundSize: 'cover',
marginBottom: '10px',
cursor: 'pointer',
};
var viewerStyle = {
position: "absolute",
top: "300px",
right: "50px",
bottom: "10px",
left: "50px",
visibility: this.state.viewerDisplay,
background: "black",
cursor: "auto",
};
const viewerColStyle = {
width: "50%",
height: "100%",
}
return (
<div
className="thumbnail container col between"
style={thumbnailStyle}
onClick={this.thumbnailClick.bind(this)}
>
<div
id="Viewer"
className="viewer container row between"
style={viewerStyle}
>
<div
id="PicList"
className="container col around"
style={viewerColStyle}
>
Thumbnails
{//map function for thumbnails of particular gallery
}
</div>
<div
id="ProjectDescr"
className="container col around"
style={viewerColStyle}
>
Project Descr
</div>
<button
onClick={this.closeViewer.bind(this)}
>CLOSE</button>
</div>
</div>
);
}
}

you should add event.stopPropagation() to the closeViewer function to prevent propagation of the click event to Thumbnail div element
closeViewer(event) {
event.stopPropagation();
this.setState({
viewerDisplay: "hidden",
});
}
Here is an example without stopPropagation
<body>
<div onclick="clickDiv()">
<button onclick="clickButton()">Test</button>
</div>
<script>
function clickButton() {
alert('clickButton');
}
function clickDiv() {
alert('clickDiv');
}
</script>
</body>
Here is an example with stopPropagation
<body>
<div onclick="clickDiv()">
<button onclick="clickButton(event)">Test</button>
</div>
<script>
function clickButton(e) {
e.stopPropagation();
alert('clickButton');
}
function clickDiv() {
alert('clickDiv');
}
</script>
</body>

You should bind your click handler in the constructor or make it an arrow function in order to pass context:
thumbnailClick = (event) => {
this.setState({
viewerDisplay: "visible",
});
}

Related

React JS How to change font color in element other than clicked button onClick?

So I have managed to change the background color of a button using setState() within that button. However, I am trying to use that button to change the font color of list elements within the same component.
Using setState() only lets me change the element I am clicking. I've tried querySelecting the class of the other elements, but using left.setState() is not a valid function.
How can I change the CSS properties of an element using an onClick function of a button?
import React, { Component } from 'react';
import firebase from 'firebase';
import { firebaseConfig } from './connection';
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
let messageRef = firebase.database().ref('messages');
class LandingPage extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
message: '',
list: [],
font: "black",
color: "blue"
}
}
// onChange = () => {
// if (this.state.color == 'blue'){
// this.setState({ color: 'green' });
// }
// else {
// this.setState({ color: 'blue' });
// }
// }
onChange = () => {
var left = document.querySelectorAll(".left");
if (this.state.color === 'black'){
this.setState({ color: 'grey' });
}
else {
this.setState({ color: 'black' });
}
}
render() {
return <div className='container'>
{/* title */}
<div className='titleDiv'>
<h1>React Message App</h1>
</div>
{/* messages will be listed here */}
<div className='messagesDiv' id='messagesDivId'>
<ul>
{/* List array is mapped through*/}
{this.state.list.map(item => {
return (
<li className={(item.name === this.state.name ? 'right' : 'left')}
style={{ color: this.state.font }}
key={item.id}
id={item.id}>
{item.name}: {item.message}
</li>
)
})}
</ul>
</div>
{/*think, delete options*/}
<button className='button think' style={{ backgroundColor: this.state.color }} onClick={this.onChange}>Think...</button>
<button className='button delete'>Delete last message</button>
</div>
}
}
export default LandingPage;
It is the 'think' button which should be clicked to change the list elements with a 'left' or 'right' class name. Please advise...
You messed up some variable names and misunderstood how React works.
First, you can't query and HTML element and execute setState because this is a React function. This function is not accessible from within the HTML document.
Second, your first approach with changing a state variable with the button click and mapping this variable to the color of the list elements is correct, but you mixed up the names:
This is your onChangeMethod:
onChange = () => {
if (this.state.color == 'blue'){
this.setState({ color: 'green' });
}
else {
this.setState({ color: 'blue' });
}
}
Here you are mapping the state variable to the color property:
<li className={(item.name === this.state.name ? 'right' : 'left')}
style={{ color: this.state.font }}
key={item.id}
id={item.id}>
{item.name}: {item.message}
</li>
You are setting state.color in theonChange function, but you are referencing state.font in you list element, instead change style to the following:
style={{ color: this.state.color }}
You need to do the binding to the onChange method. You can do it in the constructor method like this:
constructor(props) {
super(props);
this.state = {
name: '',
message: '',
list: [],
font: "black",
color: "blue"
}
this.onChange = this.onChange.bind(this)
}
import React, { Component } from "react";
class LandingPage extends Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: "1",
message: "Hello World 1"
},
{
id: "2",
message: "Hello World 2"
},
{
id: "3",
message: "Hello World 3"
}
],
color: "red"
};
this.onChange = this.onChange.bind(this);
}
onChange = () => {
if (this.state.color == "red") {
this.setState({ color: "green" });
} else {
this.setState({ color: "red" });
}
};
render() {
return (
<div className="container">
<div className="titleDiv">
<h1>React Message App</h1>
</div>
<div className="messagesDiv" id="messagesDivId">
<ul>
{this.state.list.map(item => {
return (
<li
style={{ color: this.state.color }}
key={item.id}
id={item.id}
>
{item.message}
</li>
);
})}
</ul>
</div>
<button className="button think" onClick={this.onChange}>
Change Color
</button>
</div>
);
}
}
export default LandingPage;
Check whether this is what you want?
if you want to try inline..
<button className='button think' style={{ backgroundColor: this.state.color }} onClick={()=>{this.state.this.state.color == 'blue'?this.setState({ color: 'green' }):this.setState({ color: 'blue' })}}>Think...</button>

How to make chat like UI with chat bubbles in React JS

I have some JSON data in dummyData. I am not sure how can I place the chat bubbles on left and right according to the direction. I am using Material UI and context API. Image for the reference. I don't want to use any library other than material UI.
Currently, every chat bubble is positioned to the left. How to position bubbles according to the direction. Code so far (CodeSandbox):
import React from 'react';
import makeStyles from '#material-ui/core/styles/makeStyles';
const useStyles = makeStyles(theme => ({
container: {
bottom: 0,
position: 'fixed'
},
bubbleContainer: {
width: '100%'
},
bubble: {
border: '0.5px solid black',
borderRadius: '10px',
margin: '5px',
padding: '10px',
display: 'inline-block'
}
}));
const ChatLayout = () => {
const classes = useStyles();
const dummyData = [
{
message: '1: This should be in left',
direction: 'left'
},
{
message: '2: This should be in right',
direction: 'right'
},
{
message: '3: This should be in left again',
direction: 'left'
}
];
const chatBubbles = dummyData.map((obj, i = 0) => (
<div className={classes.bubbleContainer}>
<div key={i++} className={classes.bubble}>
<div className={classes.button}>{obj.message}</div>
</div>
</div>
));
return <div className={classes.container}>{chatBubbles}</div>;
};
export default ChatLayout;
You can create separate div of chat bubble and apply CSS. And where you are receiving messages append the bubble div to your user list.

Sending relative data from mapped array into a modal?

So I have mapped data from array into a carousel, creating total of twenty carousel items. Each element has "same" button embedded into them. I want to send the relative data from each element into the modal when that button is clicked and honestly I have no idea even where to start from.
This is the code I have currently for this component:
Edit: highlighted the data I would like to pass into the relative modal.
import React from 'react';
import {connect} from 'react-redux';
import Slider from 'react-slick';
import Modal from 'react-modal';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { fetchActionM } from '../../store/actions/moviepageActions';
const img_url = 'https://image.tmdb.org/t/p/original';
const customStyles = {
content : {
top : '50%',
left : '50%',
right : 'auto',
bottom : 'auto',
marginRight : '-50%',
transform : 'translate(-50%, -50%)',
color : 'white',
background: '#080a0a none repeat scroll 0% 0%',
width: '600px',
}
};
Modal.setAppElement('#root')
class ActionMov extends React.Component{
constructor() {
super();
this.state = {
modalIsOpen: false
};
this.openModal = this.openModal.bind(this);
this.afterOpenModal = this.afterOpenModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({modalIsOpen: true});
}
afterOpenModal(){
this.subtitle.style.color = '#f00';
}
closeModal(){
this.setState({modalIsOpen: false});
}
render(){
//send same mapped data from this into the modal when clicked on the button <FontAwesomeIcon onClick....
let action;
if(this.props.action.length > 0){
action = this.props.action[0].results.map(ac => (
<div className='sliderbox' key={ac.id}>
<div className='text-block'>
<h5 className='sliderTitle'>{ac.title}</h5>
<FontAwesomeIcon onClick={() => this.openModal({ac})} icon="plus-circle" className='sliderIcon' />
{/* I need same data from these two be passed into the relative modal */}
<p className='sliderRelease'>{ac.release_date}</p>
<p className='sliderVote'>{ac.vote_average}</p>
{/* Just highlighting this area */}
</div>
<img className='sliderImg' src={`${img_url}${ac.poster_path}`} alt={ac.title} />
</div>
));
}
const settings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 6,
slidesToScroll: 3,
draggable: true,
};
return (
<div>
<Slider {...settings}>
{action}
</Slider>
<Modal
isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel='Movies modal'
>
{
//Would like to print relative data here
}
<h2 ref={subtitle => this.subtitle = subtitle}>TITLE GOES HERE</h2>
<div>
<p>Id: {`<id goes here>`}</p>
<h5 className='modalRelease'>Released: {`<release date goes here>`}</h5>
<h5 className='modalVote'>Rating: {`<rating goes here>`}</h5>
</div>
<button className='modalClose' onClick={this.closeModal}>X</button>
</Modal>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
action: state.movies.actions
}
}
export default connect(mapStateToProps)(ActionMov);
On click of the button you can set it to the state and can access inside the modal.
First let's initialize it inside the constructor
constructor() {
super();
this.state = {
modalIsOpen: false,
movie: {
id: '', release: '', rating: ''
}
};
this.openModal = this.openModal.bind(this);
this.afterOpenModal = this.afterOpenModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
Now lets set it up on onClick event, you are actually passing the object to openModal method
openModal(movie) {
this.setState({
modalIsOpen: true,
movie: movie
});
}
Now you are good to access it inside the modal
<Modal
isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel='Movies modal'
>
{
//Would like to print relative data here
}
<h2 ref={subtitle => this.subtitle = subtitle}>TITLE GOES HERE</h2>
<div>
<p>Id: {this.state.movie.id}</p>
<h5 className='modalRelease'>Released: {this.state.movie.release}</h5>
<h5 className='modalVote'>Rating: {this.state.movie.rating}</h5>
</div>
<button className='modalClose' onClick={this.closeModal}>X</button>
</Modal>

another case when the state of a component changes but seems that the component does not

I have a react component that has a state variable:
showEditor
when showEditor is false it is supposed to show only a div with some number inside it (initially showEditor is false). If this state variable is true my react component is supposed to show a textbox and a button with "save" label inside another div -making dissapear the first div with the number-. This textbox will be used to change the number. For the first div (the one that only shows a number) I defined:
<div onClick={this.showEditorProc}>
{ this.state.showEditor ?
<div ....>
{ just the numeric value }
</div>
:
<div ....>
textBox
<button onClick={save and make show Editor false}>
Save
</button>
</div>
</div>
the function this.showEditorProc will modify the state of the showEditor variable to true and the save button and textbox components will appear (inside another div too). I created a function that will executed if the save button is clicked. This function modifies the showEditor variable to false however, I can not see the div with just the numeric value. Instead I still see the textbox with the save button. Is there something else I could be missing? Here it is the code of my component:
import React from 'react';
import ReactDOM from 'react-dom';
import NumberFormat from 'react-number-format';
export class NumericBox extends React.Component {
constructor() {
super();
this.state = {
enteredValue: '',
showNumEditor: false,
index: ''
};
this.showNumericEditor = this.showNumericEditor.bind(this);
this.handle_enteredValue_change = this.handle_enteredValue_change.bind(this);
this.saveCellInfo = this.saveCellInfo.bind(this);
this.loadBasicInformation = this.loadBasicInformation.bind(this);
}
saveCellInfo(e){
alert(this.state.index);
/* cellAuxParams = new Map([['updateCellValue', this.state.updateCellValue]]); */
console.log('numericBox.js>saveCellInfo>cellAuxParams= ------------------------------------------------ ' + 28);
console.log(this.props.cellAuxParams);
var f = this.props.cellAuxParams.get('updateCellValue');
this.setState({showNumEditor: false}, () => f(this.state.Index, '77'));
}
handle_enteredValue_change(values) {
const {formattedValue, value} = values;
// formattedValue = $2,223
// value ie, 2223
this.setState({enteredValue: value});
}
showNumericEditor()
{
this.setState({showNumEditor: true})
}
loadBasicInformation()
{
this.setState({enteredValue: this.props.enteredValue,
index: this.props.index
});
}
componentDidMount(){
this.loadBasicInformation();
}
componentWillReceiveProps(nextProps){
alert(nextProps.enteredValue);
this.setState({enteredValue: nextProps.enteredValue}, () => this.loadBasicInformation());
}
render() {
const table4controls = {
display: 'table',
width: this.props.style.width,
backgroundColor: 'white',
border: '0px solid #666666',
borderSpacing: '0px',
paddingBottom: '0em',
paddingTop: '0em'
};
const table4controls_RowStyle = {
display: 'table-row',
width: 'auto',
clear: 'both',
borderBottom: '5px'
};
const table4controls_ColsStyleA = {
float: 'left',
display: 'table-column',
width: '60px',
backgroundColor: 'white'
};
const table4controls_ColsStyleB = {
float: 'left',
display: 'table-column',
width: '20px',
backgroundColor: 'white'
};
const table4controls_ColsStyleC = {
float: 'left',
display: 'table-column',
width: '20px',
backgroundColor: 'white'
};
const btnStyle={
};
return (
<div onClick={this.showNumericEditor}>
{ this.state.showNumEditor ?
<div style ={table4controls}>
<div style={table4controls_RowStyle}>
<div style={table4controls_ColsStyleA}>
<NumberFormat style={{width: '60px'}}
value={this.state.enteredValue}
thousandSeparator={true}
prefix={this.props.prefix}
onValueChange={this.handle_enteredValue_change}
/>
</div>
<div style={table4controls_ColsStyleB}>
<button style={btnStyle} onClick={() => this.saveCellInfo(this.state.index)}>
▲
</button>
<button style={btnStyle} onClick={() => this.saveCellInfo(this.state.index)}>
▼
</button>
</div>
<div style={table4controls_ColsStyleC}>
<button style={btnStyle} onClick={(e) => {this.saveCellInfo(e, this.state.index)}}>
Save
</button>
</div>
</div>
</div>
:
<div syle={table4controls_ColsStyleA}>
{this.state.enteredValue}
</div>
}
</div>
);
}
}
You have an onClick={this.showNumericEditor} handler on the surrounding div, so when you press the save button, the click event bubbles up and invokes a this.setState({showNumEditor: true}).
To fix it, you can either restructure the rendering or call e.stopPropagation(); at the start of saveCellInfo. Also note that some of your this.saveCellInfo calls are not passing the event.

Dynamic components: Calling element by ref

One part of my application is an image gallery. When the user clicks on an image, I want to put an opaque layer over the image to visualize that it is selected.
When I display the layer, and I click on the image to deselect it, naturally I'm actually clicking on the layer.
Here's the relevant ReactJS code to show what I mean:
{images.map((i, idx) => (
<div key={"cont"+idx} className="container">
<img src={i.images} ref={"img"+idx} />
<div onClick={this.handleIconDeselect} id={"div_"+idx}></div>
</div>
)
)}
I tried to give the img a unique ref (as shown above), but I'm having trouble selecting the correct img.
This is how I try to select the correct image:
handleIconDeselect = (event) => {
var imgref = "icon"+event.target.id.split("_").pop();
this.refs.imgref.click();
}
However, I get the following error message:
TypeError: Cannot read property 'click' of undefined
How can I select the correct image while using unique refs?
Alternatively, if the way I'm trying to achieve this is bad practice (I know you should only use refs when absolutely necessary), what is a better way to do it?
Try use state as here: https://codesandbox.io/s/m4276x643y
Maybe that is not the best way but it give you an rough idea.
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
const coverStyle = {
position: "fixed",
top: 0,
left: 0,
zIndex: -1,
opacity: 0,
width: "100%",
height: "100%",
background: "#000"
};
const coverStyleShow = {
...coverStyle,
zIndex: 1,
opacity: 1
};
const imgShow = {
zIndex: 10,
position: "relative"
};
const images = [
"https://dummyimage.com/100.png/f10/fff",
"https://dummyimage.com/100.png/f20/fff",
"https://dummyimage.com/100.png/f30/fff",
"https://dummyimage.com/100.png/f40/fff",
"https://dummyimage.com/100.png/f50/fff",
"https://dummyimage.com/100.png/f60/fff",
"https://dummyimage.com/100.png/f70/fff"
];
class App extends Component {
constructor(props) {
super(props);
this.state = {
cover: coverStyle,
img: imgShow,
imgId: null,
imgShow: false
};
}
handleImageClick = (target, idx) => {
// you can do something with this "target"...
this.setState({
cover: coverStyle,
coverShow: coverStyleShow,
imgId: idx,
imgShow: !this.state.imgShow
});
};
render() {
return (
<div>
<Hello name="CodeSandbox" />
<h2>Start editing to see some magic happen {"\u2728"}</h2>
<div>
{images.map((img, idx) => (
<img
key={img}
src={img}
style={idx === this.state.imgId ? this.state.img : null}
onClick={event => this.handleImageClick(event.target, idx)}
alt="dummy img"
/>
))}
</div>
<span
style={this.state.imgShow ? this.state.coverShow : this.state.cover}
/>
</div>
);
}
}
render(<App />, document.getElementById("root"));

Categories

Resources