Change Icon of clicked element (React.js) - javascript

I've done this before but it's not an optimized code, I was trying to do this in another way but I couldn't. So, what I need to achieve is to change the icon of only the clicked element. Right now, when I click on one of the icons, all of them change.
For easier understanding, there is a list with multiple colors and the user has to select one of them.
I'll leave the important code down below:
import React from 'react';
export class Costura extends React.Component {
constructor(props) {
super(props);
this.state = {
token: {},
isLoaded: false,
modelTextures: {},
changeIcon: false
};
this.changeIcon = this.changeIcon.bind(this);
}
changeIcon = () => {
this.setState(prev => ({
changeIcon: !prev.changeIcon
}));
};
render() {
let icon;
if (this.state.changeIcon === true) {
icon = (
<img src="../../../ic/icon-check.svg"
alt="uncheck" className="Checking"
onClick={this.changeIcon} />
);
} else {
icon = (
<img src="../../../ic/icon-uncheck.svg"
alt="uncheck" className="Checking"
onClick={this.changeIcon} />
);
}
const { modelTextures } = this.state;
return (
<div id="Options">
<div id="OptionsTitle">
<img src="../../../ic/icon-linha.svg" alt="costura" />
<h2>Costura</h2>
</div>
{modelTextures.textures.map(texture => (
<div>
<img src={"url" + texture.image} />
<p key={texture.id}>{texture.name}</p>
{icon}
</div>
))}
</div>
);
}
}

You can set the selectedTextureId in the state and make a check against that when rendering the component to display the unchecked or checked image icon. Following is the code for reference.
import React from 'react';
export class Costura extends React.Component {
constructor(props) {
super(props);
this.state = {
token: {},
isLoaded: false,
modelTextures: {},
selectedTexture: null
};
this.selectedImageIcon = '../../../ic/icon-check.svg';
this.unselectedImageIcon = '../../../ic/icon-uncheck.svg';
}
changeIcon = (textureId) => () => {
this.setState({
selectedTexture: textureId
})
};
render() {
const { modelTextures } = this.state;
return (
<div id="Options">
<div id="OptionsTitle">
<img src="../../../ic/icon-linha.svg" alt="costura" />
<h2>Costura</h2>
</div>
{modelTextures.textures.map(texture => (
<div key={texture.id}>
<img src={"url" + texture.image} />
<p key={texture.id}>{texture.name}</p>
<img
src={this.state.selectedTexture === texture.id ? this.selectedImageIcon: this.unselectedImageIcon }
alt="uncheck"
className="Checking"
onClick={this.changeIcon(texture.id)}
/>
</div>
))}
</div>
);
}
}

Related

React.js targeting a single element with a shared onClick function

