React not updating the render after setState - reactjs

This is really driving me in nuts. I wrote so many components and this is so basic, but probably there is something that I can't realize in this simple scenario. Basically my render is not being updated when I set the state.
import React, {Component} from 'react'
import styles from './styles'
import {css} from 'aphrodite'
export class Switcher1 extends Component {
constructor(props) {
super(props)
this.state = {
selected: 0
}
}
setIndex(ndx) {
console.log(ndx)
this.setState = {selected: ndx}
}
render() {
console.log('Rendering...')
const sel = this.state.selected
return (
<div className={css(styles.container)}>
{this.props.options.map((item, index) => {
const isSelected = index === sel
return <div key={index}
className={isSelected ? css(styles.active) : css(styles.notActive)}
onClick={() => this.setIndex(index)}>
<img className={css(styles.image)}
src={"/assets/images/" + item.icon + (isSelected ? '_blue.svg' : '_white.svg')}/>
{item.text} - {sel} - {index} - {isSelected.toString()}
</div>
})}
</div>)
}
}
Updated code...but it's still not works...
import React, {Component} from 'react'
import styles from './styles'
import {css} from 'aphrodite'
export class Switcher1 extends Component {
constructor(props) {
super(props)
this.state = {
selected: 0
}
this.setIndex = this.setIndex.bind(this)
}
setIndex(ndx) {
console.log(this.state)
this.setState = ({selected: ndx})
}
render() {
console.log('Rendering...')
const sel = this.state.selected
return (
<div className={css(styles.container)}>
{this.props.options.map((item, index) => {
const isSelected = index === sel
return <div key={index}
className={isSelected ? css(styles.active) : css(styles.notActive)}
onClick={() => this.setIndex(index)}>
<img className={css(styles.image)}
src={"/assets/images/" + item.icon + (isSelected ? '_blue.svg' : '_white.svg')}/>
{item.text} - {sel} - {index} - {isSelected.toString()}
</div>
})}
</div>)
}
}
Any thoughts ?

1) You forgot to bind this to your setIndex method : setIndex=(ndx)=> {
2) the setState is wrong, replace by : this.setState({ selected: ndx });
result :
setIndex = (ndx) => {
console.log(ndx);
this.setState({ selected: ndx });
}

You have:
this.setState = {selected: ndx}
This is not how you set state. Try this:
this.setState({ selected: ndx });
Also, as #benjamin mentioned, you should bing the setIndex function by using an arrow function:
setIndex = () => { ... }
or binding in your constructor:
this.setIndex = this.setIndex.bind(this);
You can read the docs on setState here.

Related

Delete record from todo list in ReactJS giving error

