React - Show Alert each time I click on an image - reactjs

Each time I click on an image, the changeAlertVisibility get called.
It then changes the alertVisible property of the state to true.
Then inside of the render method, if alertVisible value is true, an Alert (dialog box) should get rendered, otherwise nothing.
import React, { Component } from "react";
import Alert from "./Alert";
class ListItem extends Component {
state = {
alertVisible: false
};
changeAlertVisibility = () => {
this.setState(prevState => ({
alertVisible: !prevState.alertVisible
}));
};
render() {
return (
<div className="card__body">
<img
src={this.props.photo.urls.small}
onClick={() => this.changeAlertVisibility()}
/>
{this.state.alertVisible ? (
<Alert
photoDesc={this.props.photo.description}
photoPath={this.props.photo.urls.small}
photoAlt={this.props.photo.id}
/>
) : (
<></>
)}
</div>
);
}
}
export default ListItem;
It works, but only by every second click.
The first time, I click an image, the value of alertVisible changes to true and the Alert pops up.
But the second time, the value of alertVisible changes to false and no Alert pops up.
The third time it works again and so on.
How can I change the code that the Alert pops up on each click?

The problem is that this code is toggling alertVisible to the inverse of its previous state:
changeAlertVisibility = () => {
this.setState(prevState => ({
alertVisible: !prevState.alertVisible
}));
};
So, initially this.state.alertVisible is false. After the first click, it will be set to !this.state.alertVisible // => true. On the second click, you are setting it back to !this.state.alertVisible // => false.
In order to achieve what you want, you need to ALWAYS set alertVisible to true like this:
changeAlertVisibility = () => {
this.setState({ alertVisible: true });
};
Now, you probably want to set it back to false when the user closes the alert. I can't tell you exactly how to achieve that since I can't see the definition of your Alert component. However, I would add a onClose callback to your Alert that gets called when the alert is closed and then set alertVisible to false there:
import React, { Component } from "react";
import Alert from "./Alert";
class ListItem extends Component {
state = {
alertVisible: false
};
changeAlertVisibility = (visible) => {
this.setState({ alertVisible: visible });
};
render() {
return (
<div className="card__body">
<img
src={this.props.photo.urls.small}
onClick={() => this.changeAlertVisibility(true)}
/>
{this.state.alertVisible ? (
<Alert
photoDesc={this.props.photo.description}
photoPath={this.props.photo.urls.small}
photoAlt={this.props.photo.id}
onClose={() => this.changeAlertVisibility(false)}
/>
) : (
null
)}
</div>
);
}
}
export default ListItem;

Related

focus() doesn't set on input field after displaing (use refs)

Dumb React question: how to set focus on input after it displayed and why my code doesn't work? (Without display toggle it works.)
edit: What I expect: after click on a button, the input field appears (by removing .dnone class) and get a focus on it. (But it doesn't.)
My code:
import "./styles.css"; // with .dnone class with display:none
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
this.state = {
active: false
}
}
click = (e) => {
e.preventDefault();
this.setState({
active: true
});
this.input.current.focus();
}
render () {
return (
<div className="App">
<input type="text" ref={this.input} className={(this.state.active ? "" : "dnone")} />
<button type="button" onClick={(e) => this.click(e)}>click</button>
</div>
);
}
}
live demo: https://codesandbox.io/s/immutable-sea-9884z?file=/src/App.js:0-607
Thank you!
The issue is that React state updates are asynchronously processed, so in the click handler when you enqueue a state update you are immediately attempting to focus on the input, but since the active state hasn't updated yet you can't, the dnone classname hasn't been removed and input made visible yet.
Move the focus logic into the componentDidUpdate lifecycle method to "respond" to the active state updating.
componentDidUpdate(prevProps, prevState) {
if (prevState.active !== this.state.active && this.state.active) {
this.input.current.focus();
}
}
click = (e) => {
e.preventDefault();
this.setState({
active: true
});
}

React load in component onClick

