How to pass function with argument in classname field in React? - reactjs

I have following component in my React Project. What I want to do is to Add a className attribute to the <li> element. Set it equal to the return value of the getSortByClass() method and pass in sortByOptionValue as the argument.
import React from 'react';
import './SearchBar.css';
const sortByOptions = {
'Best Match': 'best_match',
'Highest Rated': 'rating',
'Most Reviewed': 'review_count'
}
function getSortByClass(sortByOption){
if (this.state.sortBy === sortByOption) {
return 'active';
}
else {
return '';
}
}
function handleSortByChange(sortByOption){
this.setState({
sortBy: sortByOption
});
}
export class SearchBar extends React.Component{
renderSortByOptions(){
return Object.keys(sortByOptions).map(sortByOption => {
let sortByOptionValue = sortByOptions[sortByOption];
return <li className={getSortByClass(sortByOptionValue)} key={sortByOptionValue}> {sortByOption} </li>;
});
}
constructor(props) {
super(props);
this.state = {
term: '',
location: '',
sortBy: 'best_match',
};
}
render(){
return (
<div className="SearchBar">
<div className="SearchBar-sort-options">
<ul>
{this.renderSortByOptions()}
</ul>
</div>
<div className="SearchBar-fields">
<input placeholder="Search Businesses" />
<input placeholder="Where?" />
</div>
<div className="SearchBar-submit">
<a>Lets Go</a>
</div>
</div>
);
}
}
export default SearchBar;
I am getting error: TypeError: Cannot read property 'state' of undefined after setting the classname field.

