setState is not working with multiple onClick function - javascript

I have weird issue, and been trying to debug for hours, still can't not find out why.
I have a button onClick, to handle other 2 function, some weird reason the setState one wouldn't work if I have 2 function, if I just keep one function, setState will work, but I need both function.
Here's my code
(And when I reproduce the issue here, it seems work, but the sample it's not update API, it's just change the state)
Thank you for the help!
class RecipeReviewCard extends React.Component {
constructor(props) {
super();
this.state = {
expanded: false,
in: false,
text: false
};
}
handleClick = () => {
this.setState(state => ({ expanded: !state.expanded }));
};
/* If I comment out this one, first setState will work */
handleClick = e => {
this.setState({ in: !this.state.in });
/* This come from parent props to update API */
this.props.updateThing(e.target.value)
};
<Button
className={classes.button}
onClick={e => this.handleClick()}
>

In your constructor, you should call super(props);
see: https://overreacted.io/why-do-we-write-super-props/
In your Button - you are not passing the event object to your event handler.
<Button
className={classes.button}
onClick={e => this.handleClick(e)} // note the e here.
>
Since your handleClick function is already bound to your component instance, you should just provide it to onClick handler directly without writing that arrow function, like so:
<Button
className={classes.button}
onClick={this.handleClick}
>

Related

ReactJS: Hiding a text produces an error: Maximum update depth exceeded

I just cant hide my text (Header) using a button in a class form. I try this code below:
constructor(props){
super(props)
this.state = {
showHeader: true,
}
}
And I render the state above using setState:
render () {
return (
<div>
{this.state.showHeader && <Header /> }
<button onClick={ this.setState({showHeader: false})} >Hide</button>
</div>
I know this is a stupid question but I cant help myself because Im a totally beginner. But I did this right using function and I just want try to convert it using a class. This is what I did using function:
const [show, setShow] = React.useState(true);
const hideHeader = () => {
setShow(!show)
}
And return this:
return (
<div>
{show && <Header />}
<button onClick={hideHeader}>Hide Header</button>
</div>
)
Right now you're calling setState() in your render function. That's going to cause problems because setState causes your render method to be called, and if your render method calls setState directly, you get caught in a loop.
What you need to do is call it in an event handler instead:
// bad
onClick={this.setState({showHeader: false})}
// good
onClick={() => this.setState({showHeader: false})}
So your button should look like this:
<button onClick={() => this.setState({showHeader: false})} >Hide</button>
From the docs:
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser.

Is there a way I can use button in react to delete a item in an array that is stored in the state

I am trying to build a ToDoList app and I have two components. I have a main component that handles the state and another button component that renders a delete button next to every task that I render. The problem I have is that i cant seem to connect the delete button to the index of the array and delete that specific item in the array by clicking on the button next to it.
I have tried to connect the index by using the map key id to the delete function.
just need help with how my delete function should look like and how its going to get the index of the item that is next to it and delete it.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
toDoList : []
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.delete = this.delete.bind(this);
}
handleSubmit() {
const itemsArray = this.state.userInput.split(',');
this.setState({
toDoList: this.state.toDoList.concat(itemsArray),
userInput: ''
});
}
handleChange(e) {
this.setState({
userInput: e.target.value
});
}
delete(id) {
this.setState({
toDoList: this.state.toDoList.filter( (item) => id !== item.id )
})
}
render() {
return (
<div>
<textarea
onChange={this.handleChange}
value={this.state.userInput}
placeholder="Separate Items With Commas" /><br />
<button onClick={this.handleSubmit}>Create List</button>
<h1>My Daily To Do List:</h1>
<Button toDoList={this.state.toDoList} handleDelete={this.delete} />
</div>
);
}
};
class Button extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item.id}>{item.text} <button onClick={this.props.delete(item.id)}>Done!</button></li> )
}
</ul>
);
}
};
I reviewed your edited code and made a couple of changes.
I don’t get what exactly you want to achieve with you handleSubmit method but items it adds to the list are simple strings and don’t have neither ‘id’ nor ‘text’ properties you’re referring to in other places. Possibly you’re going to change this later but while your to do items are just strings I’ve edited your code so that it work properly under this condition.
Edited delete method now accepts not item.id as a parameter but the whole item object. Yet I'm using functional form of setState as it was correctly suggested by #Hamoghamdi
delete(itemToDelete) {
this.setState(state => ({
toDoList: state.toDoList.filter( (item) => itemToDelete !== item)
}))
}
Edited render method of Button class now displays items as text and properly bind delete handler...
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item}>
{item}
<button onClick={() => this.props.handleDelete(item)}>Done!</button>
</li> )
}
</ul>
);
}
BTW Button is a bad naming for the component that isn’t exactly a button. Yet it’s better to implement it as a functional component. Use class components only if the component has its own state.
you should try using an anonymous function with setState() instead of returning an object literal directly, specially when you want to do something affected by the previous or current state
using this.state inside of setState() won't give you any good results.
here, try this:
delete = (id) => {
this.setState((prevState) => {
return { toDoList: prevState.filter( (task) => id !== task.id )}
});
You need to bind the method in constructor for example:
constructor(props) {
//...
this.handleDelete = this.handleDelete.bind(this)
}
also you can find another ways how to bind methods
In terms of handling the deleting the items, you can use
handleDelete(index) {
// Use the splice array function: splice(index, deleteCount)
this.todoList.splice(index, 1);
}
And that is all that easy

Testing onClick handler with Enzyme - cannot trigger function

I have a component that looks like such:
export default class RowCell extends Component {
...
render() {
const { column } = this.props;
const colClass = column.toLowerCase();
return (
<td className={`cell cell-row ${colClass === 'someval' ? 'anotherval' : colClass}`}>
{ this.renderAppropriateCell(colClass) }
</td>
);
}
}
And I'm testing an onClick to see if it can properly fire the handler when a click is simulated. In this case the handler is setting/updating a piece of state.
it('Should fire the onClick handler', () => {
const componentWithHandlerWrapper = shallow(
<RowCell {...props} onClick={() => this.setState({ clicked: !this.state.clicked })} />
);
// In this case, we are passing a dummy function that sets a piece of state in the component
const rowCellInstance = componentWithHandlerWrapper.setState({ clicked: false });
expect(rowCellInstance.instance().state.clicked).toEqual(false);
rowCellInstance.simulate('click');
expect(rowCellInstance.instance().state.clicked).toEqual(true);
});
However I can't get the handler to set/update state on the shallow render of the component. The last expect is failing since either 1) the click simulation is not properly executing or 2) the click simulation works but cannot update state on a shallow render? I've tried it on an instance as well with no luck.
Advice is appreciated.

setState being called too late

I am trying to make a text box auto focus.
However, I the setState is being called too late it seems.
It is being called within Popup.show. I created a button to console.log the state, and it does seem to be set to true but it must happen too late.
How can I get setState to be called as the Popup.show happens?
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
focused: false,
};
}
onClick = (event) => {
console.log('Says focussed FALSE', this.state.focused)
this.setState({ focused:true });
Popup.show(<div>
<SearchBar
autoFocus
focused={this.state.focused}
/>
<button onClick={this.checkState}>It says TRUE here</button>
</div>,
console.log('Says focussed FALSE still', this.state.focused),
{ animationType: 'slide-up'});
};
checkState = (e) =>{
console.log(this.state)
}
render() {
return (
<div style={{ padding: '0.15rem' }}>
<Button onClick={this.onClick.bind(this)}>Open & Focus</Button>
</div>);
}
}
Always remember that setState won't execute immediately. If you want Popup.show() after setState, you can use a callback:
this.setState({ focused: true }, () => {
Popup.show(...)
})
And you are already using arrow functions, you don't need the .bind(this) in your render function.
setState doesn't immediate set the state
From: https://facebook.github.io/react/docs/react-component.html#setstate
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Changing your setState to something like
this.setState({ focused: true }, () => {
Popup.show(<div>
<SearchBar
autoFocus
focused={this.state.focused}
/>
<button onClick={this.checkState}>It says TRUE here</button>
</div>)
});

