Updating the state of many children at once - reactjs

I am trying to control the visibility of GalleryItem components by toggling skill components on/off that were used to create those projects. I would like to:
Show ALL if no skills have been toggled
If one or more skills are toggled, show only GalleryItems that use that skill
The selected skills are stored in state.portfolioTypes of Portfolio component - this works as expected.
I simply do not know how to call the updateDisplay method on each of my GalleryItem components from the click handler of my Skill components.
If someone can help me do that I am flying! Thanks.
I have tried pushing my GalleryItem components into an array that is in the parent state so that I can iterate over the array when toggling a Skill, but despite seeing the component objects when I log the array to the console, they are not rendered in the UI - instead are rendered numbers 13-24 (not sure why...)
resumeData.skills looks like:
skills: ["Branding", "Design", "UX", "Marketing", "Print", "Javascript", "HTML", "Grunt", "JQuery", "LessCSS", "Sketch", "Photoshop", "Illustrator", "Bootstrap"]
The item passed to GalleryItem class looks like:
{
imagePath: "images/portfolio/monster.png",
name: "Monster.com",
description: "Web design, UI Development and Art Direction",
modalImagePath: "images/portfolio/lrg/monster.png",
modalName: "Web design, UI Development and Art Direction",
modalDescription: "Working for one of the internet's biggest brands, I developed UI for internal incubator projects, components of the global web application and helped with the full UI redesign of the job seeker experience.",
modalCategories: ["Branding", "Design", "UX", "Photoshop", "Illustrator"],
url: "http://www.monster.com"
}
My Portfolio class containing Skill class and GalleryItem classes:
(I have removed some code not relevant to this question)
import React, { Component } from 'react';
export default class Portfolio extends Component {
constructor(props){
super(props);
this.state = {
portfolioTypes: [],
galleryItems: []
}
this.togglePortfolioItems = this.togglePortfolioItems.bind(this);
}
togglePortfolioItems(item){
//render only portfolio items with selected tags
console.log("togglePortfolioItems", item);
let portfolioTypes = this.state.portfolioTypes;
if(!item.isToggleOn){
portfolioTypes.push(item.type);
}else{
portfolioTypes.splice(portfolioTypes.indexOf(item.type), 1);
}
this.setState({portfolioTypes: portfolioTypes});
console.log(this.state.portfolioTypes, portfolioTypes);
}
render() {
let resumeData = this.props.resumeData;
let togglePortfolioItems = this.togglePortfolioItems;
let portfolioTypes = this.state.portfolioTypes;
let galleryItems = this.state.galleryItems;
return (
<React.Fragment>
<section id="portfolio">
<div className="row">
<div className="twelve columns collapsed">
<h1>Check Out Some of My Works.</h1>
<div className="skillToggles">
{resumeData.skills.map((item,index) => (
<Skill
skillName={item}
togglePortfolioItems={togglePortfolioItems}
galleryItems={galleryItems}
/>
))}
</div>
{/* portfolio-wrapper */}
<div id="portfolio-wrapper" className="bgrid-quarters s-bgrid-thirds cf">
{resumeData.portfolio.map((item,index) => (
galleryItems.push(<GalleryItem
item={item}
index={index}
portfolioTypes={portfolioTypes}
/>)
))}
</div> {/* portfolio-wrapper end */}
</div> {/* twelve columns end */}
</div> {/* row End */}
</section> {/* Portfolio Section End*/}
</React.Fragment>
);
this.setState({galleryItems: galleryItems});
}
}
class Skill extends Component {
constructor(props) {
super(props);
this.state = {
isToggleOn: false,
type: props.skillName.toLowerCase()
};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
e.preventDefault();
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
this.props.togglePortfolioItems(this.state);
let galleryItems = this.props.galleryItems;
//loop through all galleryItems and set the display of each
galleryItems.map(galleryItem =>(
console.log(galleryItem);
//I would like to fire updateDisplay on galleryItem here
));
}
render() {
let skillName = this.props.skillName;
let skillNameId = skillName.toLowerCase();
return (
<React.Fragment>
<a href="" className={"skill "+(this.state.isToggleOn ? 'on' : 'off')} onClick={this.handleClick}>
{skillName}
</a> {/* Skill Section End*/}
</React.Fragment>
);
}
}
class GalleryItem extends Component{
constructor(props) {
super(props);
let portfolioTypes = this.props.portfolioTypes;
var displayed = true;
this.state = {
displayed: displayed
};
}
updateDisplay(){
let portfolioTypes = this.state.portfolioTypes;
let displayed = false;
if(portfolioTypes.length === 0){
displayed = true;
}else{
for(var x=0; x<portfolioTypes.length; x++){
let cat = portfolioTypes[x];
if(portfolioTypes.indexOf(cat) > -1){
displayed = true;
}
};
}
this.setState({displayed: displayed});
}
render() {
let item = this.props.item;
let index = this.props.index;
return (
<React.Fragment>
<div className={"columns portfolio-item "+(this.state.displayed ? "" : "hide ")+item.modalCategories.sort().join(" ").toLowerCase()}>
<div className="item-wrap">
<a href={"#modal-0"+index} title={item.name}>
<img alt src={item.imagePath} />
<div className="overlay">
<div className="portfolio-item-meta">
<h5>{item.name}</h5>
<p>{item.description}</p>
</div>
</div>
<div className="link-icon"><i className="icon-plus" /></div>
</a>
</div>
</div>
</React.Fragment>
);
}
}
When I toggle a skill, I would like the gallery to update to only display GalleryItems that used the selected skills.
Perhaps you can also suggest improvements to my approach, as there is probably a better/easier/more robust way to achieve this.

