This is my todo app made with ReactJS. I am not able to delete todo's properly.
It always removes the last todo, regardless of the one i click.
Example: If i click to delete 'Buy socks', it will remove 'Oi them'. If i try to check my list state in the debugger, it deleted the correct ToDo.
I uploaded the code to GitHub repository, it was setup with create-react-app should be very easy to setup.
From what i understand the todo entry that i delete in the state of the TodoList does not remove the state of the removed children, and therefore it does not cease to exist.
How do i take care of it? What am i doing wrong?
Hint: here is a gist with unnecessary code removed.
Right now your deleteTodo function has the wrong this bound to it. The deleteTodo function wants to set the state of the parent component, and calls this.setState(). However, the this is bound in the child's constructor. You want to bind this to the deleteTodo in the constuctor of the parent component: TodoList.
That way when the function is called, it will correctly set the state of TodoList with the new, filtered list of todos.
So be more specific about the changes needed in the code you posted.
First in the constructor of TodoList, bind deleteTodo
this.deleteTodo = this.deleteTodo.bind(this)
Now when this.setState is called in the deleteTodo function it will be setting the state of the correct component.
Now we need to make sure we correctly pass in the argument of delete todo.
In the Todo component, replace
onClick={this.props.deleteTodo.bind(this,todoText)}
with
function(){this.props.deleteTodo(todoText)}
You do not need to bind here, wrap the deleteTodo function so that when onClick is called the wrapper function calls deleteTodo with the correct argument.
When the element is clicked, the function assigned to the onClick prop will be called, with a click event object as an argument. We set this function up to call deleteTodo with the correct argument, ignoring the event object.
class Todo extends React.Component {
/*...*/
handleDelete() {
this.props.deleteTodo(this.state.text);
}
/*...*/
render() {
let priorityClass = this.switchPriority(this.state.priority);
let completedClass = this.getCompletedClass(this.state.completed);
let todoClasses = classNames(priorityClass, completedClass);
return (
<tr className={todoClasses}>
<td><button ref='deleteTodo' onClick={this.handleDelete.bind(this)} className='close-button'>×</button></td>
<td onClick={this.handleClick} className='todo-row'>{this.state.text}</td>
</tr>);
}
}
class TodoList extends React.Component {
/*...*/
deleteTodo(todoToRemove) {
let newTodos = this.state.todos.filter((_todo) => _todo.text !== todoToRemove);
this.setState({
todos: newTodos
});
}
/*...*/
}
Could you try this ? I think it is because you are binding deleteTodo two times.
Related
I was reading the documentation and practicing some things in the React documentation until I finally entered the event handling section. but I don't understand why when using method in class component we have to bind the function, can anyone explain it? for examples :
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
The point is to keep correct value of this reference. Check this example:
class Example {
private prop = 1;
echoProp() {
console.log(this?.prop);
}
}
const example = new Example();
example.echoProp();
const echoPropRef = example.echoProp;
echoPropRef();
In the console you will see 1 and then undefined. This is because:
Example:echoProp's this reference is instance of Example class, so it see also prop property.
if you pass Example:echoProp's reference to another variable (const echoPropRef = example.echoProp - there is no ()), then this reference is changed into undefined.
bind function "freezes" this reference, so it will be always the same; in you example it will be reference to Toggle class.
The bind is necessary if you use code like
<button onClick={this.handleClick}>
When handleClick is triggered by the button, this value inside handleClick will be the click event (this is js behaviour for event listener), but the click event does not have a setState method, which belongs to the component itself. So you want to bind the method to the component, so that this value inside handleClick will be the Toggle component which has the setState method
The this is problem is a general problem in JS (not limited to React).
Normally inside a method, this refers to the current object instance.
Inside an event listener (even if its a method), this refers to the event target.
The this in this.setState must refer to the Component instance.
So to change the value of this, binding is done in the constructor.
Another solution would be
onClick={(evt)=>{handleClick(evt)}}
In this case handleClick ceases being an event listener and the actual event listener would be the anonymous function inside onClick.
The simplest alternative is :
handleClick=(evt)=>{
this.setState(...)
}
Arrow functions do not have their own this & hence acquire the current object (component) instance as this.
I have a situation that an item outside the component might influence the item in the backend. ex: change the value of one of the properties that are persisted, let's say the item status moves from Pending to Completed.
I know when it happens but since it is outside of a component I need to tell to the component that it is out of sync and re-fetch the data. But from outside. I know you can pass props calling the render method again. But the problem is I have a reducer and the state will pick up the last state and if I use an prop to trigger an effect I get into a loop.
Here is what I did:
useEffect(() => {
if (props.effect && !state.effect) { //this runs when the prop changes
return dispatch({ type: props.effect, });
}
if (state.effect) { // but then I get here and then back up and so on
return ModelEffect[state.effect](state?.data?.item)}, [state.status, state.effect, props.effect,]);
In short since I can't get rid of the prop the I get the first one then the second and so on in an infinite loop.
I render the root component passing the id:
render(html`<${Panel} id=${id}/>`,
document.getElementById('Panel'));
Then the idea was that I could do this to sync it:
render(html`<${Panel} id=${id} effect="RELOAD"/>`,
document.getElementById('Panel'));
any better ways to solve this?
Thanks a lot
I resolved it by passing the initialized dispatch function to a global.
function Panel (props) {
//...
const [state, dispatch,] = useReducer(RequestReducer, initialData);
//...
useEffect(() => {
//...
window.GlobalDispatch = dispatch;
//...
}, [state.status, state.effect,]);
with that I can do:
window.GlobalDispatch({type:'RELOAD'});
I have a functional component managing several states, amongst other things a state, which stores the index, with which I am rendering a type of table, when clicking a suitable button. OnClick that button calls a callback function, which runs a click handler. That click handler changes the index state, to the same 'index' as the array entry, in which I store an object with information for the rendering of a child component.
I would expect, that onClick the state would change before the rendering happens, so the component could render correctly. Yet it only happens a render later.
I already tried calling the useEffect-hook, to re-render, when that index state changes, but that didn't help neither.
Here is a shortened version of the code:
export const someComponent = () => {
[index, setIndex] = useState(-1);
const handleClick = (id) => {
setIndex(id);
// This is a function, I use to render the table
buildNewComponent(index);
}
}
Further 'down' in the code, I got the function, which is rendering the table entries. There I pass the onClick prop in the child component of the table as following:
<SomeEntryComponent
onClick={() => handleClick(arrayEntry.id)}
>
// some code which is not affecting my problem
</SomeEntryComponent>
So as told: when that onClick fires, it first renders the component when one presses it the second time, because first then the state changes.
Could anyone tell me why that happens like that and how I could fix it to work properly?
As other have stated, it is a synchronicity issue, where index is being updated after buildComponent has been invoked.
But from a design standpoint, it would be better to assert index existence by its value, as opposed to flagging it in a handler. I don't know the details behind buildComponent, but you can turn it into a conditional render of the component.
Your component rendering becomes derived from its data, as opposed to manual creation.
export const someComponent = () => {
[index, setIndex] = useState(-1);
const handleClick = (id) => setIndex(id);
const indexHasBeenSelected = index !== -1
return (
<div>
{indexHasBeenSelected && <NewComponent index={index} />}
</div>
)
}
When calling buildNewComponent the index is not yet updated. You just called setState, there is no guarantee that the value is updated immediately after that. You could use id here or call buildNewComponent within a useEffect that has index as its dependency.
I believe that you can have the correct behavior if you use useEffect and monitor index changes.
export const someComponent = () => {
[index, setIndex] = useState(-1);
useEffect(() => {
// This is a function, I use to render the table
buildNewComponent(index);
}, [buildNewComponent, index])
const handleClick = (id) => {
setIndex(id);
}
}
The process will be:
When the use clicks and call handleClick it will dispatch setIndex with a new id
The index will change to the same id
The useEffect will see the change in index and will call buildNewComponent.
One important thing is to wrap buildNewComponent with useCallback to avoid unexpected behavior.
In short :-
I want to update child state first and than update parent state but react batches setSate and calls parent setState first and than childs. For more understanding read the explanation written below.
Basically i have a parent component which is having two child.
Sort component :- This component opens a dropdown for sort options selection. on click it should update the local state and than call the function passed as props from parent.
Product collection :- This component shows the products based on the sort selected.
I am passing a function (handleClick) from parent component to sort to get the value of selected sort into the parent and than passing it to product collection.
Since sort is a dropdown i want to close it first as soon as user selects a option and than i want to update the parent.
Right now i am using it in such a way, first updating local state and in callback calling the function passed from parent.
handleClick(param) {
this.setState({ selectedType: param.slug }, () =>
this.props.onHandleSort(param.slug)
)
}
But as written in React docs it batches the process and calls the parent setState first and than the child.
For example, that if both Parent and Child call setState
during a click event, Child isn’t re-rendered twice. Instead, React
“flushes” the state updates at the end of the browser event. This
results in significant performance improvements in larger apps.
I need it to happen in such way only because i need my dropdown to close first and than the parent should update.
I have tried the following this but nothing seems to work.
Made sort component stateless and dependent on the props from the parent but this will take time to close the dropdown.
Used callback of setState but since as written in docs it batches and calls parent setState first and than childs.
Judging from your codepen, you should lift the withRouter-wrapper up to the parent, let it figure out the selectedType and pass it to your sort component. In your onHandleSort, you can then set the new query.
class Parent extends Component {
// ...
handleClick (slug) => {
this.props.router.push({ query: { sorting: slug } })
}
// ...
render () {
const sorting = this.props.router && this.props.router.query
? this.props.router.query.sorting
: 'RELEVANCE';
return (
// ...
<Sort value={sorting} onHandleSort={this.handleClick} />
// ...
);
}
}
export default withRouter(Parent);
export default class Sort extends Component {
// ...
handleClick (param) => {
this.props.onHandleSort(param.slug)
}
// ...
render () {
const selectedType = this.props.sorting;
return (
// ...
);
}
}
I need to remove a prop from a child.
I have a container element which uses a property on it's children to perform some enhancements on the children. That property should be removed from the child before rendering.
<AsyncContainer>
<Button onClick={this.asyncStuff} asyncHandler="onClick"/>
</AsyncContainer>
The asyncHandler property should be removed from the button before rendering.
AsyncContainer uses React.cloneElement(child, properties).
I've tried nulling the asyncHandler property, setting it to undefined and deleting the property from the child.props. It seems that it is impossible to get rid of this property again.
I just ran into this issue. You can just create a new element and use the old element's type and props you want to pass through. I'm not sure if this an anti-pattern or not, I just stumbled on it and it seems to be working well so far.
It should look something like this:
function AsyncContainer(props) {
const child = React.Children.only(props.children)
const { asyncHandler, ...childProps } = child.props
// do asyncHandler stuff
return React.createElement(child.type, childProps)
}
function AsyncContainer(props) {
const child = React.Children.only(props.children);
return React.cloneElement(
child,
{ asyncHandler: undefined }
);
}
How it works
You clone element using React.cloneElement because element is immutable and only way to change its props is to create clone.
Use second React.cloneElement argument to add new props and remove old props. Unneeded props should be assigned with undefined. You need to do this because by default cloned element is cloned with all its props.
As per the comments you cannot modify the props directly as they are immutable.
However, I think I have a simple solution to this problem. I have no idea what library that is or how it works, so this may or may not work. However, this is a general answer to how you would remove a prop before a component gets mounted.
That being said, I would try to create my own component which renders a <Button />:
class MyButtonComponent extends React.Component {
...
render() {
return <Button onClick={this.props.onClickHandler} />;
}
}
Then in the component you want to do your enhancements:
render() {
<AsyncContainer>
<MyButtonComponent onClickHandler={this.asyncStuff} asyncHandler="onClick"/>
</AsyncContainer>
}
This way you maintain your onClick eventlistener on the <Button /> component but you don't pass the illegal asyncHandler prop.
Edit:
Alternatively, you could also do:
class MyButtonComponent extends React.Component {
...
componentWillMount() {
let newProps = this.props;
delete newProps.asyncHandler;
this.setState({properties: newProps}):
}
render() {
return <Button {...this.state.properties} />;
}
}
This will apply all the props (with the spread operator) to <Button /> except for asyncHandler which we delete prior to the component being mounted by creating a copy of the props in state but with asyncHandler removed.
Also check this answer I gave to a similar question.