React To-Do app: Removing items, but keeping individual state - javascript

I have run into a problem with my React application. So far there is enough functionality in place to add To-Do items to the list, remove them by index and mark them done (text-decoration: line-through).
When I remove an item that is already crossed out, I would expect the other items to keep their own state, however they don't. Here's what I mean.
Let's remove the crossed out item
Why is the bottom one crossed out now?
Here's my code
ToDoApp.js
import React from 'react';
import Header from './Header';
import AddToDo from './AddToDo';
import FilterToDo from './FilterToDo';
import ToDoList from './ToDoList';
import ListButtons from './ListButtons';
export default class ToDoApp extends React.Component {
state = {
toDos: []
};
handleAddToDo = (toDo) => {
if (!toDo) {
return "Nothing was added!";
}
this.setState((prevState) => ({
toDos: prevState.toDos.concat([toDo])
}));
};
handleRemoveToDo = (removeIndex) => {
this.setState((prevState) => ({
toDos: prevState.toDos.filter((toDo, index) => index !== removeIndex)
}));
};
render() {
return (
<div>
<Header />
<AddToDo
handleAddToDo={this.handleAddToDo}
/>
<FilterToDo />
<ToDoList
toDos={this.state.toDos}
handleRemoveToDo={this.handleRemoveToDo}
/>
<ListButtons />
</div>
);
};
};
ToDoList.js
import React from 'react';
import ToDoListItem from './ToDoListItem';
const ToDoList = (props) => (
<div>
<h3>To Do List</h3>
<div>
{props.toDos.map((toDo , index) => (
<ToDoListItem
key={index}
index={index}
toDoTitle={toDo}
handleRemoveToDo={props.handleRemoveToDo}
/>))}
</div>
</div>
);
export default ToDoList;
ToDoListItem.js
import React from 'react';
export default class ToDoListItem extends React.Component {
state = {
done: false
};
handleDoneTrigger = () => {
this.setState((prevState) => ({ done: !prevState.done }));
};
render() {
return (
<div>
<p
className={this.state.done ? "done" : ""}
>{this.props.toDoTitle}</p>
<button onClick={(e) => {
this.props.handleRemoveToDo(this.props.index)
}}>Remove</button>
<button onClick={this.handleDoneTrigger}>Done</button>
</div>
);
}
};

The problem is with this piece of code:
<ToDoListItem
key={index}
index={index}
toDoTitle={toDo}
handleRemoveToDo={props.handleRemoveToDo}
/>))}
as you set the index as key of ToDoListItem. Instead of index assign some unique key to each element because when you delete an item its index assigned to following item in the list.
This will be helpful to dig more into deep: https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318

Related

React: Render and link toggle button outside the class