change updateDisplay function like this
updateDisplay(){
let portfolioTypes = this.props.portfolioTypes;
let displayed = false;
if(portfolioTypes.length === 0){
displayed = true;
}else{
for(var x=0; x<portfolioTypes.length; x++){
let cat = portfolioTypes[x];
if(portfolioTypes.indexOf(cat) > -1){
displayed = true;
}
};
}
return displayed;
}
Then Define a variable inside render
var displayed = this.updateDisplay()
use this variable instead of this.state.displayed

import React, { Component } from 'react';
export default class Portfolio extends Component {
constructor(props){
super(props);
this.state = {
portfolioTypes: []
}
this.togglePortfolioItems = this.togglePortfolioItems.bind(this);
}
togglePortfolioItems(item){
//render only portfolio items with selected tags
console.log("togglePortfolioItems", item);
let portfolioTypes = this.state.portfolioTypes;
if(!item.isToggleOn){
portfolioTypes.push(item.type);
}else{
portfolioTypes.splice(portfolioTypes.indexOf(item.type), 1);
}
this.setState({portfolioTypes: portfolioTypes});
console.log(this.state.portfolioTypes, portfolioTypes);
}
render() {
let resumeData = this.props.resumeData;
let togglePortfolioItems = this.togglePortfolioItems;
let portfolioTypes = this.state.portfolioTypes;
return (
<React.Fragment>
<section id="portfolio">
<div className="row">
<div className="twelve columns collapsed">
<h1>Check Out Some of My Works.</h1>
<div className="skillToggles">
{resumeData.skills.map((item,index) => (
<Skill
skillName={item}
key={index}
togglePortfolioItems={togglePortfolioItems}
/>
))}
</div>
{/* portfolio-wrapper */}
<div id="portfolio-wrapper" className="bgrid-quarters s-bgrid-thirds cf">
{resumeData.portfolio.map((item,index) => (
<GalleryItem
item={item}
index={index}
key={index}
portfolioTypes={portfolioTypes}
/>
))}
</div> {/* portfolio-wrapper end */}
</div> {/* twelve columns end */}
</div> {/* row End */}
</section> {/* Portfolio Section End*/}
</React.Fragment>
);
}
}
class Skill extends Component {
constructor(props) {
super(props);
this.state = {
isToggleOn: false,
type: props.skillName
};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
e.preventDefault();
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
this.props.togglePortfolioItems(this.state);
}
render() {
let skillName = this.props.skillName;
return (
<React.Fragment>
<a href="#" className={"skill "+(this.state.isToggleOn ? 'on' : 'off')} onClick={this.handleClick}>
{skillName}
</a> {/* Skill Section End*/}
</React.Fragment>
);
}
}
class GalleryItem extends Component{
constructor(props) {
super(props);
let portfolioTypes = this.props.portfolioTypes;
}
updateDisplay(){
console.log("updateDisplay");
let portfolioTypes = this.props.portfolioTypes;
let item = this.props.item;
let displayed = false;
if(portfolioTypes.length === 0){
displayed = true;
}else{
for(var x=0; x<portfolioTypes.length; x++){
let cat = portfolioTypes[x];
if(item.modalCategories.indexOf(cat) > -1){
displayed = true;
}
};
}
return displayed;
}
render() {
let item = this.props.item;
let index = this.props.index;
var displayed = this.updateDisplay();
return (
<React.Fragment>
<div className={"columns portfolio-item "+(displayed ? "" : "hide ")+item.modalCategories.sort().join(" ")}>
<div className="item-wrap">
<a href={"#modal-0"+index} title={item.name}>
<img alt="Gallery Image" src={item.imagePath} />
<div className="overlay">
<div className="portfolio-item-meta">
<h5>{item.name}</h5>
<p>{item.description}</p>
</div>
</div>
<div className="link-icon"><i className="icon-plus" /></div>
</a>
</div>
</div>
</React.Fragment>
);
}
}

