Javascript pass by reference and props in react - javascript

As I understand correctly in Javascript we have two different passing: by value and by reference. By value is when we pass the strings or numbers. By reference when we pass the objects or arrays.
Last time in some project I noticed antipattern -> someone assigned new value to this.props.arrayOfNames = newArrayOfNames;
I haven't had the chance to test it, but... will it change somehow the parent's object? By reference?

this.props.arrayOfNames = newArrayOfNames is not an antipattern but a mistake. props object is immutable, this is ensured by the use of Object.freeze in development mode. The assignment of arrayOfNames property value will result in error.
If props should differ from received ones, a correct way is to use a state that derives from props with getDerivedStateFromProps hook.

Related

vuejs3 reactivity of props object

I'm very enthausiast about vuejs but the composition API made me get lost slightly on reactivity:
In a .vue child component a prop is passed (by another parent component), movies:
export default {
props:{
movies: Array
},
setup(props){}
.
Placing in the setup function body of this child component:
A) console.log("props: ",props) // Proxy {}
[above EXPECTED] above tells me that the props object is a reactive proxy object.
B) console.log("props.movies: ",props.movies); // Proxy {}
[above UNEXPECTED] above tells me that the props key 'movies' is a reactive proxy object as well, why? (thus why is not only the main props object reactive? Maybe I should just take this for granted (and e.g. think of it as the result of a props = reactive(props) call)
--> ANSWER: props is a **deep ** reactive object (thanks Michal Levý!)
C) let moviesLocal = props.movies ; // ERROR-> getting value from props in root scope of setup will cause the value to loose reactivity
[above UNEXPECTED] As props.movies is a reactive object, I'd assume that I can assign this to a local variable, and this local variable then also would become reactive. Why is this not the case (I get the error shown above)
D) let moviesLocal = ref(props.movies);
moviesLocal.value[0].Title="CHANGED in child component";
[above UNEXPECTED]
I made a local copy in the child component (moviesLocal). This variable I made reactive (ref). Changing the value of the reactive moviesLocal also causes the 'movies' reactive object in the parent object to change, why is that ? This even holds if I change D: let moviesLocal = ref(props.movies); to let moviesLocal = ref({...props.movies});
Thanks a lot! Looking forward to understand this behaviour
Use toRef to maintain reactivity.
const moviesLocalRef = toRef(props, 'movies')
See https://v3.vuejs.org/api/refs-api.html#toref
The rule is that a reactive object child property must always be accessed from the root of the object. a.b.c.d. You cannot just break off a piece and modify it, because the act of resolving from the root is what allows Vue to track changes.
In your case, the internal property is the same, but Vue has lost the ability to track changes.
As a side note, you can also create a computed reference.
const moviesLocalRef = computed(()=>props.movies)
Whether you use a computed ref or toRef, you will need to access your property using theRef.value outside of templates. As I state in conclusion, this is what allows Vue to maintain the reactivity.
Conclusion
Here is how I have chosen to think about Vue3 reactivity.
The act of dereferencing a property from an object is what triggers the magic.
For a reactive object, you need to start with the reactive object and then drill down to your property of interest. For a ref, you need to unbox the value from the ref. Either way, there is always a get operation that triggers the internal mechanism and notifies Vue who is watching what.
This drilling down happens automatically in templates and watchers that are aware they received a reference, but not anywhere else.
See https://v3.vuejs.org/guide/reactivity.html#how-vue-tracks-these-changes
Important part of understanding Vue reactivity is understanding JavaScript - specifically difference between Value and Reference. If you are not sure what I'm talking about, read the article carefully...
In following examples I will be using props object as it was created following way (in reality it is not created exactly like this but runtime behavior is very similar):
const props = reactive({ movies: [], price: 100 })
B) console.log("props.movies: ",props.movies); // Proxy {}
above tells me that the props key 'movies' is a reactive proxy object as well, why?
Because when Vue creates new reactive object (by wrapping existing object into a Proxy), that conversion is deep - it affects all nested properties (this behavior can be changed by using for example markRaw or shallowRef)
In terms of Value vs Reference the props is a variable holding a reference to a reactive proxy object. That object has a property movies which holds the reference to a reactive proxy of array (arrays are also technically Objects)
C) let moviesLocal = props.movies ;
// ERROR-> getting value from props in root scope of setup will cause the value to loose reactivity
As props.movies is a reactive object, I'd assume that I can assign this to a local variable, and this local variable then also would become reactive. Why is this not the case
Variables are not reactive. Only Objects can be reactive. In this case moviesLocal variable is assigned with reference to same reactive object as props.movies (moviesLocal === props.movies returns true). This object is still reactive.
Let's use our moviesLocal variable in the template to render some kind of list...
If for example parent mutates the array referenced by props.movies (adding new item with push(), assigning different value to props.movies[0] etc.) child will be notified about the change and its template will re-render.
So why the error? Where is the "loose reactivity" ? Problem is what happens when parent replaces the value of props.movies with different\new array. Our moviesLocal variable will still hold the reference to previous reactive array. Our child component will still be reactive to changes (mutations) of that original array but lost the ability to react to changes of props.movies property.
This is demonstrated in this demo
The funny thing is that in the core, this behavior has nothing to do with Vue reactivity. It is just plain JS. Check this:
const obj = {
movies: ['Matrix']
}
const local = obj.movies
obj.movies = ['Avatar']
What is the value of local at this point ? Of course it is ['Matrix']! const local = obj.movies is just value assignment. It does not somehow magically "link" the local variable with the value of obj.movies object property.
D) let moviesLocal = ref(props.movies);
moviesLocal.value[0].Title="CHANGED in child component";
I made a local copy in the child component (moviesLocal). This variable I made reactive (ref).
Again, variables are not reactive. Objects they point to (reference) can be reactive. Array referenced by props.movies was already reactive proxy. You just created a ref which holds that same object
Changing the value of the reactive moviesLocal also causes the 'movies' reactive object in the parent object to change, why is that ?
Because both point to (reference) same array (wrapped by Vue proxy) in the memory. Assign a new array to one of them and the this "link" will break...

