Prevent rerendering of child component on input change - reactjs

I have a component in reactjs where i define 2 states input and resultlist(an array). My functionality is like, I will enter some input click on search, call an api, set search results in result list. Now my problem is my search result is very huge. Once resultlist is populated first time, then whenever I am typing input it is rerendering whole component on each keypress including resultlist which is lagging a lot. How to handle this issue.
I tried using usememo on resultlist but it is not working. I don't want my Child to rerender on every keyspress in input.
const comp1 = () => {
const [input, setinput] = useState('');
const [resultlist, setresultlist] = useState([]);
const fetchResult = () =>{
fetch('some url') // returns a very long list of items(10k +)
.then(res=>setresultlist(res));
}
return <>
<input value={input} onChange={(e)=>setinput(e.target.value)}></input>
<button oncClick={fetchResult}></button>
<Child resultlist={resultlist} />
</>
}
const Child = ({resultlist}) => {
return <>
{resultlist.map(element=><div>element</div>)}
</>
}

I found a solution.
const Parent = () => {
const [resultlist, setresultlist] = useState([]);
return <>
<comp1 setresultlist={setresultlist} />
<Child resultlist={memoizedResultList} />
</>
}
const comp1 = ({setresultlist}) => {
const [input, setinput] = useState('');
const fetchResult = () =>{
fetch('some url') // returns a very long list of items(10k +)
.then(res=>setresultlist(res));
}
return <>
<input value={input} onChange={(e)=>setinput(e.target.value)}></input>
<button oncClick={fetchResult}></button>
</>
}
const Child = ({resultlist}) => {
return <>
{resultlist.map(element=><div>element</div>)}
</>
}

Related

react - text input give me null

(in REACT)
i have app function :
function App() {
const [welcomeMenu, setWelcomeMenu] = useState(true);
const [gameMenu, setGameMenu] = useState(false);
const [username, setUsername] = useState('');
const welcomeMenuShow = () => {
setWelcomeMenu(false);
}
const getUserName = (value) => {
setUsername(value);
console.log(username);
};
return (
<div className="App">
{
welcomeMenu ? <WelcomeMenu gameStarter={welcomeMenuShow} getUserName={getUserName}/> : null
}
</div>
);
}
in welcomemenu component i pass getUserName function to get username which user input
next in Welcome menu i have :
const WelcomeMenu = ({ gameStarter, getUserName }) => {
return (
<div className="welcome-menu">
<WelcomeText />
<WelcomeBoard gameStarter={gameStarter} getUserName={getUserName}/>
</div>
)
};
i pass get User Name in second time
in WelcomeBoard i have:
const WelcomeBoard = ({ gameStarter, getUserName }) => {
const [text, setText] = useState('');
const [warning, setWarning] = useState(false);
const checkBtn = (event) => {
if(text) {
gameStarter();
} else {
setWarning(true);
setTimeout(() => {
setWarning(false);
}, 3000);
}
};
const handleChange = (event) => {
setText(event.target.value);
};
return (
<div className="welcome-board">
<div className="username">Please enter the name</div>
<input type="text" value={text} onChange={handleChange} className="username-input" />
<button className="username-btn" onClick={() => {
getUserName(text);
checkBtn();
}}>start</button>
{warning ? <Warning /> : null}
</div>
)
};
in input onchange i make state and pass the input value on text state
next on button i have on click which active 2 function:
getUserName(text) // text is a state text with input value
checkBtn()
and after a click button in app i activate getUserName(text), this function pass the text in username state and here is a problem
when i try to see this text console.log(username) - it's give me null
but it if i try to see value console.log(value) - i see my input text
i don't understand how to fix that
react setState is async, which means those state variables are updated in the NEXT RENDER CYCLE(think of it as a thread or buffer).
try running this code if you want to understand what is happening BEHIND THE SCENES.
let renderCount = 0;
function TestApp() {
renderCount++;
const [state, setState] = useState(0);
const someRef = useRef(0);
someRef.current = state;
const someCallback = () => {
const someValue = new Date().getTime();
setState(someValue);
console.log(someRef.current, renderCount);
setTimeout(() => {
console.log(someRef.current, renderCount);
},100)
}
return <button onClick={someCallback}>clickme<button>;
}

Element obtaining value from input only updating after the second time I press enter. Why is this happening? - ReactJS

