Why is my state variables being rendered twice in reactjs - reactjs

Every time I use react and the useEffect method my state variable renders twice. Once an empty variable and the next the desired variable. What can I try to help avoid this problem for now and in the future?
import React, { useState,useEffect } from "react";
export default function Member (props) {
const [team,setTeam] = useState([]);
useEffect(() => {
let array = ["hello","hi"];
setTeam(array);
}, [])
console.log(team);
return (
<>
{team.forEach(i => <p>{i}</p>)}
</>
)
}

You need to use map to render an array in JSX:
export default function Member(props) {
const [team, setTeam] = useState([]);
useEffect(() => {
let array = ["hello", "hi"];
setTeam(array);
}, []);
console.log(team);
return (
<>
{team.map(i => ( // use map here
<p>{i}</p>
))}
</>
);
}
forEach doesn't return anything, so you can't use it to render components like that.
Also in your code instead of using useEffect to setup initial state, you can just set it straight in useState:
export default function Member(props) {
const [team, setTeam] = useState(["hello", "hi"]);
console.log(team);
return (
<>
{team.map(i => ( // use map here
<p>{i}</p>
))}
</>
);
}

It is an abvious behavior.
Component render's first time with initial state. After useEffect (componentDidMount) again re-renders.
So you are getting two console.log.
To avoid this, you need to set the state initally,
const [team,setTeam] = useState(["hello","hi"]);
and remove useEffect.
Note: forEach won't print data, you need map here,
{team.map(i => <p key={i}>{i}</p>)} //provide a key here

Related

How to avoid this message warning "Maximum update depth exceeded..." on NextJs

on NextJs i not understand, how useEffect work. What i need to do, to stop of receiving this warning message
"Maximum update depth exceeded":
The Code bellow is the page, that call a component ListContainer, this page add a item to container.
The page JSX:
import { useState } from "react";
import AppLayout from "../components/AppLayout";
import ListContainer from "../components/ListContainer";
export default function componentCreator(){
const [item,setItem] = useState([])
/* add item to container */
function addItem(){
let newItem = item
newItem.push({
produto: 'Skol 350ml',
preco: '1200,00',
quantidade: 'cx c/ 15 unidades'
})
setItem(newItem)
}
return (
<AppLayout>
<ListContainer items={item} setItems={setItem}/>
<div className="productcardbuttonshow" onClick={() => addItem()}>ADICIONAR</div>
</AppLayout>
)
}
Bellow the component that handle the items, remove or add. But it works, but on console trigger warning messages about update.
Component ListContainer.jsx:
import { useState,useEffect } from "react";
export default function ListContainer(props){
const [html,setHTML] = useState(null)
const [item,setItem] = useState(props.items)
/* refresh html container */
useEffect(() => {
const itemHTML = item.map((itemmap,id) => {
return (
<div id={id} onClick={() => delItem(id)} className="itemProposta">
{itemmap.produto} - {itemmap.quantidade} - R$ {itemmap.preco}
</div>
)
})
setHTML(itemHTML)
})
/* remove item from container */
function delItem(id){
let itemlist = props.items
itemlist.splice(id,1)
props.setItems(itemlist)
}
return (
<>
{html}
</>
)
}
You are getting into an infinite loops of renders. This code is responsible:
useEffect(() => {
const itemHTML = item.map((itemmap,id) => {
return (
<div id={id} onClick={() => delItem(id)} className="itemProposta">
{itemmap.produto} - {itemmap.quantidade} - R$ {itemmap.preco}
</div>
)
})
setHTML(itemHTML)
})
This callback inside useEffect will run after every render, because there is no dependency array. That means after every render, setHTML(itemHTML) is called. And even if the constituent objects of the array itemHTML are same, a new reference of the array is created. A new reference is created because .map() returns a new reference of the array. And although render and update works correctly, infinite rendering is happening.
Consider adding a dependency array to useEffect. For example:
useEffect(() => {
/* function body */
},[props.items]);
Now useEffect callback only runs if props.items reference changes.
Side note (unrelated to your question):
In the below code,
function addItem(){
let newItem = item
newItem.push({
produto: 'Skol 350ml',
preco: '1200,00',
quantidade: 'cx c/ 15 unidades'
})
setItem(newItem)
}
You should do let newItem = [...item], otherwise you are not creating a new reference of item array and setItem(newItem) is basically useless in that case.

