Unable to compare props on componentWillRecieveProps dispatch - javascript

I have an infinite loop, while its doing what I want it to, fetches my updated data without refresh its extremely slow as its processing over and over.
I have read a few things and tried a check to see if props have updated but cant get it right...
My infinite loop...
componentWillReceiveProps(nextProps) {
whenGapiReady(() => {
const { Id } = this.props;
this.props.fetchFileUploads(Id)
});
}
tried to change it to:
componentWillReceiveProps(nextProps){
if(nextProps.Id !== this.props.Id)
dispatch(this.props.fetchPlayerFileUploads(Id))
}
without success, its not doing anything now. I've missed out the gapi as I could add this back in either.
Any assistance of what this should be to stop the loop! Also saw componentDidUpdate should be used as componentWillReceiveProps is being depreciated but I will take anything at the moment.
EDIT
componentWillReceiveProps(nextProps) {
whenGapiReady(() => {
const { Id } = this.props;
if(nextProps.Id === this.props.Id) {
this.props.fetchFileUploads(this.props.Id)
}
});
}
The above still loops endlessly...?

You need to spell Receive correctly in the refactored code:
componentWillReceiveProps(nextProps){
// ^^
if(nextProps.Id !== this.props.Id)
dispatch(this.props.fetchPlayerFileUploads(Id))
}

if(nextProps.Id === this.props.Id) {
this.props.fetchFileUploads(this.props.Id)
}
componentWillReceiveProps will be invoked everytime, and nextProps.Id will always equals to this.props.Id and hence the infinite loop, you can instead store the Id to state and improve your comparison from there instead

Related

useEffect causing component to re-render infinitely