Vuejs. Mutating prop in child component does not trigger warning. Wondering why

I noticed something strange with prop mutation (Vue 2.6).
Mutating props directly in a child component should be avoided and will trigger the following famous warning
"Avoid mutating a prop directly since the value will be
overwritten...."
So if in my parent component I have a data like
exemple: "Hello World"
that I passed down as a prop to a child component. If in that child component I do something like
this.exemple = "Hello World!"
I get a warning. Fair enough. Now I noticed that if the data in the parent is an object like
exemple : {message : "Hello World"}
and that in the child I do something like that
this.exemple.message = "Hello World!"
This does not trigger any warning and moreover the data in the parent component get updated
I'm wondering why. Why the prop mutation propagate to the parent in one case but not in the other ? Does it have maybe something to do with how javascript store these variable ? Is it good practice to use that trick ?
Why does the prop mutation propagate to the parent in one case but not in the other ? Does it have maybe something to do with how javascript stores these variable ?
Yes. JavaScript reference variables such as objects and arrays don't change their references when you mutate their properties/values. This means the parent can access the changed properties too, because it also has access to the reference. And Vue only gives a prop mutation warning if the reference changes.
Warning:
this.prop = {}
No warning:
this.prop.id = 5
Is it good practice to use that trick ?
No. It's back to violating One way data flow in which data is passed down through props (to child) and up through events (to parent).
Ok, but why doesn't Vue warn about those changes?
Consider an object with 1000 properties or an array with 1000 items. Vue would have to deep check all 1000 for changes, and there is no quick way to do it because each item has to be individually compared to the old value. This would have performance implications and probably make many apps unusable.
Here is a comment on the subject from Vue team member Bolerodan:
Generally you shouldnt do that. This can / could cause unexpected bugs in your code. Generally you should make a copy of your object, modify that, then emit upwards to the parent (as you have mentioned in your last paragraph I believe). This is the method I take and is the general consensus of top down, emit up approach.
A very simple answer is that You are mutating a reference value and Vue doesn't track the whole reference instead it only checks for the address where the reference is. So if you re-assign that value then it will warn you.
A good example to understand the behaviour is understanding through variable declaration const
const makes your variable immutable but again same applies to it if the value is primitive in nature you can't change it, but if it's a reference you can't change it but you can definitely update a value located at the reference
const a = 'Foo' // Primitive value
a = 'bar' // voilation
const b = { // Reference value
name: 'Foo'
}
b.name = 'Bar' // It's good
console.log(b)
b = {} // re-assign is voilation
Because the mutation prevention only catches direct assignments to the prop - it does not catch manipulations of its properties. The idea is that if you assign new values to the prop - you are assigning to the prop and not to the variable that is sitting in the parent Vue component which feeds the prop.
As soon as something in the parent component updates the feeding variable - the prop will be updated with this new value and the value that you have assigned to the prop in your child component will be overwritten.
If you feed the prop with an object and then mutate a property inside the prop in your child component - it will affect the same object inside the parent component. But if you try to assign a new object to the prop in your child component - you should get the same warning about mutating props.

Why react `useRef` hook stores the objects in `current` property? Why cant it store directly in the ref object?

