I am adding a Item Component in the list when user click on Add Item button. I am passing a removeItem function to the Item component, so when user clicks on the Cancel button then i can remove that particular Item component from list. But the problem is when i use removeItem function and check the items.length it does not show the exact items length. Suppose i added four Items and i click on the most bottom item then it will show 0 length, if i click on second last then it will show 1 length. But what i want it must show 4 length.
import React, { useState } from "react";
import "./App.module.css";
const DUMMY_DATA = [];
function App() {
const [items, setItem] = useState(DUMMY_DATA);
function removeItem() {
console.log(items.length);
}
function addNewItem() {
setItem((prevItems) => {
return [
<Item key={Math.random()} cancelItem={removeItem} />,
...prevItems,
];
});
}
return (
<div>
<button onClick={addNewItem}>Add New Item</button>
{items.map((item) => item)}
</div>
);
}
function Item(props) {
return (
<div>
<input type="text" name="" id="" />
<button onClick={props.cancelItem}>Cancel</button>
</div>
);
}
export default App;
this is happen because when you add your item component to the list, it save the actual length of item that why you see in each component different length.
so instead try this:
import React, { useState } from "react";
import "./App.module.css";
const DUMMY_DATA = [];
function App() {
const [items, setItem] = useState(DUMMY_DATA);
function removeItem() {
console.log(items.length);
}
function addNewItem() {
setItem((prevItems) =>[...prvItems,{id:Math.random()}])
}
return (
<div>
<button onClick={addNewItem}>Add New Item</button>
{items.map((item) => <Item key={item.id} cancelItem={removeItem} />)}
</div>
);
}
function Item(props) {
return (
<div>
<input type="text" name="" id="" />
<button onClick={props.cancelItem}>Cancel</button>
</div>
);
}
export default App;
Related
NOTE: You can view and edit the code in CodeSandbox.
I have the following parent file which creates a useState list of child component called ProgressBar:
import React, { useState } from 'react';
import ProgressBar from './ProgressBar';
import './App.css';
var idCounter = 0;
export default function App() {
const [barsArray, setBarsArray] = useState([]);
const [input, setInput] = useState('');
function add() {
setBarsArray((prev) => [
...prev,
<ProgressBar key={idCounter++} restart={false} />,
]);
}
function remove() {
setBarsArray((prev) => prev.filter((bar) => bar.key !== input));
}
function reset() {
setBarsArray((prev) =>
prev.map((bar) => (bar.key === input ? { ...bar, restart: true } : bar))
);
}
return (
<div className='App'>
<div className='buttons'>
<button className='button-add' onClick={add}>
Add
</button>
<button className='button-delete' onClick={remove}>
Delete
</button>
<button className='button-delete' onClick={reset}>
Reset
</button>
<input
type='number'
value={input}
onInput={(e) => setInput(e.target.value)}
/>
</div>
<div className='bars-container'>
{barsArray.map((bar) => (
<div className='bars-index' key={bar.key}>
{bar}
<p>{bar.key}</p>
</div>
))}
</div>
</div>
);
}
The file of the child ProgressBar has the following content:
import React, { useEffect, useState } from 'react';
import './ProgressBar.css';
export default function ProgressBar(props) {
const [progress, setProgress] = useState(0);
let interval;
useEffect(() => {
interval = setInterval(() => {
setProgress((prev) => prev + 1);
}, RnadInt(10, 120));
}, []);
useEffect(() => {
if (progress >= 100) clearInterval(interval);
}, [progress]);
if (props.restart === true) {
setProgress(0);
}
return (
<>
<div className='ProgressBarContainer'>
<div className='ProgressBar' style={{ width: progress + '%' }}></div>
</div>
</>
);
}
function RnadInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
My problem is that the reset button in the parent doesn't work, as far as I'm concerned, if you change the passed props to the child, the child automatically re-renders, but even though I'm updating the props in reset function in the parent, which maps the old array of child components to a new array and only changes the props of the selected child.
Thanks!
Adding the element in state via add would require to keep the ref of element instead of actual prop bind to element. Suggestion here to use the model object and while rendering use the JSX element.
Please use the below code which defines the barsArray as object state and later uses it render ProgressBar component (from map call).
Check the working codesandbox - https://codesandbox.io/s/admiring-glitter-j3b7sw?file=/src/App.js:0-1446
import React, { useState } from "react";
import ProgressBar from "./ProgressBar";
import "./App.css";
var idCounter = 0;
export default function App() {
const [barsArray, setBarsArray] = useState([]);
const [input, setInput] = useState("");
function add() {
setBarsArray((prev) => [...prev, { id: idCounter++, restart: false }]);
}
function remove() {
setBarsArray((prev) =>
prev.filter((bar) => bar.id.toString() !== input.toString())
);
}
function reset() {
setBarsArray((prev) =>
prev.map((bar) => {
return bar.id.toString() === input.toString()
? { ...bar, restart: true }
: { ...bar };
})
);
}
return (
<div className="App">
<div className="buttons">
<button className="button-add" onClick={add}>
Add
</button>
<button className="button-delete" onClick={remove}>
Delete
</button>
<button className="button-delete" onClick={reset}>
Reset
</button>
<input
type="number"
value={input}
onInput={(e) => setInput(e.target.value)}
/>
</div>
<div className="bars-container">
{barsArray.map((bar) => (
<div className="bars-index" key={bar.id}>
<ProgressBar key={bar.id} restart={bar.restart} />
<p>{bar.key}</p>
</div>
))}
</div>
</div>
);
}
This simple fix worked for me, in the file of the child, call the conditional expression in a useEffect hook. Currently, it doesn't listen to the changes in props.restart and only runs on the initial render.
check https://reactjs.org/docs/hooks-reference.html#useeffect
useEffect(() => {
if (props.restart === true) {
setProgress(0);
}
}, [props.restart])
link to working codesandbox https://codesandbox.io/s/distracted-gauss-24d0pu
I have this component which adds a div and its elements to the dom on button click. The adding part works fine as expected but the issue arises when I want to delete.
Right now when I click on the delete button, it does remove the item but it doesn't remove that specific item which the button is associated with. It just removes the div from the top or bottom.
I have been trying to remove that specific div whose button has been clicked to remove. How can I achieve that?
Here's the CodeSandbox.
And here's the code:
import { useState } from "react";
const App = () => {
const [ counter, setCounter ] = useState( 1 );
const handleAddDiv = () => {
setCounter( counter + 1 );
};
const handleRemoveDiv = () => {
setCounter( counter - 1 );
};
return (
<div className="App">
{
Array.from(Array( counter )).map(( item, idx ) => (
<div>
<div>
<input type="text" />
<button onClick={handleRemoveDiv}>Remove</button>
</div>
</div>
))
}
<button onClick={handleAddDiv}>Add</button>
</div>
);
}
export default App;
This is not prefered react way of doing things, but this will work:
import "./styles.css";
import { useState } from "react";
const App = () => {
const [counter, setCounter] = useState(1);
const handleAddDiv = () => {
setCounter(counter + 1);
};
const removeNode = (idx) => document.getElementById(`id-${idx}`).remove();
return (
<div className="App">
{Array.from(Array(counter)).map((item, idx) => (
<div key={idx} id={`id-${idx}`}>
<div>
<input type="text" />
<button onClick={() => removeNode(idx)}>Remove</button>
</div>
</div>
))}
<button onClick={handleAddDiv}>Add</button>
</div>
);
};
export default App;
Generaly if you would like to have it made correactly then you would want to map on a real array and have every item in array eighter having an unique id or simply use map index and then based on which item you click write a function to remove from that array our specific element.
Map over an array of unique Ids
First of all, you should map over an array of items instead of an integer value.
So, on click of add button, you should push a unique ID to the array of items where each ID would denote an item being rendered in your app.
Now, when you click on remove button, you would need to remove that ID from the array of items, which would result in "deletion" of that div from the app.
In my case, I have considered timestamp as a unique ID but should explore other options for generating unique IDs. Working with indices is anti pattern in React especially when you are mapping over an array in JSX as you would encounter issues at one point of time. So, it's a good idea to maintain unique Ids.
Note: Damian's solution is not ideal as DOM Manipulation is avoided in React.
const { useState, useCallback } = React;
const Item = ({ id, removeDiv }) => {
const clickHandler = useCallback(() => {
removeDiv(id);
}, [id, removeDiv]);
return (
<div>
<input type="text" />
<button onClick={clickHandler}>Remove</button>
</div>
);
};
const App = () => {
const [items, setItems] = useState([]);
const addDiv = useCallback(() => {
// using timestamp as a unique ID
setItems([...items, new Date().getTime()]);
}, [items]);
const removeDiv = useCallback((itemId) => {
// filter out the div which matches the ID
setItems(items.filter((id) => id !== itemId));
}, [items]);
return (
<div className="app">
{items.map((id) => (
<Item key={id} id={id} removeDiv={removeDiv} />
))}
<button onClick={addDiv}>Add</button>
</div>
);
};
ReactDOM.render(<App />,document.getElementById("react"));
.app {
text-align: center;
font-family: sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I would use an array state instead of a counter state, because otherwise you don't know which element has to be removed.
import { useState } from "react";
import "./styles.css";
let counter = 1;
export default function App() {
const [array, setArray] = useState([0]);
const handleAddDiv = () => {
setArray((prev) => [...prev, counter++]);
};
const handleRemoveDiv = (idx) => {
var arrayCopy = [...array];
arrayCopy.splice(idx, 1);//remove the the item at the specific index
setArray(arrayCopy);
};
return (
<div className="App">
{array.map((item, idx) => (
<div key={item}>
<div>
<input type="text" />
<button onClick={()=>handleRemoveDiv(idx)}
>Remove</button>
</div>
</div>
))}
<button onClick={handleAddDiv}>Add</button>
</div>
);
}
When I am adding a new item, I give it the value counter++, because I will use it as a key, and a key should be unique.
I'm having a problem on inputs than want to change in array, there is a lot of lag/delay when typing in inputs when this array have more than 8 arraylists, i make this code below to simple reproduce what's happen with my code.
App.js
import './App.css';
import React,{useState} from 'react';
import Inputs from './Inputs'
function App() {
const [ array, setArray ] = useState([
["John",'30','ttt'],
["Smith",'20','ttt'],
["Thiago",'40','ttt'],
["Jake",'30','ttt'],
["Fer",'41','ttt'],
["Fred",'23','ttt'],
["Otto",'55','ttt'],
["Gago",'21','ttt'],
["Higor",'15','ttt'],
]);
return (
<div className="App">
<header className="App-header">
<Inputs array={array} setArray={setArray} />
<br />
<button onClick={() => {
/** add More arrays */
setArray(array=> [...array, ['','','']])
}}>add more</button>
<br />
<button onClick={() => console.log("Send array to DB")}>Send array to DB</button>
</header>
</div>
);
}
export default App;
Inputs.js
import './App.css';
import React,{ useEffect } from 'react';
function Inputs(props) {
const { array, setArray } = props
const handleInputChange = (e, index, position2) => {
const {value} = e.target;
const list = [...array];
list[index][position2] = value;
setArray(list);
};
useEffect(() => {
console.log(array)
},[array])
return (
array.map((item, index) => {
return (<div key={index}>
<input
value={item[0]}
onChange={(e) => {
handleInputChange(e,index,0)
}}
/>
<input
value={item[1]}
onChange={(e) => {
handleInputChange(e,index,1)
}}
/>
<input
value={item[2]}
onChange={(e) => {
handleInputChange(e,index,2)
}}
/>
</div>)
})
);
}
export default Inputs;
I don't find another option to change value in this array, and each type in inputs are creating every arrays again.
Thanks
You can consider maintaining separate states for all the inputs instead of creating a common state.
A better alternative to this will be to create a pure component that can be used for rendering all the inputs - Then you will just have to pass the value and onChange callback.
I am new to React. I am working on a piece of code. I am trying to make a ToDoList kind of thing. So i have created 3 different component , one for taking input and one for displaying the entered code along with App.jsx.
Here are the code for each.
App.jsx
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [arr, setArr] = useState([]);
function getData(note) {
// setArr(previous => [...previous, { title: newTitle, content: newContent }]);
setArr(previous => {return [...previous, note];});
console.log(arr);
}
return (
<div>
<Header />
<CreateArea reply={getData} />
{arr.map((arry, index) => (
<Note
key={index}
id={index}
title={arry.title}
content={arry.content}
/>
))}
<Footer />
</div>
);
}
export default App;
CreateArea.jsx
import React, { useState } from "react";
function CreateArea(props) {
const [note, setNote] = useState({
title: "",
content: ""
});
function handleChange(event) {
const { name, value } = event.target;
setNote(prevNote => {
return {
...prevNote,
[name]: value
};
});
}
function submitNote(event) {
// const newTitle = note.title;
// const newContent = note.content;
// props.reply(newTitle, newContent);
props.reply(note);
setNote({
title: "",
content: ""
});
event.preventDefault();
}
return (
<div>
<form>
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
/>
<textarea
name="content"
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
rows="3"
/>
<button onClick={submitNote}>Add</button>
</form>
</div>
);
}
export default CreateArea;
Note.jsx
import React from "react";
function Note(props) {
return (
<div className="note">
<h1>{props.title}</h1>
<p>{props.content}</p>
<button>DELETE</button>
</div>
);
}
export default Note;
Now somehow i am getting the result right and the new post are being added as required but when I do a console.log for the array which stores inside getData() in App.jsx ,I observe there is a delayy of one click before the array is being added.
Sample problem
In the images attached.I have added new post with random data but when i see the console log the array is still empty. When i click the Add button for the second time , only then after second click on adding new post is the data being shown. I can't seem to figure out the reasoning behind it. Am I doing something wrong or missing something ?
Your setArr() from useState() is an asynchronous function. Hence, the console.log(arr) is called before the state is actually updated.
However, you can log arr in the useEffect() hook which is called on every state change, like this:
useEffect(() => {console.log(arr)})
You can find more information about this hook at https://reactjs.org/docs/hooks-effect.html.
This is delay is because useState mutation is asynchronous, you cannot get updated result in one tick.
try console.log result in useEffect hook like below:
function App() {
const [arr, setArr] = useState([]);
function getData(note) {
// setArr(previous => [...previous, { title: newTitle, content: newContent }]);
setArr(previous => {
return [...previous, note];
});
}
useEffect(() => {
console.log(arr);
});
return (
<div>
<CreateArea reply={getData} />
{arr.map((arry, index) => (
<Note
key={index}
id={index}
title={arry.title}
content={arry.content}
/>
))}
</div>
);
}
more info: hooks-reference.html#useeffect
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} />