I am adding items to the wishlist and once it is added I am trying to re-render the Heart icon to have a number on it to indicate that the Item has been added to the wishlist. What I want to achieve is similar to Twitter's like button, when someone likes the post the number of likes increases immediately. So, in my case when someone likes (wishlists) the product it will increase the number on top of Heart icon. I am using local storage to get the items that are added to the wishlist, and I can re-render the Wishlist component when a new Item is added inside the effect but it makes the component to re-render infinitely which is giving me a warning of Warning: Maximum update depth exceeded
Here is my effect;
const [wishlist, setWishlist] = React.useState([]);
React.useEffect(() => {
if (typeof window !== "undefined") {
const wishlistStorage =
localStorage.getItem("wishlist") === null
? []
: [...JSON.parse(localStorage.getItem("wishlist"))];
if (wishlistStorage.length > 0) {
setWishlist(wishlistStorage);
}
}
}, [wishlist]);
If I remove the wishlist from dependancy array, I need to refresh the page to see the correct number of items. What am I doing wrong here and what would be the best way to approach this. Thank you!
You can remove the wishlist variable from the dependency array.
When you call setWishlist, it'll update the state, the dependency array will check that it has changed and will call the useEffect once more, thus the infinite cycle.
With an empty dependency array, it'll only run when your component mounts, fetching the data from your local storage and initializing your app. From there on, all you need is to handle the state locally and updating the localstorage with any new wishlist items, but that useEffect seems to be for initialization only.
UPDATE #1
The core issue here was that OP was using LocalStorage to transfer data between components, which is not the correct way of doing things. If you happen to stumble upon OP's problem and you're doing that, make sure to study a little bit about sharing data between components.
The most common ways are Context API, Redux and passing data down as props.
You can read more about context here.
Here you have add wishlist in dependency array of useEffect and in that you have added condition if (wishlistStorage.length > 0) { setWishlist(wishlistStorage); } , because setting again the wishlist it will cause re-render and again useEffect will be triggered.
just remove the wishlist from the dependencies
React.useEffect(() => {
if (typeof window !== "undefined") {
const wishlistStorage =
localStorage.getItem("wishlist") === null
? []
: [...JSON.parse(localStorage.getItem("wishlist"))];
if (wishlistStorage.length > 0) {
setWishlist(wishlistStorage);
}
}
}, []);
If I understand correctly, and you need this only for the initial render, then you can simply remove wishlist from the dependency array.
const [wishlist, setWishlist] = React.useState([]);
React.useEffect(() => {
if (typeof window !== "undefined") {
const wishlistStorage =
localStorage.getItem("wishlist") === null
? []
: [...JSON.parse(localStorage.getItem("wishlist"))];
if (wishlistStorage.length > 0) {
setWishlist(wishlistStorage);
}
}
}, []);
You use wishlist as the deps of useEffect hooks, when the wishlist cache exists, wishlistStorage is an object parsed from the local storage JSON string. Then, you call setWishlist(wishlistStorage), cause the hook re-render, the wishlist will have a new reference. The useEffect will execute again when its dependencies changes. That's why re-render infinitely.
You could use lazy initial state
The initialState argument is the state used during the initial render. In subsequent renders, it is disregarded. If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render
Use the cache from the local storage to initial your state if it exists, otherwise, returns a default value.
E.g:
const [wishlist, setWishlist] = React.useState(() => {
if (typeof window !== "undefined") {
const wishlistStorage =
localStorage.getItem("wishlist") === null
? []
: [...JSON.parse(localStorage.getItem("wishlist"))];
return wishlistStorage;
}
return [];
);
you are using setWishlist in useEffect that's observe wishlist, that's why it's lead you to infinity loop cuz when wishlist value changed it will execute the code in useEffect again and again so for my solution i saw localStorage in useEffect i would think it's would be only checked in first render, so you can simply remove [wishlist] -> []
const [wishlist, setWishlist] = React.useState([]);
React.useEffect(() => {
if (typeof window !== "undefined") {
const wishlistStorage =
localStorage.getItem("wishlist") === null
? []
: [...JSON.parse(localStorage.getItem("wishlist"))];
if (wishlistStorage.length > 0) {
setWishlist(wishlistStorage);
}
}
}, []);

Why is this onClick event handler firing twice in my create-react-app

can someone tell me why is this "upvote" onClick handler firing twice?
the logs would indicate it's only running once but the score it controls increases by 2
export default class Container extends Component {
constructor(props) {
super(props);
this.state = {
jokes: [],
};
this.getNewJokes = this.getNewJokes.bind(this);
this.retrieveJokes = this.retrieveJokes.bind(this);
this.upVote = this.upVote.bind(this);
}
upVote(id) {
this.setState(state => {
//find the joke with the matching id and increase score by one
const modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
console.log(modifiedJokes);
return { jokes: modifiedJokes };
});
}
render() {
return (
<div>
<h1>Container</h1>
{this.state.jokes.map(joke => (
<Joke
key={joke.id}
id={joke.id}
joke={joke.joke}
score={joke.score}
upVote={this.upVote}
downVote={this.downVote}
/>
))}
</div>
);
}
}
on the other hand if I rewrite the handler this way, then it fires only once
upVote(id) {
const modifiedJokes = this.state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
this.setState({ jokes: modifiedJokes });
};
My best guess is that in the first case, you are also modifying the state directly, when you do joke.score = joke.score + 1;
Because you are doing this mapping directly on state array variable, and in Javascript, when using array, you are only working with pointer to that array, not creating a copy of that array.
So the mapping function probably takes a shallow copy of the array, and there's where problem happens.
You can use lodash to create a deep copy of the state array before you going to work with it, which will not cause your problem:
https://codesandbox.io/s/great-babbage-lorlm
This doesn't work because React uses synthetic events which are reused when it’s handling is done. Calling a function form of setState will defer evaluation (this can be dangerous when the setState call is event dependent and the event has lost its value).
So this should also work:
this.setState((state,props) => ({ jokes: modifiedJokes}));
I can't locate my source at the moment but, I remember coming across this a while back. I'm sure looking through the React Docs can provide a more in depth explanation
I found out what was wrong. I'll post an answer just in case anyone happens to stumble into this upon encountering the same problem.
When in debug mode react will run certain functions twice to discourage certain operations that might result in bugs. One of these operations is directly modifying state.
when doing
this.setState(state => {
//find the joke with the matching id and increase score by one
const modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
console.log(modifiedJokes);
return { jokes: modifiedJokes };
the map function returns a new array where every elements points to the same element in the original array. therefore by doing
state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
I was effectively directly modifying the state. React runs the setstate function twice in debug mode to weed out this kind of operation.
so the correct "react way"of modifying the attribute of an object nested in an array is to do this instead
this.setState(state =>{
let modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
return {...joke, score:joke.score+1}
}
else{ return joke}
})
return {jokes:modifiedJokes}
})
this way when encountering an element with the correct ID a copy is made of that specific element and the score of that copy is increased by one which doesn't impact the actual element which is still in the state left untouched until it is modified by react committing the new state

this.setState does not seem to be called on react native iOS

I am currently using setState in my react-native app to render a screen.
Below is the code, for some reason the code runs everything normally except
setting the state.
showCard(event) {
const { loadCard } = this.state;
this.setState({ loadCard: true });
console.log(loadCard)
// get value of marker ID
const markerID = parseInt(event.nativeEvent.id);
console.log(markerID)
if (markerID <= this.state.markers.length && markerID != null) {
setTimeout(() => {
//USE THIS FORMAT
this.scrollView.getNode().scrollTo({
x: markerID * (CARD_WIDTH),
animated: true
});
}, 0.01);
console.log(this.scrollView)
}
}
Advice on this matter will be greatly appreciated, as it works on android for me
It does work but setting state is asynchronous so you can't just log the result after, observe with the following change:
this.setState({ loadCard: true }, () => console.log(this.state.loadCard));
The second argument is a callback fired after state is set, but you very rarely need that in reality.
Also setTimeout delay is an integer representing milliseconds not a floating point. If you want it as soon as possible use 0.
You need to bind your functions in the constructor while settling the state
constructor(props){
super(props);
this.state = {
loadCard : false;
}
this.changeCard = this.changeCard.bind(this);
}
changeCard = (userId) => {
this.setState({ loadCard: true});
}
render(){
return(
console('working state'+ this.state.loadCard);
);
}
I have somehow solved the issue. The main cause apparently as I was working with react-native maps dependency, it could not register the input directly from the marker component and required the method to be called in the main map.
But thank you for all the input guys!

