the Cardlist component is not getting called upon changing state - reactjs

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

Related

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.

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.

React todo list, move from incompleted to completed list with information on the same li

I am a beginner in react and need some help.
I have created a todo list which takes, the text,edit and remove button, a select menu a input box and a radio button. The list renders fine for the "in completed list".I am trying to move the following when the radio button is ticked to the "completed list". the text,the option on the select menu for that li aswell as the text for the last box (ticket number). At the moment this takes the todo item text and the last value selected for the select menu (regardless of the li selected). To make it clear this is a task list, the select menu will be a list of names that this task is assigned to, with a "ticket number" for that task. I need all this information on another array to be visual on the "completed list".
The "incompleted list" which is stored in the items state, only has a name. on clicking "complete" I need to pass the details from the select menu. How do i do this? At the moment i can pass the last edited select menu, but i need it to be specific to the li.
code can be found on https://codesandbox.io/s/9lj4mmon1o
import React, { Component } from 'react';
import Form from './Components/Form'
import './App.css';
import List from './Components/List'
import Completed from './Components/Completed'
class App extends Component {
constructor(props){
super(props)
this.state= {
isComplete:false,
isEditing:false,
text:"",
items:[],
completed:[]
}
this.submit=this.submit.bind(this);
this.eventHandler=this.eventHandler.bind(this)
}
submit=(e)=>{
e.preventDefault();
this.setState({
items:[
{
name:this.state.text,
},
...this.state.items
],
text:""
})
}
remove=(index)=>{
this.setState({
items:this.state.items.filter((_,i) => i!==index)
})
}
eventHandler=(e)=>{
this.setState ({
text:e.target.value
})
}
handleNameEdits=(e)=>{
this.setState({
text:e.target.value
})
}
myoptions=(option,indexToChange,name)=>{
this.setState({
webmaster:option
})
}
edit=(items,indexToChange)=>{
this.setState({
isEditing:!this.state.isEditing,
items:this.state.items.map((item,index)=>{
if(index== indexToChange){
return {name:this.state.text}
}
}),
text:""
})
}
onChange=(indexToChange,listName,)=>{
this.setState({
isComplete:!this.state.isComplete,
items:this.state.items.filter((_,i) => i!==indexToChange),
completed:[
{
name:listName.name,
webmaster:this.state.webmaster,
ticketId:this.state.ticketId
},
...this.state.completed
]
})
}
ticketId=(e)=>{
this.setState({
ticketId:e.target.value
})
}
render() {
return (
<div className="App">
<header className="App-header">
<Form submit={this.submit} myValue={this.state.text} eventHandler={this.eventHandler}/>
<h4 class="incompleted-title"> Incompleted Tasks </h4>
{this.state.items.map && this.state.items.map((item,index)=>(
<List key={index}
name={item.name}
edit={()=>this.edit(item,index)}
changeComplete={()=>this.onChange(index,item)}
remove={()=>this.remove(index) }
handleNameEdits={this.handleNameEdits}
myoptions={(e =>this.myoptions(e.target.value,index,item))}
isEditing={this.state.isEditing}
editText={this.state.changedtext}
ticketId={this.ticketId}
/>
))}
</header>
<div className="completed">
<h2>completed</h2>
{this.state.completed.length > 0 && (<Completed completed={this.state.completed}/>)}
</div>
</div>
);
}
}
export default App;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Edit from './Edit';
class List extends Component {
constructor(props) {
super()
this.options = [
{name:'web1'},
{name:'web2'},
{name:'web3'},
{name:'web4'}
render() {
const {key} = this.props;
const x=this.options;
return (
<ul>
<li > <Edit handleNameEdits={this.props.handleNameEdits} isEditing={this.props.isEditing} editText={this.props.editText} /> <span className="li-text">{this.props.name}</span>
<div class="btn-group" role="group" aria-label="Basic example">
<button className='btn-style btn btn-info' onClick={this.props.edit}>{this.props.isEditing ? 'save':'edit'}</button>
<button className='btn btn-danger' onClick={this.props.remove}>Remove</button>
</div>
<select ref={this.props.Ref} class="select" onChange={this.props.myoptions}>{this.options.map(options => <option>{options.name}</option> )}
</select>
<label> Ticket Number </label><input type='text' onClick={this.props.ticketId} />
<label className='label'> Completed
</label><input type="checkbox" className="completedbox" onChange={this.props.changeComplete} />
</li>
</ul>
)
}
}
export default List;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Completed extends Component {
constructor(props){
super();
this.state = {
completed: props.completed
};
console.log(props);
}
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.completed !== prevProps.completed) {
this.setState({
completed: this.props.completed
});
}
}
render() {
return(
<ul>
{this.state.completed.map((list,index)=>(
<li className="list-group-item list-group-item-success" key={index}> `Task: {list.name} was completed by {list.webmaster} on ticket {list.ticketId}`</li>))
}
</ul>
)
}
}
// Completed.PropTypes={
// test:PropTypes.string.isRequired
export default Completed