What's the difference between useObserver and observer in this application?

I have a react functional component that accesses the MobX store with useContext. I have found two ways to observe an array that is an observable from the store. First, the useObserver hook and wrapping the component with observer.
I thought that these are the same but that the useObserver only observes specific properties (such as the array that is passed) but I am experiencing a problem when the array reaches size 2 and then the component does not re-render. That's the case when using useObserver. When wrapping with observer, this is fixed.
Can anyone explain why this is happening and what's the difference?
const ApplesContainer = observer(() => {
const stores = useStores();
const applesArray = stores.fruits.apples;
return (
{applesArray.map(apple => (
<Apple key={apple.id} apple={apple} />
))}
);
});
// OR with useObserver()
function useGlobalState() {
const stores = useStores();
return useObserver(() => ({
applesArray: stores.fruits.apples
}));
}
const ApplesContainer = observer(() => {
const { applesArray } = useGlobalState();
return (
{applesArray.map(apple => (
<Apple key={apple.id} apple={apple} />
))}
);
});
useObserver must return JSX with an observable value.
This hook takes care of tracking changes and re-rendering them.
If no observable value exists in JSX, then it won't be re-rendered.
e.g.:
const SomeContainer =() => {
const { someStores } = useStores();
return useObserver(()=>(
{someStore.data.map(val => (
<Apple key={val.id} val={val} />
))}
));
};

Incorrect use of useEffect() when filtering an array

I have this React app that's is getting data from a file showing in cards. I have an input to filter the cards to show. The problem I have is that after I filter once, then it doesn't go back to all the cards. I guess that I'm using useEffect wrong. How can I fix this?
import { data } from './data';
const SearchBox = ({ onSearchChange }) => {
return (
<div>
<input
type='search'
placeholder='search'
onChange={(e) => {
onSearchChange(e.target.value);
}}
/>
</div>
);
};
function App() {
const [cards, setCards] = useState(data);
const [searchField, setSearchField] = useState('');
useEffect(() => {
const filteredCards = cards.filter((card) => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
});
setCards(filteredCards);
}, [searchField]);
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={cards} />
</div>
);
}
you should Include both of your state "Card", "searchedField" as dependincies to useEffect method.once any change happens of anyone of them, your component will re-render to keep your data up to date,
useEffect(() => { // your code }, [searchField, cards]);
cards original state will be forever lost unless you filter over original data like const filteredCards = data.filter().
though, in a real project it's not interesting to modify your cards state based on your filter. instead you can remove useEffect and create a filter function wrapped at useCallback:
const filteredCards = useCallback(() => cards.filter(card => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
}), [JSON.stringify(cards), searchField])
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={filteredCards()} />
</div>
);
working example
about array as dependency (cards)
adding an object, or array as dependency at useEffect may crash your app (it will throw Maximum update depth exceeded). it will rerun useEffect forever since its object reference will change everytime. one approach to avoid that is to pass your dependency stringified [JSON.stringify(cards)]

Is there a way to do an api call only once in react functional component?