Am learning ReactJS and building my todo application.
However am facing an issue when I try to delete a task.
I have two files TodoList.js and TodoItems.js
TodoList.js
import React, {Component} from 'react';
import TodoItems from './TodoItems';
class TodoList extends Component {
//Function to handle adding tasks
addTask(event) {
//Get task Value
let task = this.refs.name.value;
//Newitem Object
if (task !== "") {
let newItem = {
text: task,
key: Date.now()
}
this.setState({
items: this.state.items.concat(newItem)
});
this.refs.name.value = ""; //Blank out the task input box
}
}
deleteItem(key) {
var filteredItems = this.state.items.filter(function (item) {
return (item.key !== key);
});
this.setState({
items: filteredItems
});
}
constructor(props) {
super(props);
this.state = {
items: []
};
this.addTask = this.addTask.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form>
<input placeholder="Enter Task" id="name" ref="name"></input>
<button type="button" onClick={this.addTask}>Add Task</button>
</form>
</div>
<div className="list">
<TodoItems entries={this.state.items} delete={this.deleteItem} />
</div>
</div>
);
}
}
export default TodoList;
TodoItems.js has following code
import React, {Component} from 'react';
class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {};
}
delete(key) {
this.props.delete(key);
}
listTasks(item) {
return <li key={item.key} onClick={() => this.delete(item.key)}>{item.text}</li>
}
render() {
let entries = this.props.entries;
let listItems = entries.map(this.listTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
}
export default TodoItems;
I am getting an error on deleting task when clicked on it.
and I am getting error as here
I guess it means function delete is not defined but it has been defined still am getting an error.
Can anyone explain how do I resolve this issue?
You should never attempt to modify your props directly, if something in your components affects how it is rendered, put it in your state :
this.state = {
entries: props.entries
};
To delete your element, just filter it out of your entries array :
delete(key) {
this.setState(prevState => ({
entries: prevState.entries.filter(item => item.key !== key)
}))
}
And now the render function :
render() {
const { entries } = this.state //Takes the entries out of your state
return (
<ul className="theList">
{entries.map(item => <li key={item.key} onClick={this.delete(item.key)}>{item.text}</li>)}
</ul>
);
}
Full code :
class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {
entries: props.entries
};
}
delete = key => ev => {
this.setState(prevState => ({
entries: prevState.entries.filter(item => item.key !== key)
}))
}
render() {
const { entries } = this.state
return (
<ul className="theList">
{entries.map(item => <li key={item.key} onClick={this.delete(item.key)}>{item.text}</li>)}
</ul>
);
}
}
You should also try to never use var. If you do not plan to modify a variable, use const, otherwise, use let.
EDIT : The error shown in your edit come from listTasks not being bound to your class. To solve it you can either bind it (as shown in an other answer below) or convert it in another function :
listTasks = item => {
return <li key={item.key} onClick={() => this.delete(item.key)}>{item.text}</li>
}
Short syntax :
listTasks = ({ key, text }) => <li key={key} onClick={() => this.delete(key)}>{text}</li>
Welcome to Stackoverflow!
Check this section of the React Docs. You either have to bind your class functions in the constructor or use arrow functions.
class TodoItems extends Component {
constructor(props) {
// ...
this.delete = this.delete.bind(this);
}
delete(key) {
this.props.delete(key);
}
// Or without binding explicitly:
delete2 = (key) => {
// ...
}
}
Replace this:
onClick={this.delete(item.key)}
// passes the result of `this.delete(item.key)` as the callback
By this:
onClick={() => this.delete(item.key)}
// triggers `this.delete(item.key)` upon click

React function - is not defined no-undef

I get the following error when trying to compile my app 'handleProgress' is not defined no-undef.
I'm having trouble tracking down why handleProgress is not defined.
Here is the main react component
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
this.handleProgress = this.handleProgress.bind(this);
}
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
handleProgress = () => {
console.log('hello');
};
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
Your render method is wrong it should not contain the handlePress inside:
You are calling handlePress on this so you should keep it in the class.
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
this.handleProgress = this.handleProgress.bind(this);
}
handleProgress = () => {
console.log('hello');
};
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
If you are using handleProgress inside render you have to define it follows.
const handleProgress = () => {
console.log('hello');
};
if it is outside render and inside component then use as follows:
handleProgress = () => {
console.log('hello');
};
If you are using arrow function no need to bind the function in constructor it will automatically bind this scope.
handleProgress should not be in the render function, Please keep functions in you component itself, also if you are using ES6 arrow function syntax, you no need to bind it on your constructor.
Please refer the below code block.
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
// no need to use bind in the constructor while using ES6 arrow function.
// this.handleProgress = this.handleProgress.bind(this);
}
// move ES6 arrow function here.
handleProgress = () => {
console.log('hello');
};
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
Try this one, I have check it on react version 16.8.6
We don't need to bind in new version using arrow head functions. Here is the full implementation of binding argument method and non argument method.
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};
constructor() {
super();
}
render() {
return (
<div>
<button onClick={this.updateCounter}>NoArgCounter</button>
<button onClick={() => this.updateCounterByArg(this.state.count)}>ArgCounter</button>
<span>{this.state.count}</span>
</div>
);
}
updateCounter = () => {
let { count } = this.state;
this.setState({ count: ++count });
};
updateCounterByArg = counter => {
this.setState({ count: ++counter });
};
}
export default Counter;