React recursively call method on children

I'm making a collapsible list with React. So far it works but now I want to implement a button that expands/collapses everything. Therefore the button need to adjust the state of all elements. I'm not sure what's the best way to tackle this problem though. This is what I have:
import React, {Component} from 'react';
class CollapsibleList extends Component {
constructor(props) {
super(props);
this.state = {
collapsed: true
};
this.subLists = [];
this.papers = [];
if (this.props.subtitles) {
for (let subList of this.props.subtitles) {
this.subLists.push(
<CollapsibleList level={this.props.level + 1} subtitles={subList.subtitles} title={subList.title}/>
);
}
}
this.toggleCollapse = this.toggleCollapse.bind(this);
this.expandAll = this.expandAll.bind(this);
this.collapseAll = this.collapseAll.bind(this);
}
expandAll() {
this.setState({collapsed: false});
this.subLists.forEach(subList => subList.expandAll());
}
collapseAll() {
this.setState({collapsed: true});
this.subLists.forEach(subList => subList.collapseAll());
}
toggleCollapse() {
this.setState(prevState => {
return {collapsed: !prevState.collapsed};
});
}
render() {
return (this.state.collapsed ?
<li className={'collapsibleListItem'}>
<div onClick={this.toggleCollapse}>
{this.props.title}
</div>
<img title={'Expand all'} className={'icon'} alt={'Expand all'} src={require('../expand_all.png')} onClick={this.expandAll}/>
<img title={'Collapse all'} className={'icon'} alt={'Collapse all'} src={require('../collapse_all.png')} onClick={this.collapseAll}/>
</li> :
<li className={'collapsibleListItem'}>
<div onClick={this.toggleCollapse}>
{this.props.title}
</div>
<img title={'Expand all'} className={'icon'} alt={'Expand all'} src={require('../expand_all.png')} onClick={this.expandAll}/>
<img title={'Collapse all'} className={'icon'} alt={'Collapse all'} src={require('../collapse_all.png')} onClick={this.collapseAll}/>
<ul className={'collapsibleList'}>
{this.subLists}
</ul>
</li>
);
}
}
export default CollapsibleList;
Unfortunately, that doesn't seem to work though.
I can't understand what you are trying to do in your code but you should have 2 different components; one for the list and one for the list item. It should be something like this:
// Parent component
import React from 'react';
import ListItem from './ListItem';
class List extends React.Component {
constructor() {
super();
this.state = {
collapsed: false
}
}
render() {
const data = ['abc', 'def', 'ghi']; // whatever you want to have
return(
<div>
<button onClick={() => this.setState({collapsed: !this.state.collapsed})}>
Collapse
</button>
<ul>
{
this.state.collapsed &&
data.map((val, key) => {
return(
<li>
<ListItem value={val} key={key} />
</li>
)
})
}
</ul>
</div>
)
}
}
And this is the child component
// child component
import React from 'react';
class ListItem extends React.Component {
constructor() {
super();
}
render() {
return(
<div>
{/*// render anything you want*/}
<p>{this.props.value}</p>
</div>
)
}
}
export default ListItem;
This code is just to give you an insight.

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