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

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.

Related

Handle multiple child component in React

I've tried to look everywhere and couldn't find anything related to my use case, probably I'm looking for the wrong terms.
I have a situation where I have a bar with 3 icons, I'm looking for set one icon "active" by changing the class of it.
The icon is a custom component which have the following code
export default class Icon extends Component {
state = {
selected : false,
}
setSelected = () => {
this.setState({
selected : true
})
}
setUnselected = () => {
this.setState({
selected : false
})
}
render() {
var classStatus = '';
if(this.state.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return <div className={classStatus} onClick={this.props.onClick}><FontAwesomeIcon icon={this.props.icon} /></div>
}
}
In my parent component I have the following code
export default class MainPage extends Component {
handleClick(element) {
console.log(element);
alert("Hello!");
}
render() {
return (
<div className="wrapper">
<div className="page-header">
<span className="menu-voice">File</span>
<span className="menu-voice">Modifica</span>
<span className="menu-voice">Selezione</span>
</div>
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon icon={faFileCode} onClick={() => this.handleClick(this)} />
<Icon icon={faCodeBranch} onClick={() => this.handleClick(this)} />
<Icon icon={faMagnifyingGlass} onClick={() => this.handleClick(this)} />
</div>
</span>
<span className="files-section">Files</span>
<span className="editor-section"></span>
</div>
<div className="page-footer">
Footer
</div>
</div>
);
}
}
What I'm trying to achieve is that when one of the Icon child component get clicked it will set the selected state to true manage by the parent component, in the same time while one of them is true I would like that the parent would set to false the other twos.
I've tried to use the useRef function but it doesn't look as a best practise.
Which is the correct way to do it? Sending also this to the handleClick function it just return the MainPage class instead of the child. Any suggestion at least where I should watch?
Thanks in advance
I suggest not storing the state in the icon, since it doesn't know what else you're using it for. Simply have the icon component take it's 'selected' status from props. e.g.
export default class Icon extends Component {
render() {
var classStatus = '';
if(this.props.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return (
<div className={classStatus} onClick={this.props.onClick}>.
<FontAwesomeIcon icon={this.props.icon} />
</div>
);
}
};
Then you can just manage the state in the parent where it should be:
export default class MainPage extends Component {
constructor(props) {
super(props);
this.state = { selectedOption : '' };
}
handleSelectOption(newValue) {
this.setState({ selectedOption: newValue });
}
isSelected(value) {
return value === this.state.selectedOption;
}
render() {
return (
<div className="wrapper">
{ /* etc... */ }
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon
icon={faFileCode}
onClick={() => this.handleSelectOption("File")}
selected={isSelected("File")}
/>
<Icon
icon={faCodeBranch}
onClick={() => this.handleSelectOption("Modifica")}
selected={isSelected("Modifica")}
/>
{ /* etc... */ }
</div>
</span>
</div>
{ /* etc... */ }
</div>
);
}
};
You should define a constructor in your class component:
constructor(props) {
super(props);
this.state = { selected : false };
}
You also have to call a function which modify the state when you click on the Icon. onClick={this.props.onClick} doesn't change the state

Array has duplicated records when using checkboxes to populate an array using React

I have trouble with simple task of adding elements selected in checkboxes to an array in component state. It seems like the push method for state.toppings (Editor.js) is invoked twice for each checkbox click, even though console.log shows that updateFormValueCheck method is invoked once per click. Can anyone help?
This is App.js
import React, { Component } from "react";
import { Editor } from "./Editor";
import { Display } from "./Display";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
formData: {}
}
}
submitData = (newData) => {
console.log("newData", newData)
this.setState({ formData: newData });
}
render() {
return <div className="container-fluid">
<div className="row p-2">
<div className="col-6">
<Editor submit={this.submitData} />
</div>
<div className="col-6">
<Display data={this.state.formData} />
</div>
</div>
</div>
}
}
This is Editor.js
import React, { Component } from "react";
export class Editor extends Component {
constructor(props) {
super(props);
this.state = {
toppings: ["Strawberries"]
}
this.toppings = ["Sprinkles", "Fudge Sauce",
"Strawberries", "Maple Syrup"]
}
updateFormValueCheck = (event) => {
event.persist();
this.setState(state => {
if (event.target.checked) {
state.toppings.push(event.target.name);
} else {
let index = state.toppings.indexOf(event.target.name);
state.toppings.splice(index, 1);
}
}, () => this.props.submit(this.state));
}
render() {
return <div className="h5 bg-info text-white p-2">
<div className="form-group">
<label>Ice Cream Toppings</label>
{this.toppings.map(top =>
<div className="form-check" key={top}>
<input className="form-check-input"
type="checkbox" name={top}
value={this.state[top]}
checked={this.state.toppings.indexOf(top) > -1}
onChange={this.updateFormValueCheck} />
<label className="form-check-label">{top}</label>
</div>
)}
</div>
</div>
}
}
This is Display.js
import React, { Component } from "react";
export class Display extends Component {
formatValue = (data) => Array.isArray(data)
? data.join(", ") : data.toString();
render() {
let keys = Object.keys(this.props.data);
if (keys.length === 0) {
return <div className="h5 bg-secondary p-2 text-white">
No Data
</div>
} else {
return <div className="container-fluid bg-secondary p-2">
{keys.map(key =>
<div key={key} className="row h5 text-white">
<div className="col">{key}:</div>
<div className="col">
{this.formatValue(this.props.data[key])}
</div>
</div>
)}
</div>
}
}
}
The output is:
You cannot directly mutate this.state, it can only be done using this.setState. For more info. refer this: Why can't I directly modify a component's state, really?
Therefore, you need to update your Editor component as follows.
componentDidMount is used to display the initial state during the initial rendering. Then componentDidUpdate is used to render the state changes through display component whenever it's updated.
import React, { Component } from "react";
export class Editor extends Component {
constructor(props) {
super(props);
this.state = {
toppings: ["Strawberries"],
};
this.toppings = ["Sprinkles", "Fudge Sauce", "Strawberries", "Maple Syrup"];
}
updateFormValueCheck = (event) => {
event.persist();
let data;
if (event.target.checked) {
data = [...this.state.toppings, event.target.name];
} else {
const index = this.state.toppings.indexOf(event.target.name);
const temp = [...this.state.toppings];
temp.splice(index, 1);
data = temp;
}
this.setState({
toppings: data,
});
};
componentDidMount() {
this.props.submit(this.state.toppings);
}
componentDidUpdate(prevPros, prevState) {
if (prevState.toppings !== this.state.toppings) {
this.props.submit(this.state.toppings);
}
}
render() {
console.log(this.state);
return (
<div className="h5 bg-info text-white p-2">
<div className="form-group">
<label>Ice Cream Toppings</label>
{this.toppings.map((top) => (
<div className="form-check" key={top}>
<input
className="form-check-input"
type="checkbox"
name={top}
value={this.state[top]}
checked={this.state.toppings.indexOf(top) > -1}
onChange={this.updateFormValueCheck}
/>
<label className="form-check-label">{top}</label>
</div>
))}
</div>
</div>
);
}
}
Hope this would be helpful to solve your issue.