I have the following example where the toggleComponent.js is working perfectly.
The problem here is that I don't want to render the <ContentComponent/> inside the toggle, rather I want the opposite, I want to toggle the <ContentComponent/> that will be called in another component depending on the state of the toggle.
So the <ContentComponent/> is outside the toggleComponent.js, but they are linked together. So I can display it externally using the toggle.
An image to give you an idea:
Link to funtional code:
https://stackblitz.com/edit/react-fwn3rn?file=src/App.js
import React, { Component } from "react";
import ToggleComponent from "./toggleComponent";
import ContentComponent from "./content";
export default class App extends React.Component {
render() {
return (
<div>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && <h1>test</h1>}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && (
<h1>
<ContentComponent />
</h1>
)}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
</div>
);
}
}
Bit tweaked your source.
Modified ToggleComponent
import React from "react";
export default class ToggleComponent extends React.Component {
constructor() {
super();
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
this.setState({ checked: !this.state.checked });
this.props.toggled(!this.state.checked);
};
checkbox = () => {
return (
<div>
<label>Toggle</label>
<span className="switch switch-sm">
<label>
<input type="checkbox" name="select" onClick={this.handleClick} />
<span />
</label>
</span>
</div>
);
};
render() {
return this.checkbox();
}
}
Added OtherComponent with ContentComponent inside.
import React, { Component } from "react";
import ContentComponent from "./content";
export default class OtherComponent extends React.Component {
render() {
return <div>{this.props.show ? <ContentComponent /> : null}</div>;
}
}
Separated as per your requirement.
Modified App
import React, { Component, PropTypes } from "react";
import ToggleComponent from "./toggleComponent";
import OtherComponent from "./otherComponent";
export default class App extends React.Component {
constructor() {
super();
this.toggled = this.toggled.bind(this);
this.state = { show: false };
}
toggled(value) {
this.setState({ show: value });
}
render() {
return (
<div>
<ToggleComponent toggled={this.toggled} />
<OtherComponent show={this.state.show} />
</div>
);
}
}
Working demo at StackBlitz.
If you want to share states across components a good way to do that is to use callbacks and states. I will use below some functional components but the same principle can be applied with class based components and their setState function.
You can see this example running here, I've tried to reproduce a bit what you showed in your question.
import React, { useState, useEffect, useCallback } from "react";
import "./style.css";
const ToggleComponent = props => {
const { label: labelText, checked, onClick } = props;
return (
<label>
<input type="checkbox" checked={checked} onClick={onClick} />
{labelText}
</label>
);
};
const ContentComponent = props => {
const { label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Holder = () => {
return (
<div>
<ContentComponent label="First">
{checked => (
<h1>First content ({checked ? "checked" : "unchecked"})</h1>
)}
</ContentComponent>
<ContentComponent
label="Second"
render={checked => (checked ? <h1>Second content</h1> : null)}
/>
</div>
);
};
PS: A good rule of thumb concerning state management is to try to avoid bi-directional state handling. For instance here in my example I don't use an internal state in ToggleComponent because it would require to update it if given checked property has changed. If you want to have this kind of shared state changes then you need to use useEffect on functional component.
const ContentComponent = props => {
const { checked: checkedFromProps, label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(checkedFromProps || false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
// onChange callback
useEffect(() => {
if (onChange) {
onChange(checked);
}
}, [ checked, onChange ]);
// update from props
useEffect(() => {
setChecked(checkedFromProps);
}, [ checkedFromProps, setChecked ]);
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Other = () => {
const [ checked, setChecked ] = useState(true);
return (
<div>
{ checked ? "Checked" : "Unchecked" }
<ContentComponent checked={checked} onChange={setChecked} />
</div>
);
};

Having Trouble Removing Item From Array in React

Upon loading the page, data is retrieved from my API and stored in an array. It then displays a button for each object in the array and titles is appropriately.
What needs to happen next is when I click a button, that button disappears, and the associated item removed from the array. That part is not working. The button is registering the click, and the function is running. However the button does not disappear, and when I log the array again, the array appears to be unchanged. I cannot figure out why. Can anyone help me spot the issue?
Here is my code, the part in question is the "handleAddPolicy" function:
import React, { Component, Fragment } from 'react';
import PolicyButton from './PolicyButton/PolicyButton';
class Handbook extends Component {
constructor(props){
super(props);
this.state = {
clients: [],
policies: [],
client: 'Choose Client',
logo: '',
color1: '#000',
color2: '#fff'
};
}
componentDidMount() {
fetch('/api/themes/all')
.then(res => res.json())
.then((result) => {
this.setState({ clients: result });
console.log(result);
})
.then(
fetch(`/api/policy/all`)
.then(res => res.json())
.then((result) => {
this.setState({ policies: result });
console.log(result);
})
);
}
handleAddPolicy = policyId => {
console.log(`button clicked`);
const policies = this.state.policies.filter(policy => policy.id !== policyId);
this.setState({ policies: policies});
console.log(this.state.policies);
}
render(){
return(
<Fragment>
{/* <ClientPicker /> */}
<div className='buttons'>
{this.state.policies.map(policy => (
<PolicyButton key={policy.id} policy={policy.policy} onAddPolicy={this.handleAddPolicy} />
))}
</div>
</Fragment>
);
}
}
export default Handbook;
And here is code for my button that should disappear after being clicked if it helps:
import React, { Component } from 'react';
import styled from 'styled-components';
class PolicyButton extends Component {
state = {
id: this.props.id,
policy: this.props.policy
}
render(){
return(
<button onClick={() => this.props.onAddPolicy(this.props.id)}>{this.props.policy}</button>
)
}
}
export default PolicyButton;
You missed the id prop when rendering PolicyButton
<Fragment>
{/* <ClientPicker /> */}
<div className='buttons'>
{this.state.policies.map(policy => (
<PolicyButton
key={policy.id}
/* This is what you missed */
id={policy.id}
policy={policy.policy}
onAddPolicy={this.handleAddPolicy}
/>
))}
</div>
</Fragment>

How can i pass props through methods inside components?

i have a react component named "List" that renders smaller components "Post" using a button through method "Addpost()" that takes 2 props from the input form. I have saved the input in 2 varables but i don't know how to pass these props to the Addpost() method inside the return of List's render().
//=========== List component ==============
class List extends React.Component{
renderPost(title,content){
return(
<Post titolo={title} contenuto={content}/>
);
}
renderPost just render the Post component in a in the HTML
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" id="inputTitle"/><br></br>
Content:<br></br>
<input type="text" id="inputContent"/>
</form><br></br>
<button className="square"
how can i make this work? title and content are not defined
onClick={() =>
this.addPost(title,content)
Add Post!
</button>
</div>
);
}
}
//=========== Post component ==============
class Post extends React.Component {
render() {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{this.props.titolo}
</span><br></br>
<span>{this.props.contenuto}</span>
</li>
);
}
}
Basically, whenever you're dealing with forms and inputs, you would use refs.
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import PostList from './components/PostList'
import AddPostForm from './components/AddPostForm'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
posts: [] //state is handled here
}
this.addPost = this.addPost.bind(this)
}
addPost(title, content) {
let newPost = { title, content }
this.setState(({ posts }) => { return { posts: [...posts, newPost] } } )
}
render() {
const { posts } = this.state
return (
<div>
<AddPostForm onNewPost={this.addPost} /> //we pass addPost to the component
<br />
<PostList posts={posts} />
</div>
);
}
}
export default App;
Post.js
import React from 'react';
function Post({titolo, contenuto}) {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{titolo}
</span><br></br>
<span>{contenuto}</span>
</li>
);
}
export default Post
AddPostForm.js
import React from 'react';
const addPostForm = ({onNewPost = f => f}) => { //onNewPost method is passed by props from the parent
let _titleInput, _contentInput //these are our refs, see the docs for more information
const submit = (e) => {
e.preventDefault()
onNewPost(_titleInput.value, _contentInput.value) //here we call the addPost function that was passed to the component
_titleInput.value = '' //empty the inputs
_contentInput.value = ''
_titleInput.focus() //set focus
}
return (
<form onSubmit={submit}>
Title:<br></br>
<input type="text" ref={title => _titleInput = title} /><br></br>{/* Note the ref attribute */}
Content:<br></br>
<input type="text" ref={content => _contentInput = content} />
<button className="square">Add a new post</button>
</form>
)
}
export default addPostForm
PostList.js
import React from 'react';
import Post from './Post';
const PostList = ({ posts=[] }) => {
return (
<div className="post-list">
{
posts.map((post, index) =>
<Post key={index} titolo={post.title} contenuto={post.content} />
)
}
</div>
)
}
export default PostList
And the result:
edit
renderPost just render the Post component in a in the HTML
state = { inputTitle: '', inputContent: '' }
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post
component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" value={this.inputTitle} onChnage={event => setState({ inputTitle: event.target.value }) }><br></br>
Content:<br></br>
<input type="text" value={this.inputContent} onChnage={event => setState({ inputContent: event.target.value }) } />
</form><br></br>
<button className="square"
on click function
onClick={() =>
this.addPost(this.inputTitle,this.inputContent)
Add Post!
</button>
</div>
);
}
}

