too many re re-renders error on remove todo - reactjs

import React, { useState, useEffect } from "react";
import "./style.css";
function App() {
const [movieData, setMovieData] = useState();
const [directorData, setDirectorData] = useState();
const [listData, setListData] = useState([]);
const submitHandler = () => {
setListData([
...listData,
{
movie: movieData,
director: directorData,
},
]);
setMovieData("");
setDirectorData("");
};
const removeHandler = (id) => {
const newlist = listData.filter((remId) => {
return remId !== id;
});
setListData(newlist)
};
return (
<div className="App">
<div>
<h1>Todo App</h1>
</div>
<div className="form">
<div>
<label>Movie</label>
<input
type="text"
placeholder="type items"
value={movieData}
onChange={(e) => setMovieData(e.target.value)}
/>
</div>
<div>
<label>Director</label>
<input
type="text"
placeholder="type items"
value={directorData}
onChange={(e) => setDirectorData(e.target.value)}
/>
</div>
<div>
<button onClick={submitHandler}>Submit</button>
</div>
</div>
<div>
{listData.map((item, index) => {
return (
<li key={index}>
<span>{item.movie}</span>, <span>{item.director}</span>{" "}
<span style={{ marginLeft: "2rem" }}>
<button onClick={removeHandler(index)} style={{ color: "red" }}>
X
</button>
</span>
</li>
);
})}
</div>
</div>
);
}
export default App;
As soon as I added removeHandler, I am getting too many renders onSubmit. It's working fine once I remove removeHandler.
Is there any other method to remove items from the list apart from filter and splice(just mention the function name)?
I have even tried using useEffect but the same problem persists

Change to onClick={() => removeHandler(index)}
and you also have to remove 'return' in your array.filter
remId !== id;
});

This happens because you're not passing an arrow function to onClick.
You're writing onClick={removeHandler(index)} instead of onClick={() => removeHandler(index)}.
It's because functionName(param) syntax is generally used to call functions so when you write that, it causes infinite renders.

use this function some new changes
const removeHandler = (id) => { const newlist = [...listData];
newlist.splice(id, 1); setListData(newlist); };
and change onclick event
onClick={() => removeHandler(index)}

Related

How can I send the data to the parent component by click on the button in React?

My question is how can I send the input value to the parent component by clicking on the button? Because now if I type something in the input it shanges the value instantly, I want it to do after I click on the button.
Currently I am using that method:
const FormInput = ({setIpAddress}) => {
return (
<div className="formInput">
<form className="form_container" onSubmit={e => {e.preventDefault();}}>
<input type="text" id="input" onChange={(e) => setIpAddress(e.target.value)} required={true} placeholder="Search for any IP address or domain"/>
<button type="submit" className="input_btn">
<img src={arrow} alt="arrow"/>
</button>
</form>
</div>
);
};
export default FormInput
You can pass an onClick callback function to the child component. When this function is called it will trigger a rerender in the child.
Example:
Parent:
const handleClick = (value) => {
//set the state here
}
<ChildComponent onClick={handleClick} />
Child:
<button type="submit" className="input_btn" onClick={(value) => props.onClick?.(value)}>
In your case you need to get rid of the onChange in your input tag:
parents:
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
try {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip);
});
};
getData();
} catch (error) {
console.trace(error);
}
}, [url]);
const handleClick = (event) => {
setIpAddress(event.target.value)
}
return (
<div className="App">
<SearchSection onClick={handleClick} />
</div>
);
}
const SearchSection = ({onClick}) => {
return (
<div className="search_container">
<h1 className="search_heading">IP Address Tracker</h1>
<FormInput onClick={onClick}/>
</div>
);
};
Child
const FormInput = ({onClick}) => {
return (
<div className="formInput">
<form className="form_container" onSubmit={e => {e.preventDefault();}}>
<input type="text" id="input" required={true} placeholder="Search for any IP address or domain"/>
<button type="submit" className="input_btn" onClick={(e) => onClick(e}>
<img src={arrow} alt="arrow"/>
</button>
</form>
</div>
);
};
Thank you for your answer, but I don't really get it, bcs my parent component has no paramter, sorry I am new in react.
This is my parent component where I am fetching the data and I want to update the ipAdress when I click on the button which is in the FormInput component. So the SearchSection is the parent of the FormInput.
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip)
...
getData();
}, [url]);
return (
<div className="App">
<SearchSection setIpAddress={setIpAddress} />
</div>
);
}
I hope it's enough :)
const SearchSection = ({setIpAddress}) => {
return (
<div className="search_container">
<h1 className="search_heading">IP Address Tracker</h1>
<FormInput setIpAddress={setIpAddress}/>
</div>
);
};
function App() {
const [ipAddress, setIpAddress] = useState("");
const url = `${BASE_URL}apiKey=${process.env.REACT_APP_API_KEY}&ipAddress=${ipAddress}`;
useEffect(() => {
try {
const getData = async () => {
axios.get(url).then((respone) => {
setIpAddress(respone.data.ip);
});
};
getData();
} catch (error) {
console.trace(error);
}
}, [url]);
return (
<div className="App">
<SearchSection setIpAddress={setIpAddress} />
</div>
);
}

