How to sto React useEffect hook from rendering duplicated data - reactjs

I have a component where I use useEffect and inside call a function fetchLatest(). fetchLatest() is an action that makes a network request to get all movies that have been released during the current year. I am using Redux and in my store I have a state.movies.latest and each time I start the app for the first time the latest row of movies is accurate with no duplicated data. When I render a different component and go back to my home component where I do fetchLatest I then get duplicated data.
I figured out that my problem is because in my reducers I return something like...
return {
...state,
latest: [...state, ...payload]
}
where payload is an array of objects (movies).
So I changed my reducers to...
return {
...state,
latest: [...payload]
}
It works now and I don't get duplicated data but my concern is that I am not doing or understand this correctly. If I were to use useEffect and have my fetchLatest function in my useEffect hook and pass it as a dependency to my understanding my function fetchLatest will be recreated after every render cycle as a brand new object. Being that I already have in the store my latest array of movies I do not want to re-render a brand new object each time causing to add to my store in my state.movies.latest duplicated data. I at least believe this is what it is doing.
I decided that maybe I should be using useCallback to ensure that this brand new object of fetchLatest is not created unless its dependencies change.
So what I am not sure about is maybe I can have my updated reducer as I have it now and also useCallback to ensure no infinite loops are running for recreating my fetchLatest function each time as a new object when I re-build that component?
Also, I might not understand actually what useCallback does because I tried before using a useCallback and having my reducer as I originally had it and to my understanding if nothing changed then why is it that my fetchLatest runs when I rebuild the component. I only want my latest movies which I already have in the store when I first mount my component
my code for this component looks like this
const Latest = ({ fetchLatest, latest }) => {
useEffect(() => {
fetchLatest();
}, [fetchLatest]);
return (
<div className='genre-temp'>
<h3 className='text-light'>Latest</h3>
<div className='movies-container d-flex'>
{latest.map((movie) => (
<Movie
key={movie.id}
movieId={movie.id}
image={movie.image}
title={movie.title}
description={movie.description}
/>
))}
</div>
</div>
);
};
and what I had tried to do initially was add this useCallback and call handleFetching in my useEffect and pass it in as a dependency.
const handleFetching = useCallback(() => {
fetchLatest()
}, []);
However, useCallback in conjunction with useEffect while still having my reducer as such...
return {
...state,
latest: [...state, ...payload]
}
still would run each time the component rebuilt.

Try to add a request state to your state object. Then, you'll be able to check if the list is fetched. Usually, I use four states for requests: init, request, success, failure. Here is a rough example of a request:
if (requestState !== 'request' && requestState !== 'success') {
try {
setRequestState('request');
const response = await fetchList();
setList(response);
setRequestState('success');
catch (e) {
setRequestState('failure');
log(e);
}
}
If you have a request state, you'll always know when you need to request the data, show a spinner, show an error message, etc.
Hopefully, you got the idea.
P.S. In your code, it's supposed to be:
{
...state,
latest: [...payload]
}
latest: [...state.latest, ...payload] won't work as you expect.
latest: [...state, ...payload] looks wrong.

Related

React - an reponse data array is cleared after assigning it to state in a useEffect hook

I use MongoDB to store data. The database works properly, as it can be tested in a when an endpoint is access. I use the useEffect hook to fetch data from the database. The code is as follows:
const [persons, setPersons] = useState([]);
useEffect(() => {
personService.getAll().then((response) => {
console.log(response.data);
setPersons(response.data);
console.log(persons);
});
}, []);
The first console.log() logs the result as expected, an array of JavaScript objects is returned. However, after assigning it to persons state using setPersons method, the state remains empty and nothing is rendered. What could be the problem here and how to make data appear on the page?
EDIT: I solved the problem it was because MongoDB returns fields as objects, even though they appear to be strings. I had to redefine the toJSON method using mongoose.
Because state only has value when component rerender. So you should put console.log into useEffect to check when component re-render success.
useEffect(() => {
console.log(persons);
}, [persons]);
Basically the setState actions are asynchronous and are batched for performance gains. This is explained in the documentation of setState as well.
So when you call setPersons(response.data), this action may not be even be completed, and you are logging persons.
If you log the persons outside the useEffect you should be able to see it, as component would rerender as soon as the state is updated, logging "persons".

React Useeffect running when page loads

Am using useEffect in a react functional component to fetch data from an external API but it keeps calling the API endpoint on render on the page .
Am looking for a way to stop the useeffect from running on render on the component
Use the dependency array (second argument to the useEffect), so you can specify when you need to run the useEffect.
The problem here is that you have not used the dependency array, so that it executes every time. By adding a dependency array, you specify the changes where you want useEffect to run.
useEffect(()=>{
},[<dependency array: which contains the properties>]);
If you leave the dependency array empty, it will run only once. Therefore if you want the API call to run only once, add an empty array as the second argument to your useEffect. This is your solution.
Like this:
useEffect(()=>{
//Your API Call
},[]);
useEffect is always meant to run after all the changes or render effects are update in the DOM. It will not run while or before the DOM is updated. You may not have given the second argument to useEffect, which if u do not provide will cause the useEffect to execute on each and every change. Assuming you only want to call the API just once when on after the first render you should provide an empty array.
Runs on all updates, see no second argument to useEffect:
useEffect(() => { /* call API */ });
Runs when the prop or state changes, see the second argument:
useEffect(() => { /* call API */ }, [prop, state]);
Runs only once, see the empty second argument:
useEffect(() => { /* call API */ }, []);
I recommend you to read the full documentation about the React useEffect hook.
Here is a easy example of using useEffect
function functionalComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
const url = 'https://randomuser.me/api/?results=10';
fetch(url)
.then(data => {
setData(data);
})
.catch(error => console.error(error))
}, []); // it's necessary to use [] to avoid the re-rendering
return <React.Fragment>
{data !== null && (
<React.Fragment>
{data.results.map(data => (
<div>
{data.gender}
</div>
))}
</React.Fragment>
)}
</React.Fragment>;
}
Maybe in your useEffect implementation you are avoiding the [] dependencies, this is a bit hard to understand if you come from class states. This on hooks review when a state element inside the hook change, for example if you are using an element that always change like a prop that you pass throught another component you might be setting inside the dependencies or another state, if you do not need any dependency just use it empty like the example above. As you can see in the documentation sometimes the dependencies are not used, this might generate an infinite loop.