How to create a simple list maker app in React.JS?

I'm working on a simple list maker, to do list app using create-react-app and I'm having some trouble puzzling out the functionality. What I'm trying to accomplish with this app:
I want to be able to enter text into an input, push the button or press enter, and whatever text will be listed on the body of the app.
I want to be able to create a button that will delete the list items once the task or objective is complete
My code is broken up into these components so far:
App,
ListInput,
ItemList,
Item
The code for App is
import React, { Component } from 'react';
import './App.css';
import Navigation from './components/Navigation';
import ListInput from './components/ListInput';
import ListName from './components/ListName';
import Item from './components/Item';
import ItemList from './components/ItemList';
class App extends Component {
constructor() {
super();
this.state = {
input: '',
items: []
};
}
addItem = () => {
this.setState(state => {
let inputValue = this.input.current.value;
if (inputValue !== '') {
this.setState({
items: [this.state.items, inputValue]
})
}
})
}
onButtonEnter = () => {
this.addItem();
}
render() {
return (
<div className="App">
<Navigation />
<ListName />
<ListInput addItem={this.addItem}
onButtonEnter={this.onButtonEnter} />
<Item />
<ItemList />
</div>
);
}
}
export default App;
The code for ListInput is :
import React from 'react';
import './ListInput.css';
const ListInput = ({ addItem, onButtonEnter }) => {
return (
<div>
<p className='center f2'>
{'Enter List Item'}
</p>
<div className='center'>
<div className='center f3 br-6 shadow-5 pa3 '>
<input type='text'
className='f4 pa2 w-70 center'
placeholder='Enter Here'
/>
<button className='w-30 grow f4 link ph3 pv2 dib white bg-black'
onClick={onButtonEnter}
onSubmit={addItem} >
{'Enter'}
</button>
</div>
</div>
</div>
);
}
export default ListInput;
The code for Item is:
import React from 'react';
const Item = ({text}) =>{
return (
<div>
<ul>{text}</ul>
</div>
)}
export default Item;
And the code for ItemList is :
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{item.map(items => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
In my react app I am returning an error of 'item' is not defined and I'm confused why.
In your App.js you need to pass items as a prop to ItemList component like
<ItemList items={this.state.items} />
Also in addItem function pushing inputValue to items array isn’t correct do something like below
addItem = () => {
this.setState(state => {
let inputValue = this.input.current.value;
if (inputValue !== '') {
this.setState(prevState => ({
items: [...prevState.items, inputValue]
}));
}
})
}
And in ItemList.js do conditional check before doing .map also some typo errors in .map
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{items && items.map(item => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
Try with above changes This would work
Please excuse me if there are any typo errors because I am answering from my mobile
Your ItemList was not correct. Take a look at corrected snippet below you need to map on items and not item (hence the error item is not defined). Also, you need to items as a prop to ItemList in your app.js
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{items.map(item => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
In app.js add following line. Also, I don't see what is doing in your app.js remove it.
<ItemList items={this.state.items}/>
Seems like you have a typo in ItemList.
It receives items (plural) as prop but you are using item.
const ItemList = ({ items }) => {
return (
<div>
{items.map(items => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
And don't forget to actually pass the items prop to 'ItemList':
<ItemList items={this.state.items} />

Changing specific index of an array in state instead of all of my state object

I'm doing a React refresher. I set state in App.js and created an event called handleUserNameChange() to change usernames in the state object. Each input from UserInput.js should change it's relative UserOutput component's username in state that's set in App.js. How can I make so that when I type text into one input it only changes one index in my users array in state? Do I need to change my handleUserNameChange event?
App.js
import React, { Component } from 'react';
//Components
import UserInput from './UserInput/UserInput';
import UserOutput from './UserOutput/UserOutput';
class App extends Component {
state = {
users: [
{user: 'Debbie'},
{user: 'Ronald'}
]
};
handleUserNameChange = (event) => {
this.setState({
users: [
{user: event.target.value},
{user: event.target.value}
]
});
};
render() {
return (
<div className="App">
<UserOutput
username = {this.state.users[0].user}
/>
<UserInput
nameChange={this.handleUserNameChange} />
<UserOutput
username={this.state.users[1].user}
/>
<UserInput
nameChange={this.handleUserNameChange}
/>
</div>
);
}
}
export default App;
UserOuput.js
import React from 'react';
const UserOutput =(props) => {
return (
<div>
<h3>{props.username}</h3>
</div>
);
}
export default UserOutput;
UserInput.js
import React from 'react';
const UserInput = (props) => {
return (
<div>
<input type="text"
onChange={props.nameChange}
/>
</div>
);
}
export default UserInput;
In App.js:
<UserInput
nameChange={this.handleUserNameChange(0)}//0 for first, 1 for second
/>
handleUserNameChange = (index) => (event) => {
this.setState({
users: this.state.users.map(
(user,i)=>
(i===index)
? event.target.value
: user
)
});
};
It would probably be better to not hardcode user 0 and user 1 but just map the state to react modules.
render() {
const userInput = index =>
<UserInput
nameChange={this.handleUserNameChange(index)} />;
const UserOutput = user =>
<UserOutput
username = {user}/>;
return (
<div className="App">
this.state.users.map(
(user,index)=>
<div>{userInput(index)}{UserOutput(user)}</div>
)
</div>
);
}
pass the index value in handleUserNameChange function from render function and use double arrow in handleUserNameChange to get the index value.
handleUserNameChange = index => event => {
this.setState(prevState => {
const users = [...prevState.users];
users[index].user = event.target.value;
return { users };
});
};
render() {
return (
<div className="App">
{this.state.users.map((user, index) => (
<React.Fragment>
<UserOutput username={user} />
<UserInput nameChange={this.handleUserNameChange(index)} />
</React.Fragment>
))}
</div>
);
}

Categories

Resources