How to render form after submission in react?

Given the following form, I need whenever the form is submitted, the new post to be listed/rendered without having to refresh the page.
const PostCreate = () => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I tried adding this.forceUpdate() and this.setState(this.state), neither works, and I still have to refresh the page for the new post to show.
Here's how the posts are rendered:
const PostList = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
This is what App.js looks like
const App = () => {
return <div>
<h1>Create Post</h1>
<PostCreate></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList></PostList>
</div>;
};
export default App;
and is eventually rendered using:
ReactDOM.render(
<App></App>,
document.getElementById('root')
)
In your PostList, useEffect called once when you first load your component, so when you create new post, it will not be re-rendered
You should bring your fetchPost logic to your App component, and add function props onPostCreated to PostCreate component, trigger it after you finish creating your new post
The code should be:
const App = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
return <div>
<h1>Create Post</h1>
<PostCreate onCreatePost={() => fetchPost()}></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList posts={posts}></PostList>
</div>;
};
export default App;
const PostList = ({ posts }) => {
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
const PostCreate = ({ onCreatePost }) => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
onCreatePost && onCreatePost();
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I think the problem you are having is not in the code you have displayed. The component is indeed rerendering after you change its state and also when you forceUpdate() it. I assume the posts you are trying to display are taken from the same API that you post to. Even if this component is being rerendered, your GET request which gives the data to the component who renders it is not called again so the data doesn't update. You need to refetch it. This can be done by many different ways (useEffect(), callbacks, reactQuery refetch) depending on the rest of your code. I would need the component that renders the data and the API call to help you further.
Another thing that you didn't ask but is good practice. In your PostCreate component you don't need to manage the state of fields that are in the form, because it already does it for you. Just give a name to your inputs and use the form data. I've given an example below.
import { useState } from "react";
const PostCreate = () => {
const onSubmit = async (event) => {
event.preventDefault();
console.log(event.target.elements.title.value);
};
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input name="title" className="form-control" />
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>
);
};
export default PostCreate;

Next JS - Checkbox Select All