I am new to both coding as well as React.js, so any assistance in learning what I am doing incorrectly is greatly appreciated! I am creating multiple cards on a page with riddles where the answer is hidden via css. I am using an onClick function ("toggleAnswer") to toggle the state of each answer to change the className so that the answer will either be visible or hidden. Currently, the onClick event is changing the state for all the answers. I realize this is because my code is not targeting a particular element, but I am unsure how this can be done. How can this be achieved? My code is currently like this:
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
isHidden: true
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e) {
this.setState({ isHidden: !this.state.isHidden });
}
componentWillMount() {
this.getPageData();
}
render() {
const answerClass = this.state.isHidden ? "answer-hide" : "answer";
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={answerClass}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={props.onClick}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
You'd have to keep track of each answer that has been shown in state (in an array or something).
First
Send the index of the answer up in the onclick function. In that function, check if it exists in the "shownAnswers" array and either add or remove it.
onClick={e => props.onClick(e, props.id)}
and
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({
shownAnswers: this.state.shownAnswers.filter(val => val !== index)
});
} else {
this.setState({
shownAnswers: this.state.shownAnswers.concat(index)
});
}
}
Then
When you're passing the class name down to the child component, check if its index is in the "shownAnswers" array to decide which class name to pass.
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
Building off your example, it could look something like this (untested):
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
shownAnswers: []
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({ shownAnswers: this.state.shownAnswers.filter(val => val !== index) });
} else {
this.setState({ shownAnswers: this.state.shownAnswers.concat(index) });
}
}
componentWillMount() {
this.getPageData();
}
render() {
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={e => props.onClick(e, props.id)}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;

Show message as sent/received in other users window with react

I have a prototype chat app in react that I can currently show sent messages in both viewers windows (Agent and User). However, I'm having trouble figuring out how to only show a sent message as sent in one window and received in the other and vice versa. So if the Agent sends a message, it will show with their name and the message in both windows. I'm thinking I need to pass the "author" into each "User messages" and "Agent messages" within App.js, but this doesn't work.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
messages: []
}
this.handleNewMessage = this.handleNewMessage.bind(this);
}
static propTypes = {
messages: PropTypes.arrayOf(PropTypes.object)
}
handleNewMessage = (text) => {
this.setState({
messages: [...this.state.messages, { me: true, author: "Me", body: text},{ me: true, author: "Agent", body: text}]
})
}
render() {
return (
<div className="App">
<div className="agentWindow">
<Agent messages={this.state.messages} handleNewMessage={this.handleNewMessage} />
</div>
<div className="userWindow">
<User messages={this.state.messages} handleNewMessage={this.handleNewMessage} />
</div>
</div>
);
}
}
Agent.js(User.js is the same)
class Agent extends Component {
render() {
return (
<div className="Agent">
<header>
<p>Agent</p>
</header>
<MessageList messages={this.props.messages} />
<MessageForm onMessageSend={this.props.handleNewMessage} />
</div>
);
}
}
Message.js
class Message extends Component {
static propTypes = {
author: PropTypes.string,
body: PropTypes.string.isRequired,
me: PropTypes.bool
}
render() {
const classes = classNames('Message', {
log: !this.props.author,
me: this.props.me
})
return (
<div className={classes}>
{this.props.author && (
<span className="author">{this.props.author}:</span>
)}
{this.props.body}
</div>
)
}
}
MessageList.js
class MessageList extends Component {
static propTypes = {
messages: PropTypes.arrayOf(PropTypes.object)
}
static defaultProps = {
messages: [],
}
componentDidUpdate = () => {
this.node.scrollTop = this.node.scrollHeight
}
render() {
return (
<div className="MessageList" ref={(node) => (this.node = node)}>
{this.props.messages && this.props.messages.map((message, i) => (
<Message key={i} {...message} />
))}
</div>
)
}
}
MessageForm.js
class MessageForm extends Component {
static propTypes = {
onMessageSend: PropTypes.func.isRequired,
}
componentDidMount = () => {
this.input.focus()
}
handleFormSubmit = (event) => {
event.preventDefault()
this.props.onMessageSend(this.input.value)
this.input.value = ""
}
render() {
return (
<form className="MessageForm" onSubmit={this.handleFormSubmit}>
<div className="input-container">
<input
type="text"
ref={(node) => (this.input = node)}
placeholder="Enter Message..."
/>
</div>
<div className="button-container">
<button type="submit">
Send
</button>
</div>
</form>
)
}
}
I didn't understand your explanation...revising my answer. Please h/o.
OK. You are on the right track. Here is the solution I think you are looking for:
https://codesandbox.io/s/jz6o9y5n7v
Only a few changes. On the form, I added the "source" prop and pass that through to your handler in the App component. That correctly sets the Author prop to the source sent in from the form. I removed the second data element from the messages array. All this gives you the output I think you are after.
Thanks for the opportunity to help with this.

React js: Accessing state of other components