....
function getSortByClass(sortBy, sortByOption){
if (sortBy === sortByOption) {
return 'active';
}
else {
return '';
}
}
export class SearchBar extends React.Component{
handleSortByChange = (sortByOption) => this.setState({ sortBy: sortByOption});
renderSortByOptions(){
const that = this;
return Object.keys(sortByOptions).map(sortByOption => {
let sortByOptionValue = sortByOptions[sortByOption];
return <li className={getSortByClass(that.state.sortBy, sortByOptionValue)} key={sortByOptionValue}> {sortByOption} </li>;
});
}
....
Pass your state to the getSortByClass method as it doesn't have access to this because it is written outside the class. Also write handleSortByChange inside your class as it is accessing setState from this.
Also you can beautify the code as :
renderSortByOptions(){
const that = this;
return Object.keys(sortByOptions).map(sortByOption => {
let sortByOptionValue = sortByOptions[sortByOption];
return (
<li className={that.state.sortBy === sortOption ? 'active' : ''}
key={sortByOptionValue}
>
{sortByOption}
</li>
);
});
}

Related

Toggle class only on one element, react js

I`m changing class after clicking and it works.
The problem is that, classes change simultaneously in both elements and not in each one separately. Maybe someone could look what I'm doing wrong. Any help will be useful.
import React, { Component } from "react";
class PageContentSupportFaq extends Component {
constructor(props) {
super(props);
this.state = {
isExpanded: false
};
}
handleToggle(e) {
this.setState({
isExpanded: !this.state.isExpanded
});
}
render() {
const { isExpanded } = this.state;
return (
<div className="section__support--faq section__full--gray position-relative">
<div className="container section__faq">
<p className="p--thin text-left">FAQ</p>
<h2 className="section__faq--title overflow-hidden pb-4">Title</h2>
<p className="mb-5">Subtitle</p>
<div className="faq__columns">
<div
onClick={e => this.handleToggle(e)}
className={isExpanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>First</strong>
</p>
</div>
<div
onClick={e => this.handleToggle(e)}
className={isExpanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>Second</strong>
</p>
</div>
</div>
</div>
</div>
);
}
}
export default PageContentSupportFaq;
Every element must have its seperate expanded value. So we need an array in state.
And here is the code:
import React, { Component } from "react";
class PageContentSupportFaq extends Component {
state = {
items: [
{ id: 1, name: "First", expanded: false },
{ id: 2, name: "Second", expanded: true },
{ id: 3, name: "Third", expanded: false }
]
};
handleToggle = id => {
const updatedItems = this.state.items.map(item => {
if (item.id === id) {
return {
...item,
expanded: !item.expanded
};
} else {
return item;
}
});
this.setState({
items: updatedItems
});
};
render() {
return this.state.items.map(el => (
<div
key={el.id}
onClick={() => this.handleToggle(el.id)}
className={el.expanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>{el.name}</strong>
<span> {el.expanded.toString()}</span>
</p>
</div>
));
}
}
export default PageContentSupportFaq;
You can get two state one state for first and another for a second and handle using two function like this
import React, { Component } from 'react';
class PageContentSupportFaq extends Component {
constructor(props) {
super(props)
this.state = {
isExpanded: false,
isExpanded2:false,
}
}
handleToggle(e){
this.setState({
isExpanded: !this.state.isExpanded
})
}
handleToggle2(e){
this.setState({
isExpanded2: !this.state.isExpanded2
})
}
render() {
const {isExpanded,isExpanded2} = this.state;
return (
<div className="section__support--faq section__full--gray position-relative">
<div className="container section__faq">
<p className="p--thin text-left">FAQ</p>
<h2 className="section__faq--title overflow-hidden pb-4">Title</h2>
<p className="mb-5">Subtitle</p>
<div className="faq__columns">
<div onClick={(e) => this.handleToggle(e)} className={isExpanded ? "active" : "dummy-class"}>
<p className="mb-0"><strong>First</strong></p>
</div>
<div onClick={(e) => this.handleToggle2(e)} className={isExpanded2 ? "active" : "dummy-class"}>
<p className="mb-0"><strong>Second</strong></p>
</div>
</div>
</div>
</div>
);
}
}
export default PageContentSupportFaq;
You'll need to track toggled classes in array, that way it will support arbitrary number of components:
// Save elements data into array for easier rendering
const elements = [{ id: 1, name: "First" }, { id: 2, name: "Second" }];
class PageContentSupportFaq extends Component {
constructor(props) {
super(props);
this.state = {
expanded: []
};
}
handleToggle(id) {
this.setState(state => {
if (state.isExpanded.includes(id)) {
return state.isExpanded.filter(elId => elId !== id);
}
return [...state.expanded, id];
});
}
render() {
return elements.map(el => (
<div
key={el.id}
onClick={() => this.handleToggle(el.id)}
className={this.isExpanded(el.id) ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>{el.name}</strong>
</p>
</div>
));
}
}

Page not re-rendering after button click

I have a react class component which produces a product showcase using react-masonry. I now want to add filtering functionality with filter options displayed as buttons and on each click, the page elements should be filtered and the masonry display rerendered.
The filter functions work fine but I was not able to get the masonry to rerender.
import React from 'react'
import PropTypes from 'prop-types'
import Masonry from 'react-masonry-component'
import Product from 'components/Product'
const masonryOptions = {
transitionDuration: 0
};
class Gallery extends React.Component {
constructor (props){
super(props);
this.state ={
filter:props.filter,
elements:props.elements,
type:props.type,
}
this.handleFilterClick=this.handleFilterClick.bind(this)
}
handleFilterClick(filter){
console.log(filter)
this.setState({filter:filter})
console.log(this.state.filter)
}
filterProducts (elements,filter){
const filteredArray=elements.filter(function(el){
for (let i in el.tags) {
if (el.tags[i].slug===filter){
return true;
}
}
return false
})
return filteredArray;
}
renderGallery(){
const type=this.state.type
const elements=this.state.elements
var filter=this.state.filter
const filteredElements = elements
if (filter !=="*"){
const filteredElements = this.filterProducts(elements,filter)
}
const childElements = filteredElements.map(function(element,key){
if (element.mainPhoto!=null && element.isDogFood==type){
return (
<Product key={key} element={element}/>
);} else{
return null;
}
});
return (
<Masonry
className={'my-gallery-class'} // default ''
options={masonryOptions} // default {}
disableImagesLoaded={false} // default false
updateOnEachImageLoad={false} // default false and works only if disableImagesLoaded is false
>
<div className="col-md-12">
<ul className="filter text-center text-inline">
<li>
<button data-filter="*" className="selected">Tüm Ürünler</button>
</li>
<li>
<button filter="nograin">Tahılsız</button>
</li>
<li>
<button filter="seafood">Deniz Mahsülleri</button>
</li>
<li>
<button filter="poultry">Beyaz Et</button>
</li>
<li>
<button filter="redmeat">Kırmızı Et</button>
</li>
<li>
<button filter="pate" onClick={() =>{this.handleFilterClick("pate")}}>Püre</button>
</li>
</ul>
</div>
{childElements}
</Masonry>
);
}
render() {
return (
this.renderGallery()
)
}
}
Gallery.propTypes={
type: PropTypes.bool
}
export default Gallery
Use getDerivedStateFromProps instead of using props in constructor
import React from 'react'
import PropTypes from 'prop-types'
import Masonry from 'react-masonry-component'
import Product from 'components/Product'
const masonryOptions = {
transitionDuration: 0
};
class Gallery extends React.Component {
constructor (props){
super(props);
this.state = {
// Don't assign direct value in constructor as constructor function
// calls only at initialization
filter:props.filter,
elements:props.elements,
type:props.type,
}
this.handleFilterClick=this.handleFilterClick.bind(this)
}
getDerivedStateFromProps (props, prevState) {
if(//Put a valid condition) {
return {
filter: props.filter,
elements: props.elements,
type: props.type
}
}
return {}
}
handleFilterClick(filter){
console.log(filter)
this.setState({filter:filter})
console.log(this.state.filter)
}
filterProducts (elements,filter){
const filteredArray=elements.filter(function(el){
for (let i in el.tags) {
if (el.tags[i].slug===filter){
return true;
}
}
return false
})
return filteredArray;
}
renderGallery(){
const type=this.state.type
const elements=this.state.elements
var filter=this.state.filter
const filteredElements = elements
if (filter !=="*"){
const filteredElements = this.filterProducts(elements,filter)
}
const childElements = filteredElements.map(function(element,key){
if (element.mainPhoto!=null && element.isDogFood==type){
return (
<Product key={key} element={element}/>
);} else {
return null;
}
});
return (
<Masonry
className={'my-gallery-class'} // default ''
options={masonryOptions} // default {}
disableImagesLoaded={false} // default false
updateOnEachImageLoad={false} // default false and works only if disableImagesLoaded is false
>
<div className="col-md-12">
<ul className="filter text-center text-inline">
<li>
<button data-filter="*" className="selected">Tüm Ürünler</button>
</li>
<li>
<button filter="nograin">Tahılsız</button>
</li>
<li>
<button filter="seafood">Deniz Mahsülleri</button>
</li>
<li>
<button filter="poultry">Beyaz Et</button>
</li>
<li>
<button filter="redmeat">Kırmızı Et</button>
</li>
<li>
<button filter="pate" onClick={() =>{this.handleFilterClick("pate")}}>Püre</button>
</li>
</ul>
</div>
{childElements}
</Masonry>
);
}
render() {
return (
this.renderGallery()
)
}
}
Gallery.propTypes = {
type: PropTypes.bool
}
export default Gallery

How to add index value in the list using React.js?

I need to add index value in my data list using React.js. My code is below.
Itemlist.js:
import React, { Component } from "react";
class TodoItems extends Component {
constructor(props, context) {
super(props, context);
this.createTasks = this.createTasks.bind(this);
}
edit(key){
this.props.edit(key);
}
delete(key){
this.props.delete(key);
}
createTasks(item) {
return <li key={item._id}>{item.name}<a href="#" className="button bg_green" onClick={()=>this.edit(item._id)}>Edit</a><a href="#" className="button bg_red" onClick={()=>this.delete(item._id)}>Delete</a></li>
}
render() {
var todoEntries = this.props.entries;
var listItems = todoEntries.map(this.createTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
};
export default TodoItems;
Todolist.js:
import React, { Component } from "react";
import TodoItems from "./TodoItems";
import "./TodoList.css";
import ItemService from './ItemService';
import axios from 'axios';
class TodoList extends Component {
constructor(props, context){
super(props, context);
this.state={
items:[]
}
this.addItem=this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.editItem = this.editItem.bind(this);
this.ItemService = new ItemService();
}
componentDidMount(){
axios.get('http://localhost:8888/item')
.then(response => {
this.setState({ items: response.data });
})
.catch(function (error) {
console.log(error);
})
}
addItem(e){
e.preventDefault();
if(this.state.editKey){
this.saveEditedText();
return;
}
var itemArray = this.state.items;
if (this.inputElement.value !== '') {
itemArray.unshift({
text:this.inputElement.value,
key:Date.now()
})
this.setState({
items:itemArray
})
//console.log('items',this.state);
this.ItemService.sendData(this.inputElement.value);
this.divRef.insertAdjacentHTML("beforeend", '<p className="textcolor">'+this.inputElement.value+' has added successfully</p>');
this.inputElement.value='';
setTimeout( () => {
this.divRef.querySelector(':last-child').remove();
window.location.reload();
}, 3000);
}
}
saveEditedText(){
let value = this.inputElement.value;
this.setState(prevState => ({
items: prevState.items.map(el => {
if(el.key == prevState.editKey)
return Object.assign({}, el, {text: value});
return el;
}),
editKey: ''
}));
this.divRef.insertAdjacentHTML("beforeend", '<p className="textcolor">'+this.inputElement.value+' has updated successfully</p>');
this.inputElement.value='';
setTimeout( () => {
this.divRef.querySelector(':last-child').remove();
}, 3000);
}
render() {
return (
<div className="todoListMain">
<div className="header" id="parentDiv">
<div className="pageHeading" dangerouslySetInnerHTML={{ __html: "Todo Demo Application" }}></div>
<div className="wrapper">
<div ref={divEl => {
this.divRef = divEl;
}}></div>
<form onSubmit={this.addItem}>
<input ref={(a)=>this.inputElement=a} placeholder="enter task">
</input>
<button type="submit">{this.state.editKey? "Update": "Add"}</button>
</form>
<TodoItems entries={this.state.items} delete={this.deleteItem} edit={this.editItem}/>
</div>
</div>
</div>
);
}
}
export default TodoList;
Here after adding the data into db, the added data are shown in the list. Here I need to display the index value for each row means 1 - item1 like this.
You can do this by :
class TodoItems extends Component {
constructor(props, context) {
super(props, context);
this.createTasks = this.createTasks.bind(this);
}
edit(key){
this.props.edit(key);
}
delete(key){
this.props.delete(key);
}
render() {
var todoEntries = this.props.entries;
return (
<ul className="theList">
{todoEntries.map(this.createTasks, this)}
</ul>
);
}
createTasks(item, index) {
return (
<li key={item._id}>
{index} - {item.name}
<a href="#" className="button bg_green" onClick={()=>this.edit(item._id)}>Edit</a><a href="#" className="button bg_red" onClick={()=>this.delete(item._id)}>Delete</a>
</li>
)
}
};
If you mean to display index in ItemList component map has an overload which has paramater index that represents current index of element in array being processed.
See more in docs
So make createTasks(item, index){ } and then you will have access to index of the element.

How to access return value of a method in classname field in ReactJS?

I have following function outside the component
function getSortByClass(sortByOption){
if (this.state.sortBy === sortByOption) {
return 'active';
}
else {
return '';
}
}
I have a component which is returning following function.
return Object.keys(sortByOptions).map(sortByOption => {
let sortByOptionValue = sortByOptions[sortByOption];
return <li className={} key={sortByOptionValue}
onClick={ handleSortByChange.bind(this, sortByOptionValue)}> {sortByOption} </li>;
});
I want to know how I can access the return value of the getSortByClass function in <li> tag classname's value.
Here's the full component code.
import React from 'react';
import './SearchBar.css';
const sortByOptions = {
'Best Match': 'best_match',
'Highest Rated': 'rating',
'Most Reviewed': 'review_count'
}
function getSortByClass(sortByOption){
if (this.state.sortBy === sortByOption) {
return 'active';
}
else {
return '';
}
}
export class SearchBar extends React.Component{
renderSortByOptions(){
const that = this;
function handleSortByChange(sortByOption){
this.setState({ sortBy: sortByOption});
}
return Object.keys(sortByOptions).map(sortByOption => {
let sortByOptionValue = sortByOptions[sortByOption];
return <li className={} key={sortByOptionValue} onClick={ handleSortByChange.bind(this, sortByOptionValue)}> {sortByOption} </li>;
});
}
constructor(props) {
super(props);
this.state = {
term: '',
location: '',
sortBy: 'best_match',
};
}
render(){
return (
<div className="SearchBar">
<div className="SearchBar-sort-options">
<ul>
{this.renderSortByOptions()}
</ul>
</div>
<div className="SearchBar-fields">
<input placeholder="Search Businesses" />
<input placeholder="Where?" />
</div>
<div className="SearchBar-submit">
<a>Lets Go</a>
</div>
</div>
);
}
}
export default SearchBar;
I don't know if what you are trying to do is possible but it's definately not a good idea. Just pass the part of state you are using as a parameter to the function.
function getSortByClass(sortBy, sortByOption){
if (sortBy === sortByOption) {
return 'active';
}
else {
return '';
}
}
then change your call to
return <li className={} key={sortByOptionValue} onClick={ handleSortByChange(this.state.sortBy, sortByOptionValue)}> {sortByOption} </li>;
If the function doesn't have to be out of scope just put it in the component like #mersocarlin said.
Edit using setState:
getSortByClass(sortByOption){
if (this.state.sortBy === sortByOption) {
this.setState({styleClass: 'active' });
}
else {
this.setState({styleClass: '' });
}
}

Unable to invoke props function passed to children in a loop reactjs

I am new to react. I am just trying to create a comment box and comment board which contain multiple comments.
Each comments have one inputbox, button(save,edit) and button(remove). I have passed function made in board named updateComment to Component Comment as props.
Now When I am trying to execute save of child function in which I have called parent function updateComment using this.props.updateComment
it is giving me error can't read property of undefined.
I have searched for similar question on stackoverflow but I am unable to solved this proplem.
My app.js code is as below.
import React from 'react';
import { Home } from './home.jsx';
class App extends React.Component {
render() {
return (
<div>
<Header/>
<Board />
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>Header</h1>
</div>
);
}
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
comments:[
"My name is brijesh",
"My name is santosh",
"My name is manoj"
]}
};
removeComment(i) {
console.log("going to remove element i",i);
var arr = this.state.comments;
arr.splice(i,1);
this.setState({comments:arr});
};
updateComment(newComment, i) {
var arr = this.state.comments;
arr[i] = newComment;
this.setState({comments:arr});
};
render() {
return (
<div className="board">
{
this.state.comments.map(function(text,i) {
return (
<Comment key ={i} index = {i}
updateComment={() => {this.updateComment}}
removeComment={() => {this.removeComment}}>
{text}
</Comment>
)
})
}
</div>
)
}
}
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = {
edit: false
};
};
edit(){
this.setState({edit:true});
console.log("you clickced on edit0");
};
save(){
this.setState({edit:false});
var newText = this.refs.newText.value;
this.props.updateComment(newText, this.props.index);
console.log("you clickced on edit0",newText);
};
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
if(this.state.edit) {
return (
<div>
<div className="comment">
<input type="text" ref="newText" defaultValue={this.props.children} onChange={ this.handleChange.bind(this) } />
<button onClick={this.save.bind(this)}>Save</button>
</div>
</div>
)
}
else {
return (
<div>
<div className="comment">
<div>{ this.props.children }</div>
<button onClick={this.edit.bind(this)}>Edit</button>
</div>
</div>
)
}
}
}
export default App
And my main.js looks like this.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(
( < App / > ), document.getElementById('app'));
I have also created fiddle also.
https://jsfiddle.net/aubrijesh/k3h2pcnj/#&togetherjs=uEI7TFnJD1
I believe that DOMZE is on the right track but you should also bind the function in the map statement. In my opinion arrow functions makes it much easier to keep track of what this refers to.
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
comments:[
"My name is brijesh",
"My name is santosh",
"My name is manoj"
]}
};
removeCommment(i) {
console.log("going to remove element i",i);
var arr = this.state.comments;
arr.splice(i,1);
this.setState({comments:arr});
};
updateComment(newComment, i) {
var arr = this.state.comments;
console.log("new Comment");
arr[i] = newComment;
this.setState({comments:arr});
};
render() {
return (
<div className="board">
{
this.state.comments.map((text,i) => {
return (
<Comment key ={i} index = {i}
updateComment={() => {this.updateComment}}
removeComment={() => {this.removeComment}}>
{text}
</Comment>
)
})
}
</div>
)
}
}
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = {
edit: false
};
};
edit(){
this.setState({edit:true});
console.log("you clickced on edit0");
};
save(){
this.setState({edit:false});
var newText = this.refs.newText.value;
this.props.updateComment(newText, this.props.index);
console.log("you clickced on edit0",newText);
};
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
if(this.state.edit) {
return (
<div>
<div className="comment">
<input type="text" ref="newText" defaultValue={this.props.children} onChange={ this.handleChange} />
<button onClick={this.save.bind(this)}>Save</button>
</div>
</div>
)
}
else {
return (
<div>
<div className="comment">
<div>{ this.props.children }</div>
<button onClick={this.edit.bind(this)}>Edit</button>
</div>
</div>
)
}
}
}
ReactDOM.render(<Board />, document.getElementById("app"));
<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="app"></div>
update your render method
let self = this;
return (
<div className="board">
{
self.state.comments.map(function(text,i) {
return (
<Comment key ={i} index = {i}
updateComment={() => {self.updateComment}}
removeComment={() => {self.removeComment}}>
{text}
</Comment>
)
})
}
</div>
)
You need to bind the class to the function, so that it knows what "this" is
render() {
return (
<div className="board">
{
this.state.comments.map(function(text,i) {
return (
<Comment key ={i} index = {i}
updateComment={this.updateComment.bind(this)}
removeComment={this.removeComment.bind(this)}>
{text}
</Comment>
)
})
}
</div>
)
}
Note that you may want to do those bindings in the constructor so that it doesn't bind at each and every render

Resources