Focusing div elements with React

Is it possible to focus div (or any other elements) using the focus() method?
I've set a tabIndex to a div element:
<div ref="dropdown" tabIndex="1"></div>
And I can see it gets focused when I click on it, however, I'm trying to dynamically focus the element like this:
setActive(state) {
ReactDOM.findDOMNode(this.refs.dropdown).focus();
}
Or like this:
this.refs.dropdown.focus();
But the component doesn't get focus when the event is triggered. How can I do this? Is there any other (not input) element I can use for this?
EDIT:
Well, It seems this it actually possible to do: https://jsfiddle.net/69z2wepo/54201/
But it is not working for me, this is my full code:
class ColorPicker extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false,
value: ""
};
}
selectItem(color) {
this.setState({ value: color, active: false });
}
setActive(state) {
this.setState({ active: state });
this.refs.dropdown.focus();
}
render() {
const { colors, styles, inputName } = this.props;
const pickerClasses = classNames('colorpicker-dropdown', { 'active': this.state.active });
const colorFields = colors.map((color, index) => {
const colorClasses = classNames('colorpicker-item', [`colorpicker-item-${color}`]);
return (
<div onClick={() => { this.selectItem(color) }} key={index} className="colorpicker-item-container">
<div className={colorClasses}></div>
</div>
);
});
return (
<div className="colorpicker">
<input type="text" className={styles} name={inputName} ref="component" value={this.state.value} onFocus={() => { this.setActive(true) }} />
<div onBlur={() => this.setActive(false) } onFocus={() => console.log('focus')} tabIndex="1" ref="dropdown" className={pickerClasses}>
{colorFields}
</div>
</div>
);
}
}
React redraws the component every time you set the state, meaning that the component loses focus. In this kind of instances it is convenient to use the componentDidUpdate or componentDidMount methods if you want to focus the element based on a prop, or state element.
Keep in mind that as per React Lifecycle documentation, componentDidMount will only happen after rendering the component for the first time on the screen, and in this call componentDidUpdate will not occur, then for each new setState, forceUpdate call or the component receiving new props the componentDidUpdate call will occur.
componentDidMount() {
this.focusDiv();
},
componentDidUpdate() {
if(this.state.active)
this.focusDiv();
},
focusDiv() {
ReactDOM.findDOMNode(this.refs.theDiv).focus();
}
Here is a JS fiddle you can play around with.
This is the problem:
this.setState({ active: state });
this.refs.component.focus();
Set state is rerendering your component and the call is asynchronous, so you are focusing, it's just immediately rerendering after it focuses and you lose focus. Try using the setState callback
this.setState({ active: state }, () => {
this.refs.component.focus();
});
A little late to answer but the reason why your event handler is not working is probably because you are not binding your functions and so 'this' used inside the function would be undefined when you pass it as eg: "this.selectItem(color)"
In the constructor do:
this.selectItem = this.selectItem.bind(this)
this.setActive = this.setActive.bind(this)
This worked in my case
render: function(){
if(this.props.edit){
setTimeout(()=>{ this.divElement.focus() },0)
}
return <div ref={ divElement => this.divElement = divElement}
contentEditable={props.edit}/>
}

Categories