react-select-fast-filter-options filter does not work

I have been trying to use react-select-fast-filter-options by passing props.options, but the filtering does not happen. All the options are getting rendered but the filter doesn't work.
I am also getting a warning:
Warning: getDefaultProps is only used on classic React.createClass definitions. Use a static property named defaultProps instead.
This is how I am trying to use the fast-filter:
import React, { Component } from 'react';
import VirtualizedSelect, { Value } from 'react-virtualized-select';
import createFilterOptions from 'react-select-fast-filter-options';
import 'react-select/dist/react-select.css';
import styles from './CategoryDropdown.less';
import CategoryDropdownOption from './CategoryDropdownOption';
import CategoryDropdownValue from './CategoryDropdownValue';
class CategoryDropdown extends Component {
constructor(props, context) {
super(props, context);
**const filterOptions = createFilterOptions({
labelKey: 'code',
options: props.options
});**
this.sortOptions = this.sortOptions.bind(this);
this.setValue = this.setValue.bind(this);
this.clearValue = this.clearValue.bind(this);
const dValue = props.defaultValue ? props.defaultValue : {};
this.state = { value: dValue, options: [], selectedOption:{}, filterOptions };
}
componentDidMount() {
this.sortOptions(this.props.options);
this.setValue('');
}
componentWillReceiveProps(nextProps) {
this.sortOptions(nextProps.options);
}
clearValue() {
this.setState({ value: '' });
this.setState({selectedOption:{}});
}
return (
<div
key={key}
className={classNames.join(' ')}
onClick={() => {
focusOption(option);
selectValue(option);
}}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
focusOption(option);
selectValue(option);
}}
onMouseEnter={() => { focusOption(option); }}
style={style}
title={option.label}>
<div className="categoryOptionType">
<span className={option.categoryName}>{option.categoryDisplayName}</span>
</div>
<div className="optionLabelContainer">
<span className="optionLabel">{value}</span>
</div>
</div>
);
}
render() {
const {filterOptions} = this.state;
return (
<VirtualizedSelect
simpleValue
clearable={true}
label='code'
name="form-field-name"
multi={this.props.multi}
optionHeight={20}
onChange={this.setValue}
**filterOptions={filterOptions}**
options={this.state.options}
searchable={true}
value={this.state.selectedOption}
optionRenderer={this.virtualOptionRenderer}
valueComponent={this.props.emptyValueComponent ? Value : CategoryDropdownValue}
className={this.props.className || 'categoryDropdown'}
optionClassName={this.props.optionClassName || 'categoryOption'}
placeholder={this.props.placeholder || 'Start typing to search'}
autosize={this.props.autosize !== false}
//matchProp="label"
/>
);
}
}
export default CategoryDropdown;
I am not sure about your ** tag, seems it is used to comment the code.
However, if we skip that ** tag then your code is good, except you are filtering your filterOptions: filterOptions = createFilterOptions({ ... }) within the constructor which is ONLY executed ONCE when the component is initialized.
Put this block on componentWillReceiveProps should fix your problem.
componentWillReceiveProps(nextProps) {
const filterOptions = createFilterOptions({
labelKey: 'code',
options: nextProps.options
});
this.setState({filterOptions});
this.sortOptions(nextProps.options);
}

Passing value from Button to setState in array