I am just trying to figure out how to do my to-do list and currently am just experimenting with adding elements containing text given a text input element.
The issue is presented in this clip: https://imgur.com/a/DDTyv1I
import { useState } from "react"
function App() {
const [inputVal, setInputVal] = useState('')
const [tasks, setTasks] = useState([])
console.log(inputVal);
return <>
<Input valor = {setInputVal} inputVal={inputVal} tasks={tasks} setTasks={setTasks}/>
{
tasks.map(e=>(
<Display text={e.text}/>
))
}
</>
}
const Input = ({valor, inputVal, tasks, setTasks}) =>{
const keyPressed = (val) =>{
if(val.key === 'Enter'){
valor(val.target.value)
setTasks([
...tasks, {text: inputVal, key: Math.random()*2000}
])
}
}
return <>
<input type="text" onKeyUp={keyPressed}/>
</>
}
const Display = ({text}) => {
return <>
<h1>{text}</h1>
</>
}
export default App;
I believe this is happening because you are not using onChange on your input so your state is going stale and you are always one value behind.
I have tidied up the code and added some missing pieces (like the value attribute in the input element).Then I split the function that takes care of the submission to 2 functions - one function that is handling changing the input value and one that submits the value as a new entry to your tasks list
import { useState } from "react"
const Input = ({ input, handleChange, tasks, setTasks }) => {
const onSubmit = (e) => {
if (e.key === 'Enter') {
setTasks([
...tasks,
{ text: input, key: Math.random()*2000 }
])
setInput('');
}
}
const handleChange = (e) => {
setInput(e.target.value)
}
return <input type="text" onKeyUp={onSubmit} onChange={handleChange} value={input}/>
}
const Display = ({ text }) => <h1>{text}</h1>
const App = () => {
const [input, setInput] = useState('')
const [tasks, setTasks] = useState([])
return <>
<Input input={input} setInput={setInput} tasks={tasks} setTasks={setTasks}/>
{tasks.map((task) => (
<Display text={task.text}/>
))
}
</>
}
When keyPressed is called, your code calls setInputVal (valor) and setTasks. The setTask is being called before setInputVal actually has time to update the state, so it sets the ”old” value. This is because state setting is asynchronous and the code does not wait for the inputVal to be set before setting the task.

persist state after page refresh in React using local storage

