what is the design flaw causing the infinite loop in this component? - reactjs

I've created a basic React component as a sandbox. I added a doSomething() function to the component which simply displays an alert on useEffect(). However, the alert is getting called in an infinite loop. So obviously, there's something wrong with the design. Any idea what the design issue is with this component which is causing the infinite loop? What steps would you recommend to redesign this component in order to fix that issue?
import {Repository} from '../data/Repository';
import { useEffect , useState} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { decrement, increment } from '../features/counter/counterSlice';
import { setIsValid } from '../features/userManagement/userManagementSlice';
export const UserManagement = () => {
const [userTestData, setUserTestData] = useState([]);
const [userCount, setUserCount] = useState(0);
const count = useSelector((state) => state.counter.value)
const isValid2 = useSelector((state) => state.userManagement.isValid2)
const dispatch = useDispatch()
const doSomething = () => {
alert('about to do something');
}
useEffect(() => {
doSomething();
setUserTestData(Repository.getUserTestData());
})
useEffect(() => {
setUserCount(userTestData.length);
}, [userTestData])
//debugger;
return(
<>
<div>
{
userTestData.map((x) => {
return <div key={x.Id}>{x.FirstName}</div>
})
}
</div>
<div>there are {userCount} users</div>
<div>
{/* PARENT INPUT VALIDATION */}
<div style={{height:100}}>
<button onClick={() => dispatch(setIsValid())}>
TOGGLE VALIDATION
</button>
{isValid2 ? 'true' : 'false'}
</div>
{/* COUNTER */}
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
</>
)
}

The issue here.
useEffect(() => {
doSomething();
setUserTestData(Repository.getUserTestData());
})
You didn't add array of dependences to useEffect, so it's callback will be excuted on each render of the component, and as you set a State by setUserTestData, so the component will still re-render in infinite loop.
To fix.
useEffect(() => {
doSomething();
setUserTestData(Repository.getUserTestData());
}, [])

Related

Why react rerender another variable?