Do I have to worry about useState causing a re-render?

NB: I've asked this on wordpress.stackexchange, but it's not getting any response there, so trying here.
I'm not sure if this is WordPress specific, WordPress's overloaded React specific, or just React, but I'm creating a new block plugin for WordPress, and if I use useState in its edit function, the page is re-rendered, even if I never call the setter function.
import { useState } from '#wordpress/element';
export default function MyEdit( props ) {
const {
attributes: {
anAttribute
},
setAttributes,
} = props;
const [ isValidating, setIsValidating ] = useState( false );
const post_id = wp.data.select("core/editor").getCurrentPostId();
console.log('Post ID is ', post_id);
const MyPlaceholder = () => {
return(
<div>this is a test</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
If I comment out const [ isValidating, setIsValidating ] = useState( false ); then that console.log happens once. If I leave it in, it happens twice; even if I never check the value of isValidating, never mind calling setIsValidating. I don't want to over-optimize things, but, equally, if I use this block n times on a page, the page is getting rendered 2n times. It's only on the admin side of things, because it's in the edit, so maybe not a big deal, but ... it doesn't seem right. Is this expected behavior for useState? Am I doing something wrong? Do I have to worry about it (from a speed perspective, from a potential race conditions as everything is re-rendered multiple times)?
In your example code, the console.log statement is being immediately evaluated each time and triggering the redraw/re-rendering of your block. Once console.log is removed, only the state changes will trigger re-rendering.
As the Gutenberg Editor is based on Redux, if the state changes, any components that rely on that state are re-rendered. When a block is selected in the Editor, the selected block is rendered synchronously while all other blocks in the Editor are rendered asynchronously. The WordPress Gutenberg developers are aware of re-rendering being a performance concern and have taken steps to reduce re-rendering.
When requesting data from wp.data, useEffect() should be used to safely await asynchronous data:
import { useState, useEffect } from '#wordpress/element';
export default function MyEdit(props) {
...
const [curPostId, setCurPostId] = useState(false);
useEffect(() => {
async function getMyPostId() {
const post_id = await wp.data.select("core/editor").getCurrentPostId();
setCurPostId(post_id);
}
getMyPostId();
}, []); // Run once
const MyPlaceholder = () => {
return (
<div>Current Post Id: {curPostId}</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
As mentioned in the question, useState() is used in core blocks for setting and updating state. The state hook was introducted in React 16.8, its a fairly recent change and you may come across older Gutenberg code example that set state via the class constructor and don't use hooks.
Yes, you have to worry about always put an array of dependencies, so that, it won't re-render, As per your query, let's say are planning to edit a field here is the sample code
const [edit, setEdit]= useState(props);
useEffect(() => {
// logic here
},[edit])
that [edit] will check if there is any changes , and according to that it will update the DOM, if you don't put any [](array of dependencies) it will always go an infinite loop,
I guess this is expected behavior. If I add a similar console.log to native core blocks that use useState, I get the same effect. It seems that WordPress operates with use strict, and according to this answer, React double-invokes a number of things when in strict mode.

React state resets with Socket.io

I am currently struggling with issues pertaining to socket.io and React. Whenever socket.on() is called, the state resets, and all the previous chat data is gone. In other words, whenever the user receives a message, the 'messages' state is reset.
I saw a similar post on this Socket.io resets React state?, but I couldnt seem to apply the same solution to my issue. Any help would be appreciated!
function ChatComponent(props) {
const [messages, setMessages] = useState([]);
const [socket, setSocket] = useState(socketioclient("*********"));
function socket_joinRoom(room) {}
function _onMessageUpdate(message) {
setMessages([
...messages,
{
author: "them",
type: "text",
data: { text: message },
},
]);
}
useEffect(() => {
socket_joinRoom(parseInt(props.props[0], 10));
socket.on("updateMessage", (message) => {
//** When this is called, the state resets*
console.log(messages);
_onMessageUpdate(message);
});
return () => {
socket.off("updateMessage");
socket.disconnect();
};
}, []);
function _onMessageWasSent(message) {
setMessages([...messages, message]);
socket.emit("sendMessage", message.data.text);
}
return (
<div className="chatComponent" style={{ height: "100%" }}>
<Launcher
agentProfile={{
teamName: `Ongoing: Room #${props.props[0]}`,
}}
onMessageWasSent={_onMessageWasSent}
messageList={messages}
isOpen={true}
showEmoji
/>
</div>
);
}
first of all you need to separate the joining room logic in it's own useEffect
useEffect(()=>{
socket_joinRoom(parseInt(props.props[0],10));
},[props.props[0]])
cause you are going to listen to the changes of the received messages in another useEffect
so you don't need to init the joining logic every time you receive anew message
and for the message function it can be another useEffect which listen to the received "messages" as this following code
useEffect(()=>{
socket.on('message',(message)=>{
setMessages((currentMessages)=>[...currentMessages,message])
}
},[])
this useEffect will fire and listen to the changes and add the new message to the previous messages
I'm answering this for other people searching along the terms of " why does socket.io reset my state when .on or other event listeners are used.
This turned out to be a simple useEffect() behavior issue and not an issue within socket.io. Although this is the case I feel that socket.io made an oversight not communicating this in their react hooks documentation.
Below is problem code.
useEffect(() => {
socket_joinRoom(parseInt(props.props[0], 10));
socket.on("updateMessage", (message) => {
//** When this is called, the state resets*
console.log(messages);
_onMessageUpdate(message);
});
return () => {
socket.off("updateMessage");
socket.disconnect();
};
}, []);
The above useEffect runs a single time at the mounting of this component. One may deduce that the variables used in writing this useEffect would remain dynamic and run once executed by socket.io, however it is not dynamic, it is static and the useState called in 'update_message' remains static and at the value it was during the mounting of the parent component.
To reiterate your component mounts and because of [] at the the end of useEffect the useEffect runs a single time at component mount. There is abstraction within socket.io in which the callback function ran by .on('update_message') is saved and the variable values are frozen at the time of mount.
This obviously seems like your state is being reset however that is not the case. You are just using the out dated states.
Like the other poster responded if you have a socket.io listener that needs to change dynamically based on variables or states that update you need to write a separate use effect with a dependency based on the state you wish to remain dynamic, you MUST also return a socket.off('updateMessage') so that the old listener is removed with the old state, everytime your state is updated. If you don't do this, you may also get old out of date states
useEffect(()=>{
socket.on('updateMessage', (message)=>{
onMessageUpdate(message)
})
return ()=>{
socket.off('updateMessage')
},[messages])

Infinite useEffect loop when using Redux with React Hooks

I am refactoring some code and turning my class components into function components as a way of learning how to use Hooks and Effects. My code uses Redux for state management and axios for database requests with Thunk as middleware for handling asynchronicity. I'm having an issue in one component that does a get request to retrieve a list of customers on what used to be componentDidMount. No matter what I try, the useEffect function gets into an infinite loop and continues requesting the customer list.
The component in question, CustomersTable, gets a list of customers from the database and displays it in a table. The component is wrapped by a container component that uses Redux's connect to pass in the retrieved list of customers to the CustomersTable as a prop.
useEffect(() => {
loadCustomers(currentPage, itemsPerPage, sortProp, (ascending ? 'asc' : 'desc'), {});
}, []);
loadCustomers is a Redux action that uses axios to fetch the customer list. currentPage, itemsPerPage, sortProp and ascending are state variables that are initialized to specific values on 'component mount'
I would expect that because I use the empty array that this would run only once. Instead it runs continuously. I can't figure out why this is happening. My best guess is that when redux gets the list, it returns a new object for state and therefore the props change every time, which then triggers a re-render, which then fetches a new list. Am I using this wrong in that Redux isn't meant to be used with hooks like this?
I ended up getting this working by adding the following:
useEffect(() => {
if (!list.length) {
loadCustomers(currentPage, itemsPerPage, sortProp, (ascending ? 'asc' : 'desc'), {});
}
}, []);
I'm not sure this is the behavior I truly want though. If the list of customers was truly 0, then the code would continue to fetch the list. If the list were truly empty, then I would want it to fetch only once and then stop. Edit: Turns out this definitely doesn't work. It works for the initial load, but breaks the code for any delete or edit.
OK, providing more context here. The container component that wraps the CustomersTable is:
import { connect } from 'react-redux';
import loadCustomers from './actions/customersActions';
import { deleteCustomer } from './actions/customerActions';
import CustomersTable from './CustomersTableHooks';
function mapStateToProps(state) {
return {
customers: state.customers,
customer: state.customer
};
}
export default connect(mapStateToProps, { loadCustomers, deleteCustomer })(CustomersTable);
The action, loadCustomers is:
export default function loadCustomers(page = 1, itemsPerPage = 50, sortProp = 'id', sortOrder = 'asc', search = {}) {
return (dispatch) => {
dispatch(loadCustomersBegin());
return loadCustomersApi(page, itemsPerPage, sortProp, sortOrder, search)
.then(data => dispatch(loadCustomersSuccess(data)))
.catch(() => dispatch(loadCustomersFailure()));
};
}
the reducer for customers is:
export default function customersReducer(state = initialState, action) {
switch (action.type) {
case types.LOAD_CUSTOMERS_BEGIN:
return Object.assign({}, state, { isLoading: true, list: [], totalItems: 0 });
case types.LOAD_CUSTOMERS_SUCCESS:
return Object.assign({}, state, { isLoading: false, list: action.customers || [], totalItems: action.totalItems });
case types.LOAD_CUSTOMERS_FAILURE:
return Object.assign({}, state, { isLoading: false, list: [], totalItems: 0 });
default:
return state;
}
}
I unfortunately can't post much of the CustomersTable itself because things are named in a way that would tell you what company I'm working for.
So, if i understand your code correctly, you are dispatching the loadCustomers action in child component within useEffect but you read actual data in parents mapStateToProps.
That would, of course, create infinite loop as:
parent reads customers from store (or anything from the store, for that matter)
renders children
child fetches customers in useEffect
properties on parent change and cause re-render
whole story goes on forever
Moral of the story: don't dispatch from presentational components. or, in other words, dispatch an action from the same component you read those same properties from store.
On every render you get new customer object because mapStateToProps if doing shallow equal. You could use memoized selectors to get customers, and it won't rerender when is not needed to.
import { createSelectorCreator, defaultMemoize } from 'reselect';
const createDeepEqualSelector = createSelectorCreator(defaultMemoize, deepEqual);
const customerSelector = createDeepEqualSelector(
[state => state.customerReducer.customers],
customers => customers,
);
I disagree with the most voted answer here.
properties on parent change and cause re-render
whole story goes on forever
A re-render will not call the function argument of useEffect when the 2nd argument of dependencies is and empty array. This function will only be called the 1st time, similar to the life cycle method componentDidMount. And it seems like this topic was created because that correctly expected behavior wasn't occurring.
It seems like what is happening is that the component is being unmounted and then mounted again. I had this issue and it brought me here. Unfortunately, without the component code we can only guess the actual cause. I thought it was related to react-redux connect but it turns out it wasn't. In my case the issue was that someone had a component definition within a component and the component was being re-defined / recreated on every render. This seems like a similar issue. I ended up wrapping that nested definition in useCallback.

Resources