What I would like to happen is when displayBtn() is clicked for the items in localStorage to display.
In useEffect() there is localStorage.setItem("localValue", JSON.stringify(myLeads)) MyLeads is an array which holds leads const const [myLeads, setMyLeads] = useState([]); myLeads state is changed when the saveBtn() is clicked setMyLeads((prev) => [...prev, leadValue.inputVal]);
In DevTools > Applications, localStorage is being updated but when the page is refreshed localStorage is empty []. How do you make localStorage persist state after refresh? I came across this article and have applied the logic but it hasn't solved the issue. I know it is something I have done incorrectly.
import List from './components/List'
import { SaveBtn } from './components/Buttons';
function App() {
const [myLeads, setMyLeads] = useState([]);
const [leadValue, setLeadValue] = useState({
inputVal: "",
});
const [display, setDisplay] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setLeadValue((prev) => {
return {
...prev,
[name]: value,
};
});
};
const localStoredValue = JSON.parse(localStorage.getItem("localValue")) ;
const [localItems] = useState(localStoredValue || []);
useEffect(() => {
localStorage.setItem("localValue", JSON.stringify(myLeads));
}, [myLeads]);
const saveBtn = () => {
setMyLeads((prev) => [...prev, leadValue.inputVal]);
// setLocalItems((prevItems) => [...prevItems, leadValue.inputVal]);
setDisplay(false);
};
const displayBtn = () => {
setDisplay(true);
};
const displayLocalItems = localItems.map((item) => {
return <List key={item} val={item} />;
});
return (
<main>
<input
name="inputVal"
value={leadValue.inputVal}
type="text"
onChange={handleChange}
required
/>
<SaveBtn saveBtn={saveBtn} />
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
);
}
export default App;```
You've fallen into a classic React Hooks trap - because using useState() is so easy, you're actually overusing it.
If localStorage is your storage mechanism, then you don't need useState() for that AT ALL. You'll end up having a fight at some point between your two sources about what is "the right state".
All you need for your use-case is something to hold the text that feeds your controlled input component (I've called it leadText), and something to hold your display boolean:
const [leadText, setLeadText] = useState('')
const [display, setDisplay] = useState(false)
const localStoredValues = JSON.parse(window.localStorage.getItem('localValue') || '[]')
const handleChange = (event) => {
const { name, value } = event.target
setLeadText(value)
}
const saveBtn = () => {
const updatedArray = [...localStoredValues, leadText]
localStorage.setItem('localValue', JSON.stringify(updatedArray))
setDisplay(false)
}
const displayBtn = () => {
setDisplay(true)
}
const displayLocalItems = localStoredValues.map((item) => {
return <li key={item}>{item}</li>
})
return (
<main>
<input name="inputVal" value={leadText} type="text" onChange={handleChange} required />
<button onClick={saveBtn}> Save </button>
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
)

React useState update without set throught child component

I have a logic in my code that i don't understand, i can update a state (useState) throught my child components but without the "set" function.
Here is my (simplified) code :
const Main = () =>{
const [mission, setMission] = useState({activity:"", env:""})
const see = () =>{
console.log(mission)
}
return (
<Child1 data={mission} />
<button onClick={see}>TEST</button>
)
}
const Child1 = (props) =>{
const {data} = props
const [mission, setMission] = useState(data)
const handleChange = (value) =>{
mission["activity"] = value
}
return (
<Child2 data={mission} onChange={handleChange} />
)
}
const Child2 = (props) =>{
const {data} = props
const [activity, setActivity] = useState(data.activity)
const handleChange = (e) =>{
setActivity(e.target.value)
props.onChange(e.target.value)
}
return (
<input value={data} onChange={handleChange} />
)
}
I tried in sandbox and it work too, "mission" did update it's value throught all childs without any "setMission".
I'm relativily new to react so i miss something but i don't know what, can someone explain ?
Thank you
You probably want to "lift the state up". Only the Main component should useState. Then pass both the state value and update function to the child component. The child component will call this update function when it updates. This will update the state on the parent properly and re-render.
Using your example:
const Main = () => {
// Only one state at the top level
const [mission, setMission] = useState({ activity: "", env: "" });
const see = () => {
console.log(mission);
};
return (
<>
{/* Pass both the value and state update function to the child */}
<Child1 data={mission} update={setMission} />
<button onClick={see}>TEST</button>
<div>{mission.activity}</div>
</>
);
};
const Child1 = (props) => {
const { data, update } = props;
const handleChange = (e) => {
// This will set parent's state
update({ ...data, activity: e.target.value });
};
// You can follow the similar pattern to the next child
return <Child2 data={data} onChange={handleChange} />;
};
You can see a complete working example in this sandbox.

Function Component always using previous state in render

I'm pretty new to using hooks and functional components.
I have a Filtered List. When I try to update the filter, it will use the last filter state instead of the new one. I must be missing some render/state change orders, but I can't seem to figure out what it is.
I appreciate any help I can get :)
Pseudo code below:
export default function TransferList(props) {
const [wholeList, setWholeList] = React.useState([]);
const [filteredList, setFilteredList] = React.useState([]);
const [filter, setFilter] = React.useState([]);
return (
<>
<TextField
value={filter}
onChange={(e) => {
// Set filter input
setFilter(e.target.value);
// Filter the list
let filtered = wholeList.filter(
(item) => item.indexOf(filter) !== -1
);
setFilteredList(filtered);
}}
/>
<List>
{filteredList.map((item) => (
<ListItem>Item: {item}</ListItem>
))}
</List>
</>
);
}
Inside onChange you should use save the value in a constant, filter will not update just after setFilter(filterValue) as this is an async operation.
<TextField
value={filter}
onChange={e => {
const filterValue = e.target.value;
// Set filter input
setFilter(filterValue);
// Filter the list
let filtered = wholeList.filter(item => item.indexOf(filterValue) !== -1);
setFilteredList(filtered);
}}
/>;
State updates are asynchronous and hence the filter state update doesn't reflect immediately afterwords
You must store the new filter values and set the states based on that
export default function TransferList(props) {
const [wholeList, setWholeList] = React.useState([]);
const [filteredList, setFilteredList] = React.useState([]);
const [filter, setFilter] = React.useState([]);
return (
<>
<TextField value={filter} onChange={(e) => {
// Set filter input
const newFilter = e.target.value;
setFilter(newFilter)
// Filter the list
let filtered = wholeList.filter(item => item.indexOf(newFilter) !== -1)
setFilteredList(filtered)
}} />
<List>
{filteredList.map(item => <ListItem>Item: {item}</ListItem>)}
</List>
</>
)
}
So it turns out I had to give the initial filtered list the entire unfiltered list first. For some reason that fixes it.
const [filteredList, setFilteredList] = React.useState(props.wholeList);
I initially wanted an empty filter to display nothing, but I may have to settle for showing the entire list when the filter is empty
Order your code to be increase readability
In clean code
Main changes:
Use destructuring instead of props
Out the jsx from the html return to increase readability
Use includes instead of indexOf
Add key to the list
export default function TransferList({ wholeList }) {
const [filteredList, setFilteredList] = React.useState(wholeList);
const [filter, setFilter] = React.useState([]);
const handleOnChange = ({ target }) => {
setFilter(target.value);
const updatedFilteredList = wholeList.filter(item => item.includes(target.value));
setFilteredList(updatedFilteredList);
}
const list = filteredList.map(item => {
return <ListItem key={item}>Item: {item}</ListItem>
});
return (
<>
<TextField value={filter} onChange={handleOnChange} />
<List>{list}</List>
</>
);
}
and I have a filter component the do the filter list inside.
import React, { useState } from 'react';
function FilterInput({ list = [], filterKeys = [], placeholder = 'Search',
onFilter }) {
const [filterValue, setFilterValue] = useState('');
const updateFilterValue = ev => {
setFilterValue(ev.target.value);
const value = ev.target.value.toLowerCase();
if (!value) onFilter(list);
else {
const filteredList = list.filter(item => filterKeys.some(key => item[key].toLowerCase().includes(value)));
onFilter(filteredList);
}
}
return (
<input className="filter-input" type="text" placeholder={placeholder}
value={filterValue} onChange={updateFilterValue} />
);
}
export default FilterInput;
the call from the father component look like this
<FilterInput list={countries} filterKeys={['name']} placeholder="Search Country"
onFilter={filterCountries} />
this is in my corona app.. you can look on my Github.
and see how I build the filter
(https://github.com/omergal99/hello-corona)

Resources