the Cardlist component is not getting called upon changing state

I'm new to react js and is having some problem in my code. I have a Cardlist component which is returning my contact list layout when i pass my contactlist into it. I added a searchbox to search for a particular contact. The list updated upon entering some text in search box (I logged it out in console and it is working finely). The render and return methods are also working well upon changing search text ( i.e they are getting called everytime after changing state, I logged them out) but the Cardlist component is not getting called again. It still shows the same old list. The console.log inside this cardlist component is also not logging anything which simply implies it is not called again.
Here is the main component (not whole code shown, only the necessary part) :
constructor(props){
super(props);
this.state = {
friendslist: this.props.data.friendslist,
searchfield:''
}
}
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value });
}
render(){
var filterfriendslist = this.state.friendslist.filter(friendslistitem => {
return friendslistitem.name.toLowerCase().includes(this.state.searchfield.toLowerCase())
});
{console.log( filterfriendslist )}
return(
<div>
<input id="name" onChange={this.onSearchChange} className="input-reset ba b--black-20 pa2 mv2 db w-100 bg-near-white" type="text" placeholder='Search' />
<Scroll>
<div>
{console.log( filterfriendslist )}
<Cardlist friendlist={ filterfriendslist } loadChattingUser={ this.loadChattingUser } />
</div>
</Scroll>
</div>
);
}
}
export default Contacts;
Here is the Carlist component :
import React from 'react';
import Card from './Card';
class Cardlist extends React.Component {
constructor(props){
super(props);
console.log("Clicked");
}
cardComponent = this.props.friendlist.map((user, i) => {
return <Card key={i} id={this.props.friendlist[i].id} name = {this.props.friendlist[i].name} imageURL={this.props.friendlist[i].imageurl} email={this.props.friendlist[i].email} msgDatabase={ this.props.friendlist[i].msgdata } loadChattingUser={ this.props.loadChattingUser } />
})
render(){
return (
<div>
{this.cardComponent}
</div>
);
}
}
export default Cardlist;
Here is the card component :
import React from 'react';
class Card extends React.Component {
constructor(props){
super(props);
this.state = {
name: this.props.name,
imageURL: this.props.imageURL,
email: this.props.email,
msgDatabase: this.props.msgDatabase
}
}
fillChat = () => {
this.props.loadChattingUser(this.state);
}
render(){
return (
<div className="dt w-100 bb b--black-05 pb2 mt2 pa2 bg-near-white pointer" onClick={ this.fillChat }>
<div className="dtc w2 w3-ns v-mid">
<img alt="Profile" src={this.props.imageURL} className="ba b--black-10 db br-100 w2 w3-ns h2 h3-ns"/>
</div>
<div className="dtc v-mid pl3">
<h1 className="f6 f5-ns fw6 lh-title black mv0">{this.props.name}</h1>
<h2 className="f6 fw4 mt0 mb0 black-60">{this.props.email}</h2>
</div>
<div className="dtc v-mid">
<form className="w-100 tr">
<button className="f6 button-reset bg-white ba b--black-10 dim pointer pv1 black-60" type="submit">+ Follow</button>
</form>
</div>
</div>
);
}
}
export default Card;
I want the Cardlist layout to change according to input in the search but it remain same as the initial list
The issue on your implementation of Cardlist. In your implementation cardComponent field is precomputed field. It supposed to be like this:
class Cardlist extends React.Component {
constructor(props) {
super(props);
console.log("Clicked");
}
render() {
const cardComponent = this.props.friendlist.map((user, i) => {
return (
<Card
key={i}
id={this.props.friendlist[i].id}
name={this.props.friendlist[i].name}
imageURL={this.props.friendlist[i].imageurl}
email={this.props.friendlist[i].email}
msgDatabase={this.props.friendlist[i].msgdata}
loadChattingUser={this.props.loadChattingUser}
/>
);
});
return <div>{cardComponent}</div>;
}
}
Here is codesandbox link: https://codesandbox.io/s/chatter-zdljq