I'm trying to load in a component when a button is clicked but when I click on the button () in the below code nothing appears to be happening. I'm just trying to display a copied message and then have it disappear shortly after it appears to show the user the selected text was copied to their clipboard.
This is my current code:
import React, { useState } from 'react'
import Clipboard from 'react-clipboard.js';
const AddComponent = () => {
console.log("copied")
return (
<p className="copied">copied to clipboard!</p>
)
};
export default function Item(props) {
const { itemImg, itemName } = props
return (
<>
<Clipboard data-clipboard-text={itemName} onClick={AddComponent} className="item-container display-flex">
<img src={itemImg} alt={itemName} className="item-img" />
<h3>{itemName}</h3>
</Clipboard>
{AddComponent}
</>
)
}
mostly you want to have a state control, to conditionally render the given component like { isTrue && <MyComponent /> }. && operator only evaluates <MyComponent /> if isTrue has truthy value. isTrue is some state that you can control and change to display MyComponent.
in your case your onClick should be responsible to control the state value:
import React, { useState } from 'react'
export default function Item(props) {
const { itemImg, itemName } = props
const [isCopied, setIsCopied] = useState(false)
const onCopy = () => {
setIsCopied(true)
setTimeout(() => {
setIsCopied(false)
}, 600)
}
return (
<>
<div data-clipboard-text={itemName} onClick={onCopy} className="item-container display-flex">
<img src={itemImg} alt={itemName} className="item-img" />
<h3>bua</h3>
</div>
{isCopied && <AddComponent/>} // short circuit to conditional render
</>
)
}
you could consider check the repo react-toastify that implements Toast messages for you.
You'll want to have onClick be a regular function instead of a functional component, and in the regular function implement some logic to update the state of Item to record that the Clipboard was clicked. Then in Item, instead of always including <AddComponent />, only include it based on the state of Item.

Changing state in React JS is not re-rendering parent

My react app is a multi-page form. It goes to next page after clicking 'Next'. Currently I have some text that should have a css class when current page is page 1, and when user goes to next page, the css class should be removed for that text (the text is still displayed for all pages).
My actual code is much larger so I'm only posting all the important parts(I think) that are required for this questions.
import ChildComponent from '....';
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
page: page + 1 //to go to next page
});
this.setState({
currentPageis1: !currentPageis1
});
}
showPage = () =>{
const {page, currentPageis1} = this.state;
if(page === 1)
return (<ChildComponent
change={this.change}
currentPageis1={currentPageis1}
/>)
}
render(){
return (
<p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<form>{this.showPage()}
)
}
}
class ChildComponent extends React.Component {
someFunction = e =>{
e.preventDefault();
this.props.change();
}
render(){
return (
<Button onClick={this.someFunction}>Next</Button>
)
}
}
Currently, when I click Next button, the currentPageis1 updates to false. I checked it using Firefox React extension. But it does not re-render the page. Which means "Some Text" still has the CSS class.
My guess is className={this.currentPageis1 ? '': 'css-class'} in Parent class is only being run once (when the page is first loaded). Do I have to use lifecycle method? How do I make react re-render everytime currentPageis1 is changed?
You are doing <p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>. In order to apply styles to only page 1, you should revert the values in your condition. When currentPageis1 is false '' value is picked up.
Also this.currentPageis1 is wrong. You should use state i.e. this.state.currentPageis1
Working demo
Like this
<p className={this.state.currentPageis1 ? "some-css-class" : ""}>
Some Text
</p>
To get your style to render, you'll need to add the props keyword.
Return Child component inside of Parent and pass the change method as
a prop
Also, updated your setState so you only call it once instead of twice
in the change method
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
...this.state,
page: page + 1,
currentPageis1: !currentPageis1
});
}
render(){
return (
<div>
<p className={this.props.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<Child change={this.change} />
</div>
)
}
}

how to 'inject' confirm event into react-select?

