ReactJS, Todo list, how to remove elements from list - reactjs

I'm ReactJS newbie (well, to be specific I'm programming newbie) looking for some answers, please don't exclude me for obvious stupidity :) I'm having big troubles with passing props and understanding 'this' context.
This is my mess, two simple Todo app components. TodoApp:
import React from 'react';
import uuid from 'uuid';
import style from './App.css';
import Title from '../components/Title.js';
import TodoList from '../components/TodoList.js';
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo(id) {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div className={style.TodoApp}>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
export default App;
And TodoList:
import React from 'react';
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<li key={index}>
{item.text}
<button value={index} onClick={() => props.remove(index)}>x</button>
</li>
)}
</ul>
);
export default TodoList;
My question is how to correctly pass props to child component (TodoList) so that remove button would work?

To make sure the this always refers to the App context where your removeTodo method is defined, you can add the following inside your constructor (after setting the initial state):
this.removeTodo = this.removeTodo.bind(this);
Otherwise, your code isn't messy at all. It's even surprisingly concise and well thought out, even much more so for a self-proclaimed beginner. Congratulations!
As pointed out by Sag1v in the comment below, you're also not passing the correct value to your removeTodo method. You're passing the index of the item being iterated on, instead of its id.
Change your <button> invocation to the following:
<button value={index} onClick={() => props.remove(item.id)}>x</button>
Note that you could also achieve the same with the following:
<button value={index} onClick={props.remove.bind(null, item.id)}>x</button>

You got 2 main problems here:
As Jaxx mentioned you are not binding the handler removeTodo to
the class.
There are couple of ways to do it.
Bind it in the constructor:
this.removeTodo = this.removeTodo.bind(this);
Or use the ES2015(ES6) Arrow functions which will use the lexical context for this:
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
The 2nd problem is that inside TodoList onClick handler you are
not passing the correct id to the handler, you are passing the
index position.
onClick={() => props.remove(index)}
You should change that to:
onClick={() => props.remove(item.id)}
There is another problem with this approach which i'll explain next.
Here is a working example:
const Title = ({title}) => <h1>{title}</h1>
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<li key={index}>
{item.text}
<button value={index} onClick={() => props.remove(item.id)}>x</button>
</li>
)}
</ul>
);
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
<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="root"></div>
As i said, there is a problem with this approach, you are passing a new instance of a function on each render with this line of code:
onClick={() => props.remove(item.id)
This is considered as bad practice because this can interrupt the Reconciliation and diffing algorithm
But as we know, event Handlers should get a function reference, hence you can't just pass a function invocation like this
onClick={props.remove(item.id)}
This will pass the function's return type (if any) and not the reference for the function.
So the proper way of passing a function reference is like this:
onClick={props.remove}
But that is not good for your case as you need to pass back to the parent the current item id, but i'm afraid that the browser will only pass back the event parameter.
So what are the alternatives you ask?
Create another component and control the data you pass in and out from your component instead of relying on the goodwill of the browsers.
Here is another working example but this time without creating a new function instance on each render
const Title = ({title}) => <h1>{title}</h1>
class TodoItem extends React.Component {
handleClick = () => {
const{item, onClick} = this.props;
onClick(item.id);
}
render(){
const {item} = this.props;
return(
<li>
{item.text}
<button value={item.id} onClick={this.handleClick}>x</button>
</li>
);
}
}
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<TodoItem key={index} item={item} onClick={props.remove} />
)}
</ul>
);
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
<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="root"></div>

Related

How to edit a todo in a todo list without hooks and redux