Why does the object returned by the useRef hook stores whatever value it's supposed to hold in current property? Why can't we assign something directly into the ref object as shown below:
const sampleRef = useRef([]);
/** why can't we do this... */
sampleRef.push('1');
/** ...instead of this? Why an extra `current` object? */
sampleRef.current.pus('1');
What is the purpose of useRef returning the argument wrapped inside another object with current property?
The answer to this question is not specific to React and its hooks system.
Mutating an object is just a solution to how you share values between different closures / scopes.
When you call useRef() for your component a "ref object" is created in React internals, tied to that particular instance of your component.
Each time useRef() is called across multiple renders, the same "ref object" is returned. Setting current on it is how your store a value to re-access it on next render.
By doing something like
let value = useRef();
value = 1234;
you're throwing away the ref object, replacing it with a new value in your local scope. There's no way React can track that action and update the ref object that is stored in its internals. (In fact React does not track any action to "ref objects" anyway, it just gives them to you. You mutate them, you access them).
But with the current API, you do
const ref = useRef(); // ref is an object that is stored somewhere in React internals
ref.current = 1234; // you update the property of that object
The next time your component renders, React gives you the same ref object, so that you can use the value that you set previously.
As I understood, they made it because they needed to create an object in order to seal the DOM element object(in development mode) and memoize it. As you know if we are going to memoize something, we need to convert it to an object or array.
Reference:
function mountRef<T>(initialValue: T): {current: T} {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
hook.memoizedState = ref;
return ref;
}
https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js#L916
The react hooks system works with immutable values. Whenever the component is rendered, the hooks are called (for example the useState hook), and they produce a value or two (state and setter function). If this values are changed from the previous values, other hooks might be called (useEffect when the setter function is initialised).
However, sometimes we don't want to react to this changes. We don't really care what is the value as long as it's there, and we don't care if something changes it. For this cases we've got the ref:
The “ref” object is a generic container whose current property is
mutable and can hold any value, similar to an instance property on a
class.
Whenever you need to store a value, that will be used, but won't cause a re-render, nor cause useMemo, useCallback, useEffect, etc... to recompute, you can set that value via a ref. Since the ref itself will be used as part of hooks dependencies (useMemo(() => {}, [ref]), you can't update it. To enable immutability, a property inside that ref object, can be changed ref.current, without causing the dependant to recompute, since it's the same ref.
I'm trying to answer the root cause of your question, which I interpret to be something like: "It seems that we should not need an intermediate object with a .current property." You're correct. We don't. Whatever the actual reason regarding useRef, I have noticed it is possible to instead do something like the following, which has the kind of syntax you were asking for in your question (the elimination of .current):
//note foo is in array brackets by itself
const [foo /*no setFoo here*/]= useState({bar:"baz"});
...
foo.bar="hello"
or
//note foo in brackets by itself
const [foo /*no setFoo here*/]= useState([]);
...
foo.push(1);
This lets us directly mutate foo's properties without using .current. As long as we never call setFoo, mutating foo's properties will not cause a rerender on its own. The value of foo itself is never changed, since it always points to the same object or array.
However, it is possible, just as with any other variables including useRef ones, to cause a useEffect hook to rerun after a rerender if a changed property like foo.bar appears in the 2nd argument array of the useEffect.
I haven't tried let [foo]= useState("whatever") yet. In that case, we'd be altering the actual value of foo, and depending on React to give that altered value back to us on subsequent rerenders, even though we never notified React of the change. Seems sketchy.

is it possible to store primitive properties as reference in a class?

I'm running a simulation, which has a "timePerTick" integer variable. The user can set this variable to speed up or slow down the simulation.
The simulation itself is handled by a few classes, all of whom consume the timePerTick variable.
I'm having some trouble eloquently passing timePerTick to the classes, because if I pass an integer, it's passed as a value, not reference. Right now, I'm passing a function that returns a reference, which works, but it's not very pretty.
Is there some pattern I'm not aware of that can handle this?
Is there any reason it needs to be passed by reference? If it's a user-supplied value, then it sounds like your functions aren't going to modify it, so it's not going to change.
If you really want the equivalence of passing it by reference, you could:
Pass the value to the classes each tick, like
myClass.onTick(timePerTick)
Pass an object instead of a number.
Objects in js are passed by reference. myClass.onTick(globalState), where globalState is an object or the class containing your variable.
Pass an object, that has a timePerTick value. Objects are passed by reference.

(React) Correct way to typecheck array of objects that may be empty?

I have a prop that is usually an array of objects, but sometimes (including on the component's first render) the array is empty. I know that using PropTypes.array is frowned upon and I should use PropTypes.arrayOf() instead, but if I use PropTypes.arrayOf(PropTypes.object), there is a failed prop type warning due to the empty state of the array. What is the correct way to type check this prop?
You shouldn't be getting an error unless you set isRequired on it. That is,
myArray: PropTypes.arrayOf(PropTypes.object).isRequired
This will require an array but not necessarily an object. It's how you would handle exactly the case you mention where initially you may pass an empty array.

Categories