Issue with Saving a Value to the Component State

While attempting to add the functionality to my project of saving the term in the Search Bar through refreshing your browser, I got it to where things were being saved correctly. The only issue came up with me saving the term in the "handleTermChange" method of the component, so it would pass undefined if you do not change the term in any way before searching. I attempt to bypass this in the "search" method by checking if the state for the term is empty (as it's live updated with the handleTermChange method). The conditional checking if the term in the state is empty works fine, as you enter it when you search without changing anything in the SearchBar. The first console.log prints out the variable fine, but the second console log still prints out an empty string, I'm probably missing something small, but I can't tell, I'm too tired to see little things.
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
this.setState({term: savedTerm});
console.log(this.state.term);
}
this.props.onSearch(this.state.term);
}
Does this work ? Because of the state updates could be asynchronous, you can't be sure that term will be savedItem without the callback.
Doc here.
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
this.setState({term: savedTerm}, () => {
console.log(this.state.term);
this.props.onSearch(this.state.term);
});
} else {
this.props.onSearch(this.state.term);
}
}
This call is asynchronous, try adding a callback function before accessing the property like:
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
var thisState = this;
this.setState({term: savedTerm}, function () {
console.log(thisState.state.term);
});
}
this.props.onSearch(this.state.term);
}

Reactjs: Checkbox state is update, then reverted when callback function terminates

I'm pretty new to react and I ran into a weird issue today. I have a function called handleCheckBoxClick()
handleCheckboxClick : function (e) {
this.setState({isChecked : e.target.checked},
function () {
if (this.state.isChecked) {
this.props.addToTransactionSubmissions(
this.props.submissionGuid,
this.props.paymentInfo
);
} else {
this.props.removeFromTransactionSubmissions(
this.props.submissionGuid
);
}
}
);
}
This particular function calls a function passed down through a parent called addTo/removeFromTransactionSubmission. The code for both is as follows:
addToTransactionSubmissions : function (guid, paymentInfo) {
if (Object.keys(this.state.transactionSubmissions).length === 0) {
this.setState({submissionType : paymentInfo.status_description.toLowerCase()},
function () {
console.log('it set the state though');
console.log(this.state.transactionSubmissions);
this.toggleButtons(this.state.submissionType);
}
);
}
var newTansactionSubmissions = update(this.state.transactionSubmissions,
{
$merge : {[guid] : paymentInfo}
});
this.setState({transactionSubmissions : newTansactionSubmissions},
function () {
console.log('state is now', this.state.transactionSubmissions);
}
);
},
removeFromTransactionSubmissions : function (guid) {
if (Object.keys(this.state.transactionSubmissions).length === 0) {
this.setState({submissionType : undefined},
function () {
this.toggleButtons(this.state.submissionType);
}
);
}
var newTransactionSubmission = update(this.state.transactionSubmissions,
{
[guid] : {$apply: function (x) {return undefined}}
});
this.setState({transactionSubmissions : newTransactionSubmission},
function () {
console.log('here in remove Transaction');
});
}
The problem I run into is that when addTo/removeFromTransactionSubmissions is called, the checkbox does not changes states, even though the state is changed before addTo/removeFromTransactionSubmissions is called. Through further debugging using Firebug, I discovered that 1) the functions are all being called properly, 2) if I do not set state in addTo/removeFromTransactionSubmissions everything runs without a hitch, and 3) the checkbox becomes unchecked after handleCheckboxClick completely finishes.
I suspect that for whatever reason, the state is being lost when Reactjs is trying to update the DOM. However, I do not know why this is the case and don't know how to further debug. And for clarification, the checkbox is in the child component whereas the transactionSubmissions state is in a parent, and on the click of a checkbox, transactionSubmissions is modified (Child Action modifies Parent state). If this is the wrong way to go about the problem, please tell me.
Basically what I want to do is every time I click/unclick a box, it removes the corresponding object to/from a map of ids to the object. Am I doing something incorrectly?
Thanks for the help!
I think you should use another aproach.
handleCheckboxClick : function (e) {
this.setState({isChecked : e.target.checked})
}
Add a method componentWillUpdate(https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate) or componentDidUpdate(https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate) and handle there the changes that must occur after state change
componentWillUpdate()
{
if (this.state.isChecked)
{
this.props.addToTransactionSubmissions(
this.props.submissionGuid,
this.props.paymentInfo);
} else {
this.props.removeFromTransactionSubmissions(this.props.submissionGuid);
}
}
Also you should not call setState sequencially, it can throw some errors by trying to mutate a component while it was updating.
Every time your code calls setState, react goes trought all the dom, check what has changed and render the changes again, saving processing. Ref:https://facebook.github.io/react/docs/component-api.html#setstate

Categories

Resources