I have been stuck on this for days reading up on tutorials and articles but can not figure this out. Whenever I click on the pencil icon, I want it to edit the current do to. I have 4 components, the form (searchbar where i add todo), the app.js, the todoList, and a todo.js component. I am keeping all the state in the app and state in the form to keep track of the terms I am entering.
I am thinking I would need to create an editTodo method in the app and pass it down as a prop to the list and then the todoItem. Most tutorials or help online uses hooks or redux but I am learning vanilla React first. I am not asking for the answer directly but rather the steps or thought process to implement editing a todo item in the todolist. I am not sure even if my todo app is correct in the places where I am keeping state. I may get slack for asking.. but I do not know what else to do. Here is my code..
class App extends React.Component {
state = {
todos: []
}
addTodo = (todo) => {
const newToDos = [...this.state.todos, todo];
this.setState({
todos: newToDos
});
};
deleteTodo = (id) => {
const updatedTodos = this.state.todos.filter((todo) => {
return todo.id !== id;
});
this.setState({
todos: updatedTodos
});
}
editTodo = (id, newValue) => {
}
render() {
return (
<div className="container">
<div className="row">
<div className="col">
<Form addTodo={this.addTodo} />
</div>
</div>
<div className="row">
<div className="col">
<ToDoList
todos={this.state.todos}
deleteTodo={this.deleteTodo}
editingTodo={this.state.editingTodo}/>
</div>
</div>
</div>
)
}
}
export default App;
const ToDoList = ({todos, deleteTodo, editingTodo}) => {
const renderedList = todos.map((todo, index) => {
return (
<ul className="list-group" key={todo.id}>
<ToDoItem todo={todo} deleteTodo={deleteTodo} editingTodo={editingTodo}/>
</ul>
)
});
return (
<div>
{renderedList}
</div>
)
}
export default ToDoList;
const ToDoItem = ({todo, deleteTodo}) => {
return (
<div>
<li style={{display: 'flex', justifyContent: 'space-between' }} className="list-group-item m-3">
{todo.text}
<span>
<FontAwesomeIcon
icon={faPencilAlt}
style={{ cursor: 'pointer'}}
/>
<FontAwesomeIcon
icon={faTrash}
style={{ marginLeft: '10px', cursor: 'pointer'}}
onClick={ () => deleteTodo(todo.id)}
/>
</span>
</li>
</div>
);
}
export default ToDoItem;
I don't think the form component is relevant here as I am trying to edit a todo item so will not include it here. If I do need to include it, let me know. It may not look like I have tried to implement this functionality, but either I could not find what I was looking for, understand the code, or just do not know how to implement it.
Update:
I added an isEditing field in the form component to my todo items so that maybe it can help me know if an item is being editing or not. I also redid the editTodo method.
class Form extends React.Component {
state = { term: ''};
handleSubmit = (e) => {
e.preventDefault();
this.props.addTodo({
id: shortid.generate(),
text: this.state.term,
isEditing: false
});
this.setState({
term: ''
});
}
editTodo = (id, newValue) => {
const editedTodos = [...this.state.todos].map((todo) => {
if(todo.id === id) {
todo.isEditing = true;
todo.text = newValue;
}
return todo.text;
});
this.setState({
todos: [...this.state.todos, editedTodos]
});
}
I also passed that method down to the todoList and then to the todoItem like so
const ToDoItem = ({todo, deleteTodo, editTodo}) => {
const renderContent = () => {
if(todo.isEditing) {
return <input type='text' />
} else {
return <span>
<FontAwesomeIcon
icon={faPencilAlt}
style={{ cursor: 'pointer'}}
onClick={ () => editTodo(todo.id, 'new value')}
/>
<FontAwesomeIcon
icon={faTrash}
style={{ marginLeft: '10px', cursor: 'pointer'}}
onClick={ () => deleteTodo(todo.id)}
/>
</span>
}
}
return (
<div>
<li style={{display: 'flex', justifyContent: 'space between'}} className="list-group-item m-3">
{{!todo.isEditing ? todo.text : ''}}
{renderContent()}
</li>
</div>
);
}
So whenever I click on the the edit icon, it successfully shows 'new value' but now also adds an extra todo item which is blank. I figured out how to add the input field so that it shows also. I am accepting the answer Brian provided since it was the most helpful in a lot of ways but have not completed the functionality for editing a todo.
am thinking I would need to create an editTodo method in the app and pass it down as a prop to the list and then the todoItem.
This is exactly what you need to do. And yet:
editTodo method has no logic in it.
ToDoList component receives editingTodo method as a prop instead of defined editTodo.
You are indeed passing the editingTodo futher down to ToDoItem but you are not utilising it there const ToDoItem = ({todo, deleteTodo}) => ...
You don't have an onClick listener on the pencil icon, so nothing can happen.
I don't know how you are planning on doing the editing (modal window with a form, or replacing the text with an input field), either way the bottom line is that you need to trigger your pencil onClick listener with () => editTodo(id, newText).
My recommendation would be - address all 5 points above and for now just hardcode the new value, just to test it out: () => editTodo(id, 'updated value!') and check that everything works. You can worry about getting the real value in there as your next step.

How to dispatch an action from the column formatter component of react-data-grid

I need to dispatch an action for deleting a row entry when a custom formatted delete button is clicked
I think what you want is available HERE
you can can props in grid formatter like
render() {
return (
<ReactDataGrid
columns={this._columns}
rowGetter={this.rowGetter}
rowsCount={this._rows.length}
minHeight={500}
rowRenderer={RowRenderer} />);
}
And RowRenderer is a function
const RowRenderer = React.createClass({
propTypes: {
idx: React.PropTypes.string.isRequired
},
setScrollLeft(scrollBy) {
// if you want freeze columns to work, you need to make sure you implement this as apass through
this.refs.row.setScrollLeft(scrollBy);
},
getRowStyle() {
return {
color: this.getRowBackground()
};
},
getRowBackground() {
return this.props.idx % 2 ? 'green' : 'blue';
},
render: function() {
// here we are just changing the style
// but we could replace this with anything we liked, cards, images, etc
// usually though it will just be a matter of wrapping a div, and then calling back through to the grid
return (<div style={this.getRowStyle()}><ReactDataGrid.Row ref="row" {this.props}/></div>);
}
});
I faced a similar issue and solved it in a different way. I had 2 columns for which I wanted to add a selectable image, the functionality added was to delete an entry via redux and download a file.
I had a wrapping top level react component shown below:
class ItemComponent extends Component {
constructor(props) {
super(props);
this.props.getData();
}
additionalColumns = (item) => {
let conditionalImage;
if (record.status === 'SOMETHING') {
conditionalImage = (
<div>
<a href="javascript:void(0)" role="button" onClick={() => this.download(item)}><span className="fa fa-file" /></a>
</div>
);
}
return {
...record,
conditionalImage,
delete: (
<div>
<a href="javascript:void(0)" role="button" onClick={() => this.delete(item)}><span className="fa fa-trash" /></a>
</div>
),
};
};
delete(item) {
//here you would call your redux action
console.log(item);
}
download(item) {
//here you would call your redux action
console.log(item);
}
render() {
return (
<main className="items">
<TableWrapper items={this.props.items} additionalColumns={this.additionalColumns} />
</main>
);
}
}
The TableWrapper component is a functional stateless component that builds a ReactDataGrid:
const TableWrapper = ({ items, additionalColumns }) => {
const itemsWithAddColumns = items.map(additionalColumns);
const rowGetter = rowNumber => itemsWithAddColumns[rowNumber];
return (
<div>
<ReactDataGrid
rowKey="id"
columns={ExternallyDefinedColumns}
rowGetter={rowGetter}
rowsCount={recordsPlus.length}
enableRowSelect="multi"
minHeight={600}
emptyRowsView={ExternallyDefinedEmptyRowsView}
/>
</div>
);
};
So basically what happens here is you take the array that will be passed to ReactDataGrid to populate the rows and extend it with your own custom logic pointing at methods within your top level react component. This means you can then get the result of what item is selected and pass whatever data is needed to your redux layer or update your state if you aren't using redux.

How to manipulate style/atributes in React DOM

I have two components. If I hovered over one I'd like to move (by changing style proporties) the other one component.
How can I achieve that?
In pure js it's simply
let elem1 = document.querySelector('.elem1');
let elem2 = document.querySelector('.elem2');
elemt1.addEventListener('mouseover', () => {
elem2.style.right = "200px" //or any other style property
})
So.. in react we can use "ref" and this works if I define static ref
import React, {Component} from 'react';
class MainCanvas extends Component {
onHover(){
console.log(this.refs.mybtntest);
}
render(){
return(
<div>
<h1 onMouseEnter={() => this.onHover()}> Testing</h1>
<button ref="mybtntest">Close</button>
</div>
);
}
}
export default MainCanvas
However in my case I need to each component should has dynamically added "ref" atribute.. so my code looks like below
import React, {Component} from 'react';
class Test extends Component {
onHover(e, dynamicRef){
console.log(dynamicRef); //correct number of ref
console.log(this.refs.dynamicRef); //undefined
console.log(this.refBtnName); //button reference but eachtime is overrided
console.log(this.dynamicRef);//undefinded
}
render(){
const elements = this.props.elements.map( element => {
let refBtnName = element.id + "btn";
return [
<ComponentElement
onHover={(e) => this.onHover(e, refBtnName)}
key={element.id} {...element}
/>,
<button key={element.id*2}
ref={refBtnName => this.refBtnName = refBtnName} //each time he will be overrided :(
className={`${refBtnName} deleteComponentBtn`} >
Close
</button>
]
})
return(
<div>
{elements}
</div>
);
}
}
export default Test
A real goal is that I want to positioning the button relative to the element. I could use div for this purpose as a wrapper but I don't want it.
So I thought to use for example this piece of code
onHover(e, dynamicRef){
Math.trunc(e.target.getBoundingClientRect().right)
dynamicRef.style.right = `${right}px`;
}
If you need dynamic object keys you shouldn't use the dot . and instead use the brackets:
ref={ref=> this[refBtnName] = ref}
Note that i changed the inline parameter to ref instead of refBtnName so you won't get variable names conflicts.
Running example:
class App extends React.Component {
state = {
items: [
{ name: 'John', id: 1 },
{ name: 'Mike', id: 2 },
{ name: 'Jane', id: 3 },
]
}
move = refName => e => {
this[refName].style.right = '-90px';
}
render() {
const { items } = this.state;
return (
<div>
{
items.map(item => {
return (
<div key={item.id} >
<div
ref={ref => { this[item.name] = ref }}
style={{ position: 'relative' }}
>
{item.name}
</div>
<button onClick={this.move(item.name)}>Move {item.name}</button>
</div>
)
})
}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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="root"></div>

State not updating in Component

Hey I am trying to create a simple to-do list and I have added the components necessary. However, the state is not being updated in the Title {this.state.data.length} and the TodoList {this.state.data}. A Codepen and the relevant code is below.
https://codepen.io/skasliwal12/pen/BREYXK
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input ref={node => {input = node;}} />
<button onClick={(e) => {
e.preventDefault();
addTodo(input.value);
input.value='';
}}> +
</button>
</div>
);
};
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo}</li>
});
return <div> {todoNodes} </div>;
}
const Title = ({todoCount}) => {
return (
<div>
<div>
<h1>To-do App {todoCount} items</h1>
</div>
</div>
);
}
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = { data : [] }
}
addTodo(val) {
let todo = {text: val}
this.state.data.push(todo);
this.setState = ({data: this.state.data});
console.log('state updated?')
}
render(){
return (
<div>
<Title todoCount={this.state.data.length}/>
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList todos={this.state.data}/>
</div>
);
}
}
ReactDOM.render(<TestApp />, document.getElementById('root'));
Quite simply it is important that you DO NOT MUTATE the state like you are doing here
this.state.data.push(todo);
It is hard to debug and adds side effects that are hard to keep track of. Following your approach you should copy the state to a var, update that var and then pass it as the new field in your state. Which could work but it's also something I do not recommend. A general good approach is to to compute the new state based on the old one
// this.state.data.push(todo); You can remove this line
this.setState(prevState => ({ data: prevState.data.concat(todo) }))
This will fix your issue and avoid mutating the state, which is something you should never do, only update the state using the setState method.
I also updated your TodoList which was not displaying properly, you have to access the text field of the todo in order to show something.
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo.text}</li>
});
return <div> {todoNodes} </div>;
}
https://codepen.io/anon/pen/MmRVmX?editors=1010