how to pass props to a component via onClick event?

Here I'm iterating through the list and displaying clickable {expense.createdAt}. When I click on the {expense.createdAt} I want to show its <ExpenseDetails /> on the same page, so I need to pass the props. How do I do that with onClick?
My code below which doesn't display anything.
class ExpenseList extends Component {
render () {
const {expenses} = this.props;
const list = expenses.expenseList.map(expense =>
<Segment clearing key={expense.uid} >
<a href="" onClick={() => {this.passProps(expense)}}>
{expense.createdAt}
</a>
</Segment>
)
const details = function passProps(expense){
return (
<div>
<ExpenseDetails expense={expense}/>
</div>
)
}
return (
<div>
<Grid celled='internally'>
<Grid.Row>
<Grid.Column width={5}>
<div>
<h1>Your Expense List:</h1>
<Segment.Group raised>
{list}
</Segment.Group>
</div>
</Grid.Column>
<Grid.Column width={11}>
<Segment>
{details}
</Segment>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
)
}
}
When I click on{expense.createdAt} the page refreshes. This might be an issue. How can I prevent that?
console.log(this.props.expenses):
Something like this should work. The important concept is that you need to have a parent to both of your subcomponents passing data. In this case, Expenses is the parent. In the parent, you create a function, showItemDetails that you pass down to ExpenseList. That is your clickhandler, which is used to pass back up the index location of the data you want to render. That showItemDetails then uses this.setState to give ItemDetails's the props you want.
import React, {Component} from 'react'
export default class Expenses extends Component {
constructor(props) {
super(props)
this.state = {
expenseItems: []
}
}
showItemDetails(idx) {
const {expenseItems} = this.props.expenses.expenseList[idx]
this.setState({expenseItems})
}
render() {
const createdDates = this.props.expenses.expenseList.map(({createdAt}) => {
return createdAt
})
const expenseListProps = {
showItemDetails: (idx) => this.showItemDetails(idx),
createdDates
}
const itemDetailsProps = {expenseItems: this.state.expenseItems}
return (
<div>
<ExpenseList {...expenseListProps}/>
<ItemDetails {...itemDetailsProps}/>
</div>
)
}
}
class ExpenseList extends Component{
render() {
const expenseList = this.props.createdDates.map((date, idx) => (
<div key={idx}>
<button onClick={() => this.props.showItemDetails(idx)}>
{date}
</button>
</div>
))
return (
<div>
{expenseList}
</div>
)
}
}
class ItemDetails extends Component {
render() {
debugger;
const items = this.props.expenseItems.map(({uid, date, desc, amount}) => (
<div>
{uid}
{date}
{desc}
{amount}
</div>
))
return (
<div>
{items}
</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