Related

How to change state of a sibiling, if I click on a component?

I have three components that render a list of available timeslots.
If I click on a timeslot on the list of component1, it gets selected, now, If a sibiling component, let's call it component2, also has a timeslot that matches the one that had been clicked on component1, I want the one in component2 to be greyed out.
How can I do this?
The components that render the lists of available timeslots are called CompanyPanel:
export default class CompanyPanel extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedTime: 'None',
times: this.props.times,
}
this.chooseTime = this.chooseTime.bind(this)
this.deleteTime = this.deleteTime.bind(this)
}
componentDidMount () {
this.chooseTime(this.state.selectedTime)
}
deleteTime (time) {
this.setState( ({times}) => ({
times: [...this.state.times].filter( t => t !== time),
}))
}
chooseTime (selectedTime) {
this.setState({
selectedTime,
})
}
render() {
const { selectedTime, times } = this.state
return (
<React.Fragment>
<div className="flex-auto pa3">
<div className="ba mv2">
<p className="tc pa2 dib bg-near-white">{this.props.name}</p>
</div>
<div className="ba mv2">
<p className="tc pa2 dib bg-red white">{selectedTime}</p>
</div>
<div className="ba mv2">
{times.map((time, i) => (
<div key={i} className="bg-green">
<span className="pa2 red pointer ma2 bg-white" onClick={() => this.deleteTime(time)}>X</span>
<p onClick={() => this.chooseTime(time.dateUTCString)} className="tc pa2 dib bg-yellow">{time.dateUTCString}</p>
</div>
))}
</div>
</div>
</React.Fragment>
)
}
}
And those CompanyPanel components are being wrapper by a parent component called CompaniesDashboard:
export default class CompaniesDashboard extends React.Component {
constructor(props) {
super(props)
this.state = {
error: null,
data,
}
this.isLoading = this.isLoading.bind(this)
}
isLoading() {
return this.state.posts === null && this.state.error === null
}
render() {
const { data, error } = this.state
return (
<React.Fragment>
{this.isLoading() && <p>LOADING</p>}
{error && <p>{error}</p>}
<div className="flex">
{data && data.map((company, i) => (
<CompanyPanel key={i} times={company.times} name={company.name} />
))}
</div>
</React.Fragment>
)
}
}
I think i need to somehow to set a state in the parent, when the chooseTime method is clicked inside if the CompanyPanel component. But not sure how to do it.

Cannot change the state of parent component and re-render