React checkbox in stateless component is not updating immediately

I created a basic interface using checkboxes that used a react design pattern that has served me well before and that I thought worked well - namely lifting up state and passing down props to UI components. My checkbox components are passed a value(a metric), an state-changing method, and a boolean for checked. The problem is that the checkboxes do not update immediately, even though you can see them updating in the React dev tools. They only update on the next click, as in when another checkbox is checked. Here is the code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
metricsSelected: []
}
this.selectMetric = this.selectMetric.bind(this)
}
selectMetric(metric) {
const metricsSelected = this.state.metricsSelected
const index = metricsSelected.indexOf(metric)
if (index !== -1){
metricsSelected.splice(index, 1)
}
else {
metricsSelected.push(metric)
}
this.setState({
metricsSelected,
});
}
render() {
return (
<div>
<Sidebar
metricsSelected={this.state.metricsSelected}
selectMetric={this.selectMetric}/>
<SomethingElse/>
</div>
)
}
}
const SomethingElse = () => (<div><h2>Something Else </h2></div>)
const Sidebar = ({ metricsSelected, selectMetric }) => {
const metrics = ['first thing', 'second thing', 'third thing']
return (
<div>
<h3>Select Metrics</h3>
{ metrics.map( (metric, i) =>
<Checkbox
key={i}
metric={metric}
selectMetric={selectMetric}
checked={metricsSelected.includes(metric)}/>
)}
</div>
)
}
const Checkbox = ({ metric, selectMetric, checked }) => {
const onChange = e => {
e.preventDefault()
selectMetric(e.target.value)
}
return (
<ul>
<li>{metric}</li>
<li><input
type='checkbox'
value={metric}
checked={checked}
onChange={onChange} /></li>
</ul>
)
}
I've read pretty much everything I can get my hands on about checkboxes for react and most of the applications of the checkbox are doing something different from what I want to do. I've tried adding state to the Checkbox component, but that didn't seem to help, since the checked value still needs to come in from elsewhere. I thought react components rerendered when the props changed. What gives?
Here's a codepen: https://codepen.io/matsad/pen/QpexdM
Here is a working version: http://codepen.io/TLadd/pen/oWvOad?editors=1111
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
metricsSelected: {}
}
this.selectMetric = this.selectMetric.bind(this)
}
selectMetric(metric) {
this.setState(({ metricsSelected }) => ({
metricsSelected: {
...metricsSelected,
[metric]: !metricsSelected[metric]
}
}))
}
render() {
return (
<div>
<Sidebar
metricsSelected={this.state.metricsSelected}
selectMetric={this.selectMetric}/>
<SomethingElse/>
</div>
)
}
}
const SomethingElse = () => (<div><h2>Something Else </h2></div>)
const Sidebar = ({ metricsSelected, selectMetric }) => {
const metrics = ['first thing', 'second thing', 'third thing']
return (
<div>
<h3>Select Metrics</h3>
{ metrics.map( (metric, i) =>
<Checkbox
key={i}
metric={metric}
selectMetric={selectMetric}
checked={Boolean(metricsSelected[metric])}/>
)}
</div>
)
}
const Checkbox = ({ metric, selectMetric, checked }) => {
return (
<ul>
<li>{metric}</li>
<li>
<input
type='checkbox'
name={metric}
checked={checked}
onChange={() => selectMetric(metric)}
/>
</li>
</ul>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
The couple of things that were causing issues were that you were mutating state in selectMetric, and your checkbox input's onChange function is using e.target.value instead of e.target.checked.
I changed the metricsSelected state to be an object, since I think it makes the management of it quite a bit easier.

Resources