Example Code:
(Pretend we are inside component function)
let count = useRef(0).current
useEffect(()=>{
count++
console.log(count)
}, [count])
Question:
What will happen if I run this code
(I am afraid that the endless loop execution will blow up my m1 chip macbookair, so I didn't run ^_^).
Should I awalys some_ref_object.curent = some_value to change the value?
The code probably will not do what you expect.
useRef returns a mutable object, where the object is shared across renders. But if you extract a property from that object into a variable and then reassign that variable, the object won't change.
For the same reason, reassigning the number below doesn't change the object:
const obj = { foo: 3 };
let { foo } = obj;
foo = 10;
console.log(obj);
In your code, the ref object never gets mutated. It is always the following object:
{ current: 0 }
So
let count = useRef(0).current
results in count being initially assigned to 0 at the beginning of every render.
This might be useful if for some odd reason you wanted to keep track of a number inside a given render, not to persist for any other render - but in such a case, it'd make a lot more sense to remove the ref entirely and just do
let count = 0;
Your effect hook won't do anything useful either - since count is always 0 at the start of every render, the effect callback will never run (except on the first render).
Should I awalys some_ref_object.curent = some_value to change the value
You should use current, not curent. But yes - if you want the change to persist, assign to a property of the object, instead of reassigning a standalone variable. (Reassigning a standalone variable will almost never have any side-effects.)
But, if you have something in the view that the count is used in - for example, if you want to return it in the JSX somewhere - you probably want state instead of a ref (or a let count = 0;), so that setting state results in the component re-rendering and the view updating.
I just tried it on my colleague's computer, and fortunately it didn't blow up
Conclusion 1:
The useEffect won't effect, because ref can't be denpendency.
Conclusion 2:
Only let count = useRef(0).current is the right way.
Related
I am making a NewsCardComponent which will display slideshow of images provided in an array which will be passed down as a prop. Everytime the component is used, will have a different number of elements in images array. so i put the "imgArr" in src of img as:
<img src={imgArr[index]}>
where "index" is the state and i have to dynamically check if a component has reached to the end of array then setIndex to zero. I have achieved what I wanted but i dont know why all the techniques other than first are not working.
My useEffect:
useEffect(() => {
const interval = setInterval(() => {
indexResetter();
}, 2000);
return () => {
clearInterval(interval);
};
}, []);
Technique 1 (working fine) :
function indexResetter() {
setIndex((prev) => {
let newIndex = prev + 1;
if (newIndex > imgArr.length - 1) {
newIndex = 0;
}
return newIndex; }); }
Technique 2 (state is not setting to zero but increasing infinitely):
function indexResetter() {
let newIndex = index + 1;
if (newIndex === imgArr.length - 1) {
setIndex(0);
} else {
setIndex((prev) => prev + 1);
}
}
Technique 3 (same problem with second one):
function indexResetter() {
if (index >= imgArr.length - 1) {
setIndex(0);
} else {
setIndex((prev) => prev + 1);
}
}
In short, your useEffect() runs on initial mount, meaning that the setInterval() continues to execute the indexResetter function that was defined on the initial render, even after subsequent rerenders when new indexResetter have been created. That means the version of the indexResetter function that you end up executing only knows about the index state upon the initial mount, giving you the issue where your index doesn't change.
For more details, when you define a function, it stores a reference to the "scope" it's defined in, which includes all the variables defined in that scope:
function NewsCardComponent() {
const [index, setIndex] = useState(0);
function indexResetter() {
...
}
}
When your component above renders, it does the following:
The NewsCardComponent function gets called, creating a new "scope" (formally an environment record). This scope holds the variables and functions (bindings) such as index created within the function.
The indexResetter function gets created (note: it's just being created, it's not being called yet). This function stores an internal reference to the scope created in step 1. This is called a closure.
Later on, when indexResetter gets called, it uses the scope that it stored internally at step 2 to work out the value of the index variable.
When you update your index state using setIndex(), your component rerenders, and performs the above two steps 1 and 2 again. When this occurs, it creates a new scope for the NewsCardComponent that now holds the updated value of index, as well as creates a new indexResetter function. This means that each time you call setIndex, you effectively create new versions of the indexResster function that can see the new value of index. The value of index in the previous NewsCardComponent scope is still what it was before, and so the indexResetter function created in the previous render can still only see the old index value. The new index value is only available in the newly created scope that was created as part of the rerender.
Your problem is that your useEffect() only runs on the initial mount of your component, so the function that you're calling within your setInterval() is the first indexResetter function that was created on the initial mount. As a result, it only has visibility of the value of index for when your component initially mounted:
const interval = setInterval(() => {
indexResetter();
}, 2000);
On subsequent rerenders, the indexResetter function will be recreated, but the above setInterval() will continue to call the version of the indexResster function that was defined on the initial render (again due to a closure), which only knows about the index state at that time. As a result, the value of index within the function ends up always being the initial value.
In your working example, you're using the state setter function to access prev, which means you're no longer relying on the index value from the surrounding scope of your function. The state setter function will provide you with the most up-to-date value of your state, and so you don't face the same issue.
From what I am reading, your use effect only runs during the first render. Put index in the dependency array in your useEffect so that it runs everytime that your index changes
#robinmuhia254 is right!
The index should be passed as a dependency to the useEffect for the change to run whenever the index changes. In that case, all your techniques will result in the same output.
If you do not pass the index dependency, Techniques 2 and 3 will not meet the if-crieria(index doesn't have an updated value, it's 0 always), and the setTimeout will run the else part to increase the counter. Technique 1 works in this case because you do not bank on the index state variable at all for your computations. You compute based on the prev value.
I hope that explains!
Btw, Technique 2 has got a logical error. If you set newIndex = index + 1, the carousel will start-over before it reaches the last element.
I have made a small demo on your use case where I have also fixed the Technique 2 logic. Please have a look:
https://stackblitz.com/edit/react-1mbtpm?file=src/App.js
I'm trying to create a resuable component that will be able to take a function passed down into it, allowing it to update the state of the parent component. I've got a demo on CodeSandbox that shows the issue.
TrackListing is passed the removeTrack function from the parent, and when the delete button is clicked inside the TrackListing, the function is called with the rows index to be removed from the tracks state. Unfortunately this doesn't seem to update the tracks state.
Your not changing the state, tracks it's the same tracks.
In JS if you do -> var a = [1,23]; var b = a; console.log(a === b); it would return true. Assigning an array to another var, does not make a new array.. Doing var a = [1,23]; var b = [...a]; console.log(a === b); will do a shallow copy of a, and this as expected will return false. IOW: In react causing a state change.
Also splice(index,index), I'm pretty sure you meant splice(index, 1).
const removeTrack = (index: number) => {
const t = tracks;
t.splice(index, index);
console.log(t);
setTracks(t); //t is still the same instance of t.
};
So you will need to make a copy of t.
const removeTrack = (index: number) => {
const t = tracks;
t.splice(index, 1);
setTracks([...t]); //lets make a copy of `t` state has changed.
};
As pointed out in the comments, using the callback version of setState is a good idea, it will prevent issues of scope, here scope isn't an issue, but it's easy to get caught out by it.
Also another issue I've found with your current implementation you might want to look into, using array index as a key is not a good idea, it's not unique and you could get some funky rendering quirks, you really need unique ids for your tracks to use as the key
eg. This bit ->
tracks.map((track, index) => (
<tr key={index}> //not a good idea.
tracks.map((track, index) => (
<tr key={track.id}> //much better
Also when it comes to deleting, use the track id, using the callback/filter version in comments from Ali, you could also then just change removeTrack(track:Track) {}, and pass the track. IOW: remove index array positions from all code.
I've done a fork of your CodeSandBox with these changes to make it easier. CodeSandBox
Am using function based component and am having trouble in pushing a subarray into a useState array.
my code is shown below. There is an array called mode coming from the props which i need to append it as a sub array of more
const ViewCharts = (props) =>{
//other codes
let [more,setMore] = useState([])
useEffect(()=>{
console.log(props.mode,' mode array')
let temp = [...more,props.mode]
console.log(temp, ': this will append to more')
setMore(temp)
setTimeout(()=>{
console.log(more,'after setting')
},2000)
},[//some value])
}
when the props.mode has value ['top','bottom'] i expect more to have value of [['top','bottom']] and when next time if the props.mode is ['top'] i need more to have [['top','bottom'],['top']]. but this is what am getting when i run the above code.
["top"] mode array
["top"] : this will append to more"
[] : "after setting"
why the setMore is not adding array even when the temp is having the expected value.
If I remember correctly the useState variable will change value in the next render when you set it. You are trying to read the new more value in the same render you've changed it (as you are reading it in the same effect you've set the value in), which will be [] the first time as that's how you initialised it.
Try creating a second useEffect with a more dependency to see if it gives you the value you want:
// You can also just console.log() before the return, needing no `useEffect` to see if the value indeed changed.
React.useEffect(() => {
console.log('More: ', more);
}, [more]);
https://reactjs.org/docs/hooks-state.html#recap
Line 9: When the user clicks, we call setCount with a new value. React will then re-render the Example component, passing the new count value to it.
I would suggest reading the hooks API to better understand how they work.
I was doing the official ReactJs tutorial and I started to get confused about the meaning and application of immutability.
An example of the way I thought it was. Imagine i have an object in a constructor this.state = { dog: some data} and then I call a handleClick function which sets this.setState = {dog: another data}. Since I was using setState to update the value, the old reference would still be immutable, I could get to know what changed.
But in the tutorial, when they copy the "squares" state with .slice() to not change the original array and then apply the setState with a new value, I got confused.
Why they need to copy the array, couldnt they just make a reference of it in setState and change things there? The original reference would still be traceable...
EDIT:
#FelixKling Here:
class Board extends React.Component {
constructor() {
super();
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
}
Couldn't they just put that inside the setState, instead of copying the initial array?
This is a really great article that helps to explain why immutability is important: http://reactkungfu.com/2015/08/pros-and-cons-of-using-immutability-with-react-js/
In fact, React.js does not need to have knowledge about what exactly changed. All it needs to know is whether the state changed at all or not.
While immutability does not provide easier answers to a what exactly changed problem, it provides a great answer to the is it changed at all or not question.
React, as the name suggests 'reacts' to changes in sate and will re-render components as the state changes. So for React, knowing if the state changes is important, unfortunately, mutating an object can make it difficult to compare.
Consider this example:
var a = 6; // numbers are primatives in javascript and compare by value
var b = 6;
var c = a; // 6
// in this case
a === b // true
b === c // true, 6
But you can't do the same for objects as they are compared by reference, that is are they pointing to the same point in memory.
var objA = { name: 'Sally' };
var objB = { name: 'Sally' };
var objC = objA;
// these are not the same object
objA.name === objB.name // true, 'Sally'
objA === objB // false, they reference different objects even though the keys and values are the same
objA === objC // true, they reference the same point in memory
The issue that arrises in React is more easily illustrated when you try and modify a value.
Let's take our number example again and make some changes:
c = 14;
a === b // true, still 6, the value of 'a' is still 6
a === c // false, 6 ≠ 14
That makes, sense we've changed the value of c, it should now be different. Let's modify our objects now.
objA.age = 30; // give objA an age property
objB.age = 35; // give objB an age property
objC.age = 40; // give objC an age property, actually changes objA
// now try equality
objA === objB // false, we already know this
objA.age === objB.age // false, as expected
objA.age === objC.age // true, possibly unexpected as it looks like we are comparing 30 to 40, but in fact our change to objC modified objA
objA === objC // true both have age set to 40
So if instead we give React a new state each time, instead of mutating it, then it's much easier to know if it should re-render.
You might be wondering then, why react doesn't just store a copy of the state and compare it to the mutated state. Maybe you're not wondering this, but it seems like this is something that React/Redux could handle and then we could just be free to mutate the state, making it easier for us.
Well it turns out that comparing two objects is quite an intensive task, first you have to make sure that they both have the same keys, and that those keys hold the same values - doesn't seem that hard - but then you realise that objects can contain many other nested objects and this comparison become recursive, checking each nested object.
It would be much easier to just pass in a new state, so that React can tell right away that it has changed.
On top of this, generating a new state also allows you to 'time travel' by keeping track of and storing previous states if you choose to.
A bit further down in the tutorial they detail the importance of immutability:
https://facebook.github.io/react/tutorial/tutorial.html#why-immutability-is-important
You can also read a lot about Immutability here: https://facebook.github.io/immutable-js/
Immutable JS is a library Facebook created for object Immutability
TLDR Links: It's basically easier to track changes and easier to determine when to re-render.
Edit regarding comment:
So you're correct that this.setState does update the object rather than mutating it.
In this example, we create a copy instead of mutating.
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
});
}
In this example, we mutate instead of creating a copy
handleClick(i) {
this.state.squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
});
}
For the first example where we did not mutate, when React runs through its lifecycle events after this.setState is called and gets to shouldComponentUpdate, here's what would happen
shouldComponentUpdate(nextProps, nextState) {
// this will return true and trigger an update
return this.state.squares !== nextState;
}
For the second example, where we did mutate, here's what would happen:
shouldComponentUpdate(nextProps, nextState) {
// this will return false and will not trigger an update
return this.state.squares !== nextState;
}
and now, even though we've changed our state, since our current, mutated state is the same as our next state (set in this.setState) our react component won't update.
How do I increment an integer inside a variable, every time that variable is called? Javascript.
var a=0;
var t=loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+x[a].getElementsByTagName("name")[0].childNodes[0].nodeValue+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en").getElementsByTagName("bio");
for (i=0;i<20;i++)
{
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=t[0].getElementsByTagName(\"summary\")[0].childNodes[1].nodeValue;'>Open Bio</button></div>");
}
I'm not sure how I would go about incrementing variable a. I need it to increase by 1 every time variable t is called in the for loop.
When I put all of the code in the for loop I get [object node list] returned so this method is not desired.
If I understood your question correctly, you could define your own getters and setters for the property.
var o = {}
o.__defineSetter__('property', function(value) { this._counter = 0; this._holder = value; })
o.__defineGetter__('property', function() { console.log(this._counter++); return this._holder; })
The counter would be reset every time o.property is assigned a value
o.property = 'Some value'
and then increase every time the property is accessed.
So,
console.log(o.property)
would print
0
Some value
to the console. And if you do it again, it would print
1
Some value
After your edit I think I can see your problem now. You will need to put the loadXMLDoc statement in the loop (since you want to load 20 different XML files), but you can't assign the result of every call to the same variable t - as once the button is clicked, the handler will evaluate t and get only the last value.
Instead, use an array:
var bios = []; // empty array
for (var i=0; i<20; i++) {
var artist = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue,
doc = loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+artist+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en"),
bio = doc.getElementsByTagName("bio")[0].getElementsByTagName("summary")[0].childNodes[1].nodeValue;
bios[i] = bio; // store it in the array
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=bios["+i+"];'>Open Bio</button></div>");
}
Of course, while that will work it's a bunch of bad practises, including
unsecured accessing of DOM nodes/properties. If the xml changes its format, you will get lots of exceptions here. You might be sure now that this never happens, but wrapping artist and bio in try-catch might not be a bad idea.
snychronous Ajax. One can do better than that.
loading 20 documents (and that sequentially!) even if you don't need them. It might be worth to try loading each of them only when the respective button is clicked.
document.write
Inline attribute event handlers
…and creating them even by JS.