I have a component built using the below code. The aim is to add a class on the card to highlight it when the button inside it is clicked. However, the below code works on the first click but doesn't work for the subsequent clicks.
I understood that I have to set the clicked state of other elements to false when I remove the class. How can this be done?
import React, { Component } from 'react';
import './PricingCard.css';
class PricingCard extends Component {
constructor(){
super();
this.state = {
clicked : false
}
}
makeSelection(){
let elems = document.getElementsByClassName('Card');
for(var i=0;i<elems.length;i++){
elems[i].classList.remove("active");
}
this.setState({clicked: true});
}
render() {
var activeClass = this.state.clicked ? 'active' : '';
return (
<div className= {"categoryItem Card " + this.props.planName + " " +activeClass}>
<div className="cardDetails">
<div> {this.props.planName} </div>
<div className="pricing"> {this.props.price} </div>
<button onClick={this.makeSelection.bind(this)} className="buttonPrimary"> Select this plan </button>
<div className="subtitle"> {this.props.footerText} </div>
</div>
</div>
);
}
}
export default PricingCard;
Wouldn't it be easier to have the logic in a parent component? Since it is "aware" of all the child Card components.
Have something like...
this.state = { selectedComponent: null };
onClick(card_id) {
this.setState({ selectedComponent: card_id });
}
...in render:
const cards = smth.map((card) =>
<Card onClick={this.onClick.bind(this, card.id)}
isActive={map.id === this.state.selectedComponent} />
Would this work?
Best way will be to lift lift the state up. Like this:
class PricingCardContainer extends React.Component {
constructor(props){
super(props);
this.state = {
selectedCard: NaN,
}
}
handleCardClick(selectedCard){ this.setState({ selectedCard }); }
render() {
return (
<div>{
this.props.dataArray.map((data, i) =>
<PricingCard
key={i}
className={this.state.selectedCard === i ? 'active': ''}
price={data.price}
onClick={() => this.handleCardClick(i)}
footerText={data.footerText}
planName={data.planName}
plan={data.plan}
/>
)
}</div>
)
}
}
const PricingCard = ({ className = '', planName, price, onClick, footerText }) => (
<div className= {`categoryItem Card ${planName} ${className}`}>
<div className="cardDetails">
<div> {planName} </div>
<div className="pricing"> {price} </div>
<button onClick={onClick} className="buttonPrimary"> Select this plan </button>
<div className="subtitle"> {footerText} </div>
</div>
</div>
);
export default PricingCard;
Although it would be better to use some data id than index value.

Is This An Anti-Pattern - Reactjs

I have just started using React and working on a small app, in the meantime I made a small show and hide modal. I wanted to know the way I have made it is a wrong way to do it. If this is an anti-pattern how should I go about it?
class App extends Component {
constructor(props) {
super(props);
this.state = {show: false};
this.showModal = this.showModal.bind(this);
}
render() {
return (
<div>
<h2 className={styles.main__title}>Helloooo!</h2>
<Modal ref='show'/>
<button onClick={this.showModal} className={styles.addtask}>➕</button>
</div>
);
}
showModal(){
this.setState({
show: true
});
this.refs.show.showModal();
}
}
The modal component which i have made is using this logic, it hooks the dom elements and modifies using the document.queryselector. Is this a right way to do the dom manipulation in react.
The modal code which i have used is this :
class Modal extends Component {
constructor() {
super();
this.hideModal = this.hideModal.bind(this);
this.showModal = this.showModal.bind(this);
this.state = { modalHook: '.'+styles.container };
}
render() {
return (
<div>
<div onClick={this.hideModal} className={styles.container}>
<div className={styles.container__content}>
<div className={styles.card}>
<div className={styles.card__header}>
<h2>Add new task</h2>
</div>
<div className={styles.card__main}>
<Input type="text" placeholder="enter the task title" />
<Input type="textarea" placeholder="enter the task details" />
</div>
<div className={styles.card__actions}>
</div>
</div>
</div>
</div>
</div>
);
}
showModal(){
let container = document.querySelector(this.state.modalHook);
container.classList.add(styles.show);
}
hideModal(e){
let container = document.querySelector(this.state.modalHook);
if(e.target.classList.contains(styles.container)){
container.classList.remove(styles.show);
}
}
}
Your example looks good and simple, but accordingly to this it is better don't overuse refs.
And also it might be helpful to lifting state up, like described here.
Here my example:
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {show: props.show};
}
componentDidUpdate(prevProps, prevState) {
let modal = document.getElementById('modal');
if (prevProps.show) {
modal.classList.remove('hidden');
} else {
modal.className += ' hidden';
}
}
render() {
return (
<div id="modal" className={this.state.show ? '' : 'hidden'}>
My modal content.
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {show: false};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
show: !prevState.show
}));
}
render() {
return (
<div>
<button onClick={this.handleClick}>
Launch modal
</button>
<Modal show={this.state.show} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Here i don't pretend for ultimate truth, but try to provide another option how you can reach desired result.
To do what you require you don't need to use refs at all. You can pass the state down the to child component as a prop. When the state updates the prop will automatically update. You can then use this prop to switch a class. You can see it in action on jsbin here
const Modal = (props) => {
return (
<div className={props.show ? 'show' : 'hide'}>modal</div>
)
}
const styles = {
main__title: 'main__title',
addtask: 'addtask'
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {show: false};
this.toggleModal = this.toggleModal.bind(this);
}
render() {
return (
<div>
<h2 className={styles.main__title}>Helloooo!</h2>
<Modal show={this.state.show} />
<button onClick={this.toggleModal} className={styles.addtask}>➕</button>
</div>
);
}
toggleModal(){
this.setState({
show: !this.state.show
});
}
}
ReactDOM.render(<App />, document.getElementById('root'));

How to toggle an active class on list items?

I'm having a issue in trying to implement a basic functionality in React. I have a list of <img>, and when I click in one, I want to add an active class to this img and remove this class from the other images.
class DefaultImages extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick(e) {
console.log("Element:", e.target)
// I can add the class here, but where to remove from the other images?
}
render() {
var imgSize = "100"
return (
<div>
<img onClick={this.handleClick} src="img/demo.png" width={imgSize} height={imgSize} />
<img onClick={this.handleClick} src="img/demo2.png" width={imgSize} height={imgSize} />
<img onClick={this.handleClick} src="img/demo3.jpg" width={imgSize} height={imgSize} />
<img onClick={this.handleClick} src="img/demo4.png" width={imgSize} height={imgSize} />
</div>
)
}
}
I know how to toggle the class from the clicked image, but how can I remove the active class from the siblings images?
Use the component's state to store the active item and rerender the view when it changes:
import React, { Component } from 'react'
const IMG_SIZE = 100
const imgs = [{ id: 1, src: 'img/demo.png' }, { id: 2, src: '...' }, etc]
class DefaultImages extends Component {
constructor() {
this.state = { activeItem: {} }
this.toggleActiveItem = this.toggleActiveItem.bind(this)
}
toggleActiveItem(imgId) {
this.setState({ activeItem: { [imgId]: true } })
}
render() {
return (
<div>
{imgs.map(img =>
<img
className={this.state.activeItem[img.id] ? 'active' : ''}
onClick={e => this.toggleActiveItem(img.id)}
src={img.src}
width={IMG_SIZE}
height={IMG_SIZE}
alt={`Default image ${img.id}`}
/>
)}
</div>
)
}
}

Categories