I'm a beginner in Next Js. and I'm trying to implement select all on the checkboxes.
I following this reference https://www.freecodecamp.org/news/how-to-work-with-multiple-checkboxes-in-react/
what I expect is, if the checkbox select all is checked then sum all the prices.
but I don't know how to start it.
Here is my sandbox https://codesandbox.io/s/eager-feather-2ieme9
Any help with this would be greatly appreciated, been working on this for a while and exhausted all avenues!
You can check the below logic with some explanation
You also can check this sandbox for the test
import { useState } from "react";
import { toppings } from "./utils/toppings";
// import InputTopings from "./InputTopings";
const getFormattedPrice = (price) => `$${price.toFixed(2)}`;
export default function TopingApp() {
const [checkedState, setCheckedState] = useState(
new Array(toppings.length).fill(false)
);
// console.log(checkedState);
const [total, setTotal] = useState(0);
//Separate `updateTotal` logic for avoiding duplication
const updateTotal = (checkboxValues) => {
const totalPrice = checkboxValues.reduce((sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
}, 0);
setTotal(totalPrice);
};
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
//update total
updateTotal(updatedCheckedState);
};
const handleSelectAll = (event) => {
//filled all checkboxes' states with `Check All` value
const updatedCheckedState = new Array(toppings.length).fill(
event.target.checked
);
setCheckedState(updatedCheckedState);
//update total
updateTotal(updatedCheckedState);
};
return (
<div className="App">
<h3>Select Toppings</h3>
<div className="call">
<input
type="checkbox"
name="checkall"
checked={checkedState.every((value) => value)}
onChange={handleSelectAll}
/>
<label htmlFor="checkall">Check All</label>
</div>
<ul className="toppings-list">
{toppings.map(({ name, price }, index) => {
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
type="checkbox"
// id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
<label>{name}</label>
</div>
<div className="right-section">{getFormattedPrice(price)}</div>
</div>
</li>
);
})}
<li>
<div className="toppings-list-item">
<div className="left-section">Total:</div>
<div className="right-section">{getFormattedPrice(total)}</div>
</div>
</li>
</ul>
</div>
);
}
You can lift the 'Check all' state to a parent object and on change of this state set all <input/> tags value to that state you can achieve this by dividing your app. First create a component for the items in the list like <Toppings/> and give props to this component like so <Toppings name={name} price={price} checkAll={checkAll}/> inside the toppings component create a state variable like this
const Toppings = ({name,price, checkAll}) => {
const [checked,setChecked] = useState(checkAll)
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
type="checkbox"
// id={`custom-checkbox-${index}`}
name={name}
value={checked}
onChange={setChecked(!checked)}
/>
<label>{name}</label>
</div>
</div>
</li>
)
}
Edit:
inside index.js:
const [checkAll, setCheckAll] = useState(false)
//when rendering inside return method
{toppings.map(({name,price},index) => <Toppings key={index} name={name} price={price} checkAll={checkAll}/> )

CRUD in React, update item using useState not working

I am trying to create a CRUD using React Hooks but I am having some problems on the updateItem function.
The first input using useState works perfectly (I can type inside the input) but when I click Rename Item, the second input appears but I can’t type inside and doesn’t log any errors from console.
Here is my code:
import ReactDOM from 'react-dom';
import React, { useState } from 'react';
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: 'Test' }]);
//Inputs
const [input, setInput] = useState('');
const [newInput, setNewInput] = useState('');
const [edit, setEdit] = useState();
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value }
setList([...list, obj])
}
function deleteItem(id) {
setList(list.filter(item => item.id !== id));
}
function updateItem(id) {
setEdit(
<div>
//I can't type anything in here
<input type="text" value={newInput} onChange={e => setNewInput(e.target.value)} />
<button onClick={() => {
let array = [...list];
array.map((item, i) => {
if (item.id === id) array[i] = { id, newInput }
})
setList([...array])
setEdit('') //Remove the edit from the DOM
}}>Rename</button>
</div>
)
}
return (
<div>
<input type="text" value={input} onChange={e => setInput(e.target.value)} />
<button onClick={() => createItem(input)}>Add Item</button>
{list.map(item => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => updateItem(item.id)}>Rename Item</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
</div>
))}
{edit}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
Try to use setEdit to show/hide through boolean value. You will have ability to type in the input
import ReactDOM from "react-dom";
import React, { useState } from "react";
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: "Test" }]);
//Inputs
const [input, setInput] = useState("");
const [newInput, setNewInput] = useState("");
const [edit, setEdit] = useState(false);
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value };
setList([...list, obj]);
}
function deleteItem(id) {
setList(list.filter(item => item.id !== id));
}
function updateItem() {
setEdit(true);
}
return (
<div>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
/>
<button onClick={() => createItem(input)}>Add Item</button>
{list.map(item => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => updateItem(item.id)}>Rename Item</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
{edit && (
<div>
//I can't type anything in here
<input
type="text"
value={newInput}
onChange={e => {
setNewInput(e.target.value);
}}
/>
<button
onClick={() => {
let array = [...list];
array.map((o, i) => {
if (o.id === item.id) array[i] = { id: item.id, newInput };
});
setList([...array]);
setEdit(false); //Remove the edit from the DOM
}}
>
Rename
</button>
</div>
)}
</div>
))}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
The problem is with how you run updateItem() function.
You are using the following call <button onClick={() => updateItem(item.id)}>Rename Item</button>
In updateItem(), you do a setEdit and it adds an input box and button to your screen.
But now, you have trouble updating the input box. This is because the input box will only be updated when you run the updateItem() function again.
So in order for your input box to be updated, you would have to run setEdit again
Here is a solution of how to put it in your main component so that the input box updates with each render
import ReactDOM from "react-dom";
import React, { useState } from "react";
function App() {
//List with one item
const [list, setList] = useState([{ id: Math.random() + 1, name: "Test" }]);
//Inputs
const [input2, setInput2] = useState("");
const [newInput, setNewInput] = useState("");
const [edit, setEdit] = useState(null);
function createItem(value) {
if (!value.trim()) return;
let obj = { id: Math.random() + 1, name: value };
setList([...list, obj]);
}
function deleteItem(id) {
setList(list.filter((item) => item.id !== id));
}
function renameItem() {
// Rename item
alert("Rename item " + edit + " to " + newInput);
setEdit(null);
setNewInput(null);
}
return (
<div>
<input
type="text"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button onClick={() => createItem(input2)}>Add Item</button>
{list.map((item) => (
<div key={item.id}>
<p>{item.name}</p>
<button onClick={() => setEdit(item.id)}>
Rename Item {item.id}
</button>
<button onClick={() => deleteItem(item.id)}>Delete Item</button>
</div>
))}
{edit && (
<div>
<input
type="text"
value={newInput}
onChange={(e) => setNewInput(e.target.value)}
/>
<button onClick={() => renameItem()}>Rename</button>
</div>
)}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
There are other better solutions, such as turning the {edit && ...} part to an component. This should get your code working for now.
I also left out the code to actually rename the item, you should be able to do that yourself also.

Delete from state in react

I'm facing a problem when I try to delete an item from an array in react state, only one element remains in the array containing all the other item's contents.
Here is a sandbox describing the problem.
Has anyone an idea for that behaviour ?
App.js
import React, { useEffect, useState } from "react";
import { Button, Input } from "semantic-ui-react";
import "./App.css";
import Item from "./components/Item";
function App() {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const [emptyError, setEmptyError] = useState(false);
const handleClick = () => {
if (todo.length === 0) {
setEmptyError(true);
} else {
todos.push(todo);
setTodos(todos);
setTodo("");
}
};
const handleChange = (e) => {
setTodo(e.target.value);
if (e.target.value.length > 0) {
setEmptyError(false);
}
};
const handleDelete = (e) => {
e.stopPropagation();
console.log(e.target.parentNode.id)
setTodos(todos.filter((item, index) => index !== +e.target.parentNode.id));
};
return (
<div className="App">
<div className="inputArea">
{" "}
<input
class="form-control"
onChange={handleChange}
value={todo}
type="text"
placeholder="Enter todo"
></input>{" "}
<button onClick={handleClick} type="button" class="btn btn-dark">
Add todo
</button>
</div>
{!emptyError ? (
""
) : (
<div className="error"> Todo can't be empty, please add a todo </div>
)}
<div className="todoList">
{todos.map((item, index) => {
return <Item text={item} delete={handleDelete} id={index} />;
})}
</div>
</div>
);
}
export default App;
You need to remove bracket in setTodos.
id of the element is a string. It has to be converted to number if you compare them with ===.
setTodos([todos.filter((item, index) => index !== e.target.parentNode.id)]);
to be:
setTodos(todos.filter((item, index) => index !== +e.target.parentNode.id));
you need convert string to number
you can do it with the help of the Number constructor
setTodos( todos.filter((item, index) => index!==Number(e.target.parentNode.id))

Resources