I am using this project https://github.com/JedWatson/react-select for select component. I use it to render a multi select component. Below is a sample code:
import Async from 'react-select/lib/Async';
<Async
className="user-select"
classNamePrefix="user-select"
defaultValue={this.state.defaultValue}
defaultOptions
isClearable={false}
loadOptions={this.loadOptions}
isMulti
/>
below is a screenshot. It renders two items Purple and Red.
The item will be removed when I click the close button after each item. How can I add a prompt model to ask user confirm before deleting?
on clicking the item send that particular id to the event handler function and set that value and id in the state and also do setState for modal to true to show the model when item is clicked
You also make sure to set the showModal to false when user clicks yes or no in the modal so that it will work next time when you want to delete other item.
constructor(props){
super(props);
this.state = {
itemId: 0,
showModal: false,
itemValue: ""
}
}
handleItem = event => {
this.setState({
itemId: event.target.id,
showModal: true,
itemValue: event.target.value
});
}
resetModalFlag = () => {
this.setState({
showModal: false
})
}
render(){
const { showModal, itemId, itemValue } = this.state;
return(
<div>
<Select onChange={this.handleItem} />
{showModal && <Modal id={itemId} itemValue={itemValue} resetModalFlag={this.resetModalFlag} />}
</div>
)
}
In Modal component access itemId and itemValue using this.props and you can show text like are you sure you want delete this.props.itemValue With yes or no button. When either one of these buttons clicked you need to call resetModalFlag in yes and no button event handler functions like
handleYesButton= () =>{
this.props.resetModalFlag();
}
handleNoButton= () =>{
this.props.resetModalFlag();
}

Semantic-ui-react: How do I trigger a Popup without it being click/hover?

When submitting a form, I wish to show a small popup for 2.5 seconds if the server sends back a bad response.
The logic is fairly simple, however, I cannot figure out how to make this popup listen to a boolean somewhere in the state management(MobX in my case). I can get the content into the Popup just fine, however, the trigger is a button(and the content will show, if you click it) - But how do I make it listen to a boolean value somewhere?
Fairly simple class here:
import React from "react";
import { Popup, Button } from "semantic-ui-react";
import { inject } from "mobx-react";
const timeoutLength = 2500;
#inject("store")
export default class ErrorPopup extends React.Component {
state = {
isOpen: false
};
handleOpen = () => {
this.setState({
isOpen: true
});
this.timeout = setTimeout(() => {
this.setState({
isOpen: false
})
}, timeoutLength)
};
handleClose = () => {
this.setState({
isOpen: false
});
clearTimeout(this.timeout)
};
render () {
const errorContent = this.props.data;
if(errorContent){
return(
<Popup
trigger={<Button content='Open controlled popup' />}
content={errorContent}
on='click'
open={this.state.isOpen}
onClose={this.handleClose}
onOpen={this.handleOpen}
position='top center'
/>
)
}
}
}
However, the trigger value is a button which is rendered if this.props.data is present. But that's not the behavior I wish; I simply want the popup to render(and thus trigger) if this.props.data is there; alternatively, I can provide a true value with props if need be.
But how do I make this component trigger without it being a hover/button?
How about passing in the isOpen prop? Then you could add some logic onto the componentWillReceiveProps hook:
import React from "react";
import { Popup, Button } from "semantic-ui-react";
import { inject } from "mobx-react";
const timeoutLength = 2500;
#inject("store")
export default class ErrorPopup extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
}
};
//This is where you trigger your methods
componentWillReceiveProps(nextProps){
if(true === nextProps.isOpen){
this.handleOpen();
} else {
this.handleClose();
}
}
handleOpen = () => {
this.setState({
isOpen: true
});
this.timeout = setTimeout(() => {
//No need to repeat yourself - use the existing method here
this.handleClose();
}, timeoutLength)
};
handleClose = () => {
this.setState({
isOpen: false
});
clearTimeout(this.timeout)
};
render () {
const errorContent = this.props.data;
if(errorContent){
return(
<Popup
trigger={<Button content='Open controlled popup' />}
content={errorContent}
on='click'
open={this.state.isOpen}
position='top center'
/>
)
}
}
}
Without a need of handling delay - you could simply pass in the isOpen prop and that would do the trick.
And here what it could look like in your parent component's render:
let isOpen = this.state.isOpen;
<ErrorPopup isOpen={isOpen}/>
Set this value to control the popup, ideally, this should be a part of your parent component's state. Having a stateful component as a parent is important to make the popup re-rendered
Just use the property open as in the documentation. There is a separate example when popup is opened by default.

Resources