React: Lag observed when array is updated - javascript

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.

Related

React parent couldn't update a child component in a mapped JSX list

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

function holding old state

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;

React.js updates some part of DOM nodes when I'm deleting only one element

As I know React creates a new Virtual DOM and compares it with previous one, then it updates Browser DOM with least number of changes possible without rendering the entire DOM again. (in short)
In React documentation I have also read how key should work.
for demonstrating this, I have created todo app, but when I'm deleting one element, all previous elements are re-rendered again (except if I'm not deleting recently added element)
Here is screenshot:
(In Chrome developer tool paint flashing is active for showing renders)
My questions:
Why do previous elements re-render again?
Why key could not fix this problem?
Here is the entire code:
TodoApp.jsx
import React, { useState } from 'react';
import Input from './Input';
import List from './List';
const TodoApp = () => {
const [inputText, setInputText] = useState('');
const [todos, setTodos] = useState([]);
const onSubmitChange = e => {
e.preventDefault();
setTodos([
...todos,
{ text: inputText, completed: false, id: Math.random() * 1000 },
]);
setInputText('');
};
const onChangeEvent = e => {
setInputText(e.target.value);
};
return (
<div>
{todos.map(todo => {
return (
<List todos={todos} setTodos={setTodos} todo={todo} key={todo.id} />
);
})}
<Input
onSubmit={onSubmitChange}
onChange={onChangeEvent}
inputText={inputText}
/>
</div>
);
};
export default TodoApp;
List.jsx
import React from 'react';
import "../todolist/css/TodoApp.css"
const List = ({ todo, todos, setTodos }) => {
const deleteHandle = () => {
setTodos(todos.filter(el => el.id !== todo.id));
};
const completeHandle = () => {
setTodos(
todos.map(el => {
if (el.id === todo.id) {
return { ...el, completed: !el.completed };
}
return el;
})
);
};
return (
<div className={`${todo.completed ? 'completed' : ''}`}>
<div>{todo.text}</div>
<div className="btns">
<button onClick={deleteHandle} className="btn btn-delete">
Delete
</button>
<button onClick={completeHandle} className="btn btn-complete">
complete
</button>
</div>
</div>
);
};
export default List;
Input.jsx
import React from 'react';
const Input = ({ onSubmit, onChange, inputText }) => {
return (
<form onSubmit={onSubmit}>
<input
onChange={onChange}
value={inputText}
type="text"
name="myInput"
autoComplete="off"
/>
</form>
);
};
export default Input;

Delay in storing the content of an event in array by one click in React

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

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} />

Categories

Resources