I am trying to created boolean filters in an array.
If a button is "Active" (True) it should add the button name to the array ("selected").
If the button is "Inactive" (false), it should remove it from the array.
However, only some values end up in set state. I put it in a codepen:
https://codesandbox.io/s/wpxD35Oog
import React from 'react';
import { render } from 'react-dom';
import { x } from './data.js';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: x.movies,
};
}
render() {
const uniqueGenres = []
.concat(
...this.state.movies.map(movies =>
movies.genres.map(genres => genres.name),
),
)
.filter((genre, i, _) => _.indexOf(genre) === i);
return (
<div>
<div>
{uniqueGenres.map(e => <Filter1 key={e} result={e} />)}
<br />
</div>
</div>
);
}
}
class Filter1 extends React.Component {
constructor(props) {
super(props);
this.state = {
active: true,
selected: '',
};
}
handleActive = e => {
// this.setState(previousState => ({
// selected: [...previousState.selected, e.target.value]
// }));
console.log('pre-setState', e.target.value);
const active = !this.state.active;
const selected = e.target.value;
this.setState({
active: active,
selected: selected,
});
console.log('status', this.state.active, this.state.selected);
};
render() {
return (
<span>
<button onClick={this.handleActive} value={this.props.result}>
{this.props.result} {}<b>
{this.state.active ? 'Active' : 'Inactive'}
</b>
</button>
</span>
);
}
}
Ideally you would let the parent component (in this case, App) take care of managing the active genres. I rewrote part of your code to demonstrate it: https://codesandbox.io/s/JZWp1RQED

adding hammerjs to a react js component properly

I try to add hammer js to my reactjs component and my component looks as it follows
import React from 'react';
import _ from 'underscore';
import Hammer from 'hammerjs';
class Slider extends React.Component {
constructor(props) {
super(props)
this.updatePosition = this.updatePosition.bind(this);
this.next = this.next.bind(this);
this.prev = this.prev.bind(this);
this.state = {
images: [],
slidesLength: null,
currentPosition: 0,
slideTransform: 0,
interval: null
};
}
next() {
console.log('swipe')
const currentPosition = this.updatePosition(this.state.currentPosition - 10);
this.setState({ currentPosition });
}
prev() {
if( this.state.currentPosition !== 0) {
const currentPosition = this.updatePosition(this.state.currentPosition + 10);
this.setState({currentPosition});
}
}
componentDidMount() {
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next());
this.hammer.on('swiperight', this.prev());
}
componentWillUnmount() {
this.hammer.off('swipeleft', this.next())
this.hammer.off('swiperight', this.prev())
}
handleSwipe(){
console.log('swipe')
}
scrollToSlide() {
}
updatePosition(nextPosition) {
const { visibleItems, currentPosition } = this.state;
return nextPosition;
}
render() {
let {slides, columns} = this.props
let {currentPosition} = this.state
let sliderNavigation = null
let slider = _.map(slides, function (slide) {
let Background = slide.featured_image_url.full;
if(slide.status === 'publish')
return <div className="slide" id={slide.id} key={slide.id}><div className="Img" style={{ backgroundImage: `url(${Background})` }} data-src={slide.featured_image_url.full}></div></div>
});
if(slides.length > 1 ) {
sliderNavigation = <ul className="slider__navigation">
<li data-slide="prev" className="" onClick={this.prev}>previous</li>
<li data-slide="next" className="" onClick={this.next}>next</li>
</ul>
}
return <div ref={
(el) => this._slider = el
} className="slider-attached"
data-navigation="true"
data-columns={columns}
data-dimensions="auto"
data-slides={slides.length}>
<div className="slides" style={{ transform: `translate(${currentPosition}%, 0px)`, left : 0 }}> {slider} </div>
{sliderNavigation}
</div>
}
}
export default Slider;
the problem is like on tap none of the components method are fired.
How do I deal in this case with the hammer js events in componentDidMount
Reason is, inside componentDidMount lifecycle method swipeleft and swiperight expect the functions but you are assigning value by calling those methods by using () with function name. Remove () it should work.
Write it like this:
componentDidMount() {
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next); // remove ()
this.hammer.on('swiperight', this.prev); // remove ()
}

Resources