im new to React, trying to make some simple 'Chat' app, stuck a bit in some feature.
im trying to make user list, that onClick (on one of the user) it will change the class (to active), and when hitting another user it will set the active class to the new user.
tried a lot of things, managed to make it active, but when hitting another user, the old one & the one receive the 'active' class.
here is my Parent componenet
class Conversations extends React.Component {
constructor(props) {
super(props);
this.loadConversations = this.loadConversations.bind(this);
this.selectChat = this.selectChat.bind(this);
this.state = { count: 0, selected: false, users: [] }
}
selectChat = (token) => {
this.setState({ selected: token });
}
loadConversations = (e) => {
$.get('/inbox/get_conversations', (data) => {
let r = j_response(data);
if (r) {
this.setState({ count: r['count'], users: r['data']});
}
});
}
componentDidMount = () => {
this.loadConversations();
}
render() {
return (
<div>
{this.state.users.map((user) => {
return(<User selectChat={this.selectChat} selected={this.state.selected} key={user.id} {...user} />)
})}
</div>
)
}
here is my Child componenet
class User extends React.Component {
constructor(props) {
super(props);
this.handleSelect = this.handleSelect.bind(this);
this.state = {
token: this.props.token,
selected: this.props.selected,
username: this.props.username
}
}
handleSelect = (e) => {
//this.setState({selected: e.target.dataset.token});
this.props.selectChat(e.target.dataset.token);
}
render() {
return (
<div data-selected={this.props.selected} className={'item p-2 d-flex open-chat ' + (this.props.selected == this.props.token ? 'active' : '')} data-token={this.props.token} onClick={(e) => this.handleSelect(e)}>
<div className="status">
<div className="online" data-toggle="tooltip" data-placement="right" title="Online"></div>
</div>
<div className="username ml-3">
{this.props.username}
</div>
<div className="menu ml-auto">
<i className="mdi mdi-dots-horizontal"></i>
</div>
</div>
)
}
Any help will be great...hope you can explain me why my method didnt work properly.
Thank you.
You can make use of index from map function to make element active.
Initially set selected to 0;
this.state = { count: 0, selected: 0, users: [] }
Then pass index to child component,also make sure you render your User component when you are ready with data by adding a condition.
{this.state.users.length > 0 && this.state.users.map((user,index) => {
return(<User selectChat={this.selectChat} selected={this.state.selected} key={user.id} {...user} index={index} />)
})}
In child component,
<div data-selected={this.props.selected} className={`item p-2 d-flex open-chat ${(this.props.selected === this.props.index ? 'active' : '')}`} data-token={this.props.token} onClick={() => this.handleSelect(this.props.index)}>
...
</div>
handleSelect = (ind) =>{
this.props.selectChat(ind);
}
Simplified Demo using List.

I want id of image on click of delete button

**i want id of the particular image at Onclick in reactjs. we have an array with all the data about images i.e. images[ ]. How to pass the id of that particular image to delete it to deleteimage function. any suggestion or advice will be welcomed **
class Image extends Component {
constructor() {
super();
this.state = { images: [] };
}
componentDidMount() {
let result = apiGateway.getImages();
if (result != null) {
result.then((value) => {
this.setState({ images: value.data.data });
});
}
}
deleteImage=()=>{
let result = apiGateway.removeImage(??);
}
renderImage(value,index){
let str = value.imagePath.split('/').pop();
let string = str.slice(0,-5);
return(
<div key={index}>
<p>Title: {value.title}</p>
<p>Description: {value.description}</p>
<button onClick={this.deleteImage}>DELETE</button>
<button>UPDATE</button>
<img className='image' src= {'http://localhost:3000/'+ string } width='100' height='100' alt='nature'/>
<br/><br/>
</div>
);
}
render() {
return(
<div>
<div>{
this.state.images.map((image,index)=>{
return this.renderImage(image,index);
})
}
</div>
</div>
);
}
}
export default Image;
Try send value to deleteImage(value) function:
<button onClick={this.deleteImage.bind(this, value)}>DELETE</button>
OR
<button onClick={(e) => this.deleteImage(value, e)}>DELETE</button>
Please view more here

onClick in reactjs not working

Below is my code. My onClick is nor working. It always through error "Uncaught TypeError: Cannot read property 'likeQuestion' of undefined". But my "gotoPage" function is working. I don't know where I am wrong. I am very new in Reactjs. Why "likeQuestion" function is not recognized.
My first onClick is working
export default class Question extends React.Component {
constructor(){
super();
this.toggle = this.toggle.bind(this);
this.state = {
pageNo : 1,
dropdownOpen: false,
questioninfo : []
}
}
componentWillMount(){
//some action
}
gotoPage(index) {
//some action. This is working
}
toggle() {
this.setState({
dropdownOpen: !this.state.dropdownOpen
});
}
likeQuestion(e){
console.log('this is clicked');
//But this is not working
}
render() {
var canvases = this.state.questionItem.map(function(data,i) {
var firstLtr = data.user_name.charAt(0);
return (
<div key={i}>
<Col sm="12" md={{ size: 12, offset: 2 }} className="questionCard">
<Card block>
<CardTitle>
<div className="outerCircle"><span>{firstLtr}</span></div> {data.user_name}
<i className="fa fa-flag-o flagging" aria-hidden="true"></i>
{data.location_url}
</CardTitle>
<CardText className="questionTxt">{data.message}</CardText>
<div>
<Button className="replyBtn" disabled>No Discussion</Button>
<Button size="sm" color="link" className="disussionSpan" onClick={(i) => this.likeQuestion(i)}>{data.likes} Likes</Button>
</div>
</Card>
</Col>
</div>
);
});
return(
<div className="container">
<div className="row">
<div className="pageInfo">
<Dropdown className="inline" isOpen={this.state.dropdownOpen} toggle={this.toggle}>
<DropdownToggle caret>
Pages
</DropdownToggle>
<DropdownMenu>
{pgrow}
</DropdownMenu>
</Dropdown>
<p className="inline currPgNo">Page: {currentPage}</p>
</div>
<div className="col-md-8 col-md-offset-2">
{canvases}
</div>
</div>
</div>
)
}
React wouldn't auto-bind map inside render(), so you have to do it yourself in order to use this and call this.likeQuestion. Luckily, map provides a second argument to specify the context (this).
So just use...
this.state.questionItem.map(function(data,i) {
...
}, this)
instead of
this.state.questionItem.map(function(data,i) {
...
})
Option 2: Use arrow function in the map, such as map((data, i) => ...
Option 3: bind this to likeQuestion in the constructor of the component.
Try to define your helper functions using arrow functions
gotoPage = (index) => {
//some action. This is working
}
toggle = () => {
this.setState({
dropdownOpen: !this.state.dropdownOpen
});
}
likeQuestion = (e) => {
console.log('this is clicked');
//But this is not working
}
or
Bind these methods in constructor of your React component. e.g
this.likeQuestion = this.likeQuestion.bind(this);
// Needs to be done for all the helper methods.
So that you access the class level this context.
E.g a minimal setup
class Question extends React.Component {
constructor(props) {
super(props);
this.state = {
likes:10
};
}
likeQuestion = (e) => {
console.log('this is clicked');
//But this is not working
}
render() {
return ( < div >
< button size = "sm"
color = "link"
className = "disussionSpan"
onClick = {
(i) => this.likeQuestion(i)
} > {
this.state.likes
}
Likes < /button>
< /div >
);
}
};
ReactDOM.render( < Question / > , document.querySelector('#test'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="test">
</div>

react change the class of list item on click

I have a react element like this:
import React, { PropTypes, Component } from 'react'
class AlbumList extends Component {
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick() {
if(this.state.active){
this.setState({'active': false,'class': 'album'})
}else{
this.setState({'active': true,'class': 'active'})
}
}
render() {
var album_list
const {user} = this.props
if(user.data){
list = user.data.filter(album => album.photos).map((album => {
return <div className={"col-sm-3"} key={album.id}>
<div className={this.state.class} key={album.id} onClick={this.handleClick.bind(this)}>
<div className={"panel-heading"}>{ album.name }</div>
<div className={"panel-body"}>
<img className={"img-responsive"} src={album.photo.source} />
</div>
</div>
</div>
}))
}
return (
<div className={"container"}>
<div className="row">
{list}
</div>
</div>
)
}
}
export default AlbumList
Here map gives the list of filter data as I wanted. Here what I am doing changes the class of all the list element if I click on one.
I am getting the class name from this.state.class
How can I change the class of only element that i have clicked..
Thanks in advance ...
I have considered it once.So you have so many divs and you want to know which is clicked.My way to solve this problem is to give a param to the function handleClick and you can get the dom of the div while you click the div.Like this:
array.map(function(album,index){
return <div onClick={this.handleClick}/>
})
handleClick(e){
console.log(e.target);
e.target.className = 'active';
...
}
Then you have a param for this function.While you can use the e.target to get the dom of your div which is clicked.
There are some mistake into your code about the state.class.
class AlbumList extends Component {
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick(e) {
if(e.target.class === 'active'){
e.target.className = 'album'
}else{
e.target.className = 'active'
}
}
render() {
var album_list
const {user} = this.props
if(user.data){
list = user.data.filter(album => album.photos).map((album => {
return (
<div className={"col-sm-3"} key={album.id}>
<div className='active' key={album.id} onClick={this.handleClick.bind(this)}>
<div className={"panel-heading"}>{ album.name }</div>
<div className={"panel-body"}>
<img className={"img-responsive"} src={album.photo.source} />
</div>
</div>
</div>
)
}))
}
return (
<div className={"container"}>
<div className="row">
{list}
</div>
</div>
)
}
}
You can try this and tell me anything wrong.

Resources