Code like this:
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
const MenuItems = () => {
const renderMenuItems = () => {
if (menuitems && menuitems.length){
console.log("Render")
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
}
return (
renderMenuItems()
)
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{menuitems && <MenuItems/>}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
When the input tag is used, the variable MenuItems is reloaded. What's wrong in my code? Why is it rerendering and how to prevent this from happening?
As far as I understand, this happens after setting the variable "searchTi" through the function "setSearchTic". This updates the variable "menuitems " and reloads this section of code:
{menuitems && <MenuItems/>}
you are using MenuItems like it was a component, but it's only a render function. should just call it like this:
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
const renderMenuItems = () => {
if (menuitems && menuitems.length){
console.log("Render")
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
return null;
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{renderMenuItems()}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
Compact example:
Also, there's no need to check to the menuitems.length. Best way to render the menu items would be something like this:
const renderMenuItems = () => menuitems?.map((name) => <button key={name}>{name}</button>);
useMemo:
If you want to avoid re-render the menu items over and over, you should also use React.useMemo like this:
const renderMenuItems = useMemo(() => menuitems?.map((name) => <button key={name}>{name}</button>), [menuitems]);
Note that it's now an object (similar to your JSX), and you should not call it, just put it as part of your JSX like this:
return (
<div className="App">
{renderMenuItems}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
I came across your question and it seemed interesting so I researched about it and finally, I found out that NEVER CREATE A COMPONENT INSIDE ANOTHER FUNCTION COMPONENT.
And I found an article written by Kuldeep Bora.
you can go through the article to understand this completely.
https://dev.to/borasvm/react-create-component-inside-a-component-456b
React components automatically re-render whenever there is a change in their state or props.
Function renderMenuItems will re-create on every re-render and it is not an issue.
But if you don't want this behavior you can use the useCallback hook, and then the function will re-create only when one of the dependencies will change.
useCallback hook docs: https://reactjs.org/docs/hooks-reference.html#usecallback
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
// this function will re-create for every re-render
const renderMenuItems = () => {
if (menuitems && menuitems.length){
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{renderMenuItems()}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;

Why am I getting a null value in localStorage even after parsing it?

I'm trying to build a crypto tracker where you can add the items by clicking a button. Each time the button is clicked, the array should be added to the storage with the name "crypto" and then on another component where it is the portfolio one we should be able to get the items.
Here is where I set the item to an array whenever I click the add button:
import React, {useEffect, useState} from 'react'
import axios from 'axios'
import './tracker.css'
import Navigation from './Nav.js'
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
function Tracker() {
const [data, setData] = useState([])
const [portfolio, setPortfolio] = useState([])
useEffect(() => {
setInterval(() => {
const fetchData = async () => {
const result = await axios('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false' , {
'mode': 'no-cors',
'headers': {
'Access-Control-Allow-Origin': '*',
}
})
setData(result.data)
}
fetchData()
}, 1000)
}, [])
return (
<div>
<Navigation />
<div className="tracker__names">
<b>Coins</b>
<b>Symbol</b>
<b>Price</b>
<b>Market Cap</b>
</div>
{data.map((coins, i) => {
const addToPortfolio = () => {
setPortfolio([...portfolio, data[i]])
localStorage.setItem('crpyto', JSON.stringify(portfolio))
}
return (
<>
<div className="tracker__main">
<div className="tracker__img">
<img src={coins.image} className="tracker__image"/>
<button key={i} onClick={addToPortfolio}>{coins.id}</button>
</div>
<div className="tracker__symbol">
<p>{coins.symbol}</p>
</div>
<div className="tracker__price">
<p></p>
${coins.current_price}
</div>
<div className="tracker__market">
<p></p>
${coins.market_cap}
</div>
</div>
</>
)
})}
</div>
)
}
export default Tracker
Here is the component where I want to get the item:
import React, {useState, useEffect} from 'react'
import Navigation from './Nav.js'
function Portfolio() {
const [value, setValue] = useState(JSON.parse(localStorage.getItem('crypto')) || '')
useEffect(() => {
console.log(value)
}, )
return (
<div>
<Navigation />
{value}
</div>
)
}
export default Portfolio
It is because useState is executed before JSON.parse(localStorage.getItem('crypto')) and once you get the value from the localstorage, component doesn't re-render.
Instead do:
useEffect(() => {
const crypto = JSON.parse(localStorage.getItem('crypto'))
if(crypto) setValue(crypto)
}, [])
In React you can't set a state var and on the next line save it in localStorage (or even read it). This because setPortfolio is async!
To solve this you have I think 2 ways:
store value and not state variable:
localStorage.setItem('crpyto', JSON.stringify([...portfolio, data[i]]))
use an useEffect hook:
useEffect(() => {
localStorage.setItem('crpyto', JSON.stringify(portfolio))
}, [portfolio])
First of all, when yo uare setting state like this, in the next block of code, portfolio won't necessarily have the updated state.
setPortfolio([...portfolio, data[i]])
localStorage.setItem('crpyto', JSON.stringify(portfolio))
update the portfolio like this.
const newPortfolio = [...portfolio, data[i]];
setPortfolio(newPortfolio )
localStorage.setItem('crpyto', JSON.stringify(newPortfolio))

Controlling one element inside an array

I'm trying to create an edit feature to my todo-list but i'm kind of stuck and receiving a weird behaviour.
I'm filtering the array using the id's but what happens is that the entire array is changing instead of one element inside of it.
What supposed to happen is when clicking the edit button, the element im clicking on should change to an input (not the entire array)
thanks for any kind of help!
App:
import React, { useState } from "react";
import Header from "./UI/Header";
import TodoList from "./Components/TodoList";
import AddTodo from "./Components/AddTodo";
import { v4 as uuidv4 } from "uuid";
function App() {
const [todos, setTodos] = useState([]);
const [editTodo, setEditTodo] = useState(false);
const onAddHandler = (text) => {
setTodos([
...todos,
{
name: text,
id: uuidv4(),
},
]);
};
const deleteTodoHandler = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const editTodoHandler = (id) => {
todos.filter((todo) => todo.id === id);
setEditTodo(!editTodo);
};
return (
<div>
<div className="App">
<AddTodo onAddHandler={onAddHandler} />
<Header />
<TodoList
todos={todos}
deleteTodoHandler={deleteTodoHandler}
editTodoHandler={editTodoHandler}
editTodo={editTodo}
/>
</div>
</div>
);
}
export default App;
TodoList.js :
import React, { useState } from "react";
import Todo from "./Todo";
const TodoList = (props) => {
return (
<Todo todo={props.todo}>
{props.todos.map((todo) => {
return (
<p>
{props.editTodo ? <input /> : <span>{todo.name}</span>}
<button onClick={() => props.deleteTodoHandler(todo.id)}>
Delete
</button>
<button onClick={() => props.editTodoHandler(todo.id)}>Edit</button>
</p>
);
})}
</Todo>
);
};
export default TodoList;
When you set the editTodo property to true, the TodoList component re-renders and loops through the todo array again, changing every <span> to an <input>. You're going to have to pass the id of the todo that you want to edit, and add a condition to only change that single item to an <input>.

want to know why componentWillUnmount execute when a component update

App.js
import React from "react";
import CounterOne from "./CounterOne";
function App() {
const [display, setDisplay] = React.useState(true);
return (
<>
{display && <CounterOne />}
<button
onClick={() => {
setDisplay(!display);
}}
>
display
</button>
</>
);
}
export default App;
CounterOne.js
import React, { useState } from "react";
function CounterOne() {
const [count, setCount] = useState(0);
React.useEffect(() => {
console.log("component did update");
return () => {
console.log("component will unmount");
};
}, [count]);
return (
<div>
<p>Count is {count}</p>
<button
onClick={() => {
setCount(0);
}}
>
reset
</button>
<button
onClick={() => {
setCount(count + 5);
}}
>
Add 5
</button>
<button
onClick={() => {
setCount(count - 1);
}}
>
Sub 1
</button>
</div>
);
}
export default CounterOne;
When i hit the Add 5 or sub 1 button, the component re-render then in browser console it prints
component will unmount
component did update
i am confuse why will unmount part execute when the update of state taking place
Because when you change a state in a component, React will re-render the whole component. So the old component will be unmounted first, triggering the return callback in useEffect. When a new component is mounted, the logic inside useEffect will be triggered again, hence you see the "component did update" after the "component will unmount" message. More on useEffect here What is the expected return of `useEffect` used for?.

Why do I get Invalid Hook Call

I'm trying to build a react web application using functional components, react hooks, and redux. I can't figure out why I am getting the invalid hook call. When I click the Search button I get the react error.
Here's the entirety of the code:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux'
export const SearchBar = () => {
const searchParameters = useSelector(state => state.searchParameters ?? {});
const dispatch = useDispatch;
return (
<div className="searchbar">
<div className="search-parameters">
<BasicSearch searchParameters={searchParameters} />
<div className="col form-group">
<button type="button" className="btn btn-primary" onClick={() => dispatch(doSearch())} >
SEARCH
</button>
</div>
</div>
</div>
);
}
const BasicSearch = (props) => {
return (
<>
<input type="text" id="origin" />
</>
);
}
const Actions = {
DO_SEARCH: "DO_SEARCH"
}
const doSearch = () => {
return { type: Actions.DO_SEARCH };
}
useDispatch on your 6th line of code is a function, so you need to assign the variable dispatch to it like so:
const dispatch = useDispatch();
Update
const dispatch = useDispatch();
and
onClick={() => dispatch(doSearch)}

Resources