Sorry if it's a beginner question>
I am trying to use Functional Component, as I was doing Class Component all the time.
I have a simple component that should load a list from a server, and display it.
The component looks like this (I simplified a bit so sorry if there is a type) :
const ItemRelationsList = (props: ItemRelationsListProps): JSX.Element => {
const [getList, setList] = useState([]);
const loadRelation = (): void => {
HttpService.GetAsync<getListRequest, getListResponse>('getList',{
// params
}).subscribe(res => {
setList(res.data.list);
});
}
loadRelation();
return (
<>
<Table
columns={columns}
dataSource={getList}
>
</Table>
</>
)
}
thew problem I face is that everytime I use setList, the component is redraw, so the http call is reexecute.
Is there a way to prevent that other than use a class component ?
use useEffect
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
const ItemRelationsList = (props: ItemRelationsListProps): JSX.Element => {
const [getList, setList] = useState([]);
// componentDidMount
useEffect(() => {
loadRelation()
}, [])
const loadRelation = (): void => {
HttpService.GetAsync<getListRequest, getListResponse>('getList',{
// params
}).subscribe(res => {
setList(res.data.list);
});
}
return (
<>
<Table
columns={columns}
dataSource={getList}
>
</Table>
</>
)
}
useEffect(yourCallback, []) - will trigger the callback only after the
first render.
Read the Docs hooks-effect
This is related to How to call loading function with React useEffect only once

React Hook useEffect() run continuously although I pass the second params

I have problem with this code
If I pass the whole pagination object to the second parameters of useEffect() function, then fetchData() will call continuously. If I only pass pagination.current_page so It will call only one time, but when I set new pagination as you see in navigatePage() function, the useEffect() does not call to fetchData() although pagination has changed.
How to solve this. Thank you very much!
Besides I do not want the use useEffect() call when first time component mounted because the items is received from props (It is fetch by server, this is nextjs project).
import React, {useEffect, useState} from 'react';
import Filter from "../Filter/Filter";
import AdsListingItem from "../AdsListingItem/AdsListingItem";
import {Pagination} from "antd-mobile";
import styles from './AdsListing.module.css';
import axios from 'axios';
const locale = {
prevText: 'Trang trước',
nextText: 'Trang sau'
};
const AdsListing = ({items, meta}) => {
const [data, setData] = useState(items);
const [pagination, setPagination] = useState(meta);
const {last_page, current_page} = pagination;
const fetchData = async (params = {}) => {
axios.get('/ads', {...params})
.then(({data}) => {
setData(data.data);
setPagination(data.meta);
})
.catch(error => console.log(error))
};
useEffect( () => {
fetchData({page: pagination.current_page});
}, [pagination.current_page]);
const navigatePage = (pager) => {
const newPagination = pagination;
newPagination.current_page = pager;
setPagination(newPagination);
};
return (
<>
<Filter/>
<div className="row no-gutters">
<div className="col-md-8">
<div>
{data.map(item => (
<AdsListingItem key={item.id} item={item}/>
))}
</div>
<div className={styles.pagination__container}>
<Pagination onChange={navigatePage} total={last_page} current={current_page} locale={locale}/>
</div>
</div>
<div className="col-md-4" style={{padding: '15px'}}>
<img style={{width: '100%'}} src="https://tpc.googlesyndication.com/simgad/10559698493288182074"
alt="ads"/>
</div>
</div>
</>
)
};
export default AdsListing;
The issue is you aren't returning a new object reference. You save a reference to the last state object, mutate a property on it, and save it again.
const navigatePage = (pager) => {
const newPagination = pagination; // copy ref pointing to pagination
newPagination.current_page = pager; // mutate property on ref
setPagination(newPagination); // save ref still pointing to pagination
};
In this case the location in memory that is pagination remains static. You should instead copy all the pagination properties into a new object.
const navigatePage = (pager) => {
const newPagination = {...pagination}; // shallow copy into new object
newPagination.current_page = pager;
setPagination(newPagination); // save new object
};
To take it a step further you really should be doing functional updates in order to correctly queue up updates. This is in the case that setPagination is called multiple times during a single render cycle.
const navigatePage = (pager) => {
setPagination(prevPagination => {
const newPagination = {...prevPagination};
newPagination.current_page = pager;
});
};
In the case of pagination queueing updates may not be an issue (last current page set wins the next render battle), but if any state updates actually depend on a previous value then definitely use the functional update pattern,

Resources