At the top of my component I define a variable with useRef like below.
const data = {
item1 : useRef (null),
}
console.log (data['item1'].current?.srcObject)
Now as the logic proceeds through the component the value for item1 is set up. The ref is attached to a video element. And at certain points the component re-renders.
The console.log prints the value of the srcObject upon the first re-render and the second. But on the third that value is lost. The srcObject is not explicitly changed in between the re-renders.
I don't fully understand refs but I'd like to know how these values are retained or destroyed. Of course I get that they maybe case specific but I'd still like to get the general idea.
You probably want to instead do const ref = useRef({item1: ...}) and reference the current value as ref.current.item. Your object is getting re-created every render.
If you put the object inside the ref, ref.current will be the object, and that object won't update or change with react renders. That's what refs are for, values that you can update independent of rendering, that also stick around for the lifetime of the component.
Related
I'm trying to understand usage of the Ref in React. I saw an example in the Ant Design documentation. https://3x.ant.design/components/tag/#components-tag-demo-control
There is a one line code that I couldn't get how it works.
saveInputRef = input => (this.input = input);
And usage as follows:
<Input ref={this.saveInputRef} ...
But in the React documentation, it is said that you create a ref using React.createRef() method.
https://reactjs.org/docs/refs-and-the-dom.html#adding-a-ref-to-a-dom-element
Is it an alternative way of using it? Why there is no React.createRef() method?
As far as I know there are 2 different kind of Refs, but they are commonly used together.
A. There is "Ref" created by "createRef" (or "useRef" when using hooks);
This stores a value in the "current" property of the Ref. This value won't cause rerenders and is kept after rerenders.
B. And there is ref as a property of build-in components. This property is used to access the domnode.
Usually ref (B) is stored in a Ref (A)
You can, however, store whatever you'd like in Ref (A).
And you don't need necessarily need to store a node gotten from ref (B) in a Ref (A), you can access it directly as well, that what this piece of code does:
<div ref={node => doSomething(node)}/>
Or simply
<div ref={doSomething}/>
This is called a "callback Ref":
Callback Refs
React also supports another way to set refs called “callback refs”, which gives more fine-grain control over when refs are set and unset.
Instead of passing a ref attribute created by createRef(), you pass a function. The function receives the React component instance or HTML DOM element as its argument, which can be stored and accessed elsewhere.
https://reactjs.org/docs/refs-and-the-dom.html#callback-refs
EDIT:
more about Ref (A) when using hooks.
Essentially, useRef is like a “box” that can hold a mutable value in its .current property.
You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with , React will set its .current property to the corresponding DOM node whenever that node changes.
However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
https://reactjs.org/docs/hooks-reference.html#useref
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 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.
I have seen a lot of questions regarding updating a parent's state from a child component, however my question is just the opposite. I have a component, a modal editing window, used for modifying objects stored in a parent's state.
I only want to have these changes copied to the parent if the user saves these changes, which is handled by a function I pass to the child component.
Currently, anytime I make a change to the prop.obj in the child, the change is picked up by the parent on re-render.
here is an example of one of my change handlers
handleChange(e) {
this.state.childobj.myvaluetochange = e.target.value
}
In my parent, as stated, I pass obj to the child from the parent's state like so
<MyComponent obj={this.state.obj} />
to summarize:
I send a state object to a child
The child uses this.props.obj to use this obj in a form and uses handlers to update
When changes are picked up in the child, they reflect on the parent's state object on update - I don't want this unless they choose to save. (this is handled by a function passed in from the parent)
to clarify, I want the state obj passed to the child to act as a completely separate object when it lives in the child. Is this possible?
Thanks
This is happening because you are mutating the given prop.
To avoid this you should create new instance of "this.props.obj". Probably you want to do it in "componentWillMount" and "componentWillReceiveProps" lifecycle methods. To create new instance of object you can use Object.assign or spread operators.
You are creating a link to the parent object and then mutating the state which will reflect in the parent. An easy way around this is to create a new version of it by using the spread operator to create a new object:
this.state = {childObj: {...this.props.obj}}
I have an autocomplete component which has following props:
value (display text),
onChange (fired on every keystroke)
onSelect(fired when user picks something from the list of
suggestions, passes selected object + display text of selected object - the same as value)
state:
- suggestionsList (I've decided it's a state as it's something internal to the component).
On one of the views parent of this component doesn't care about the value after each keystroke. It just cares about the selected item passed by onSelect handler. So in this scenario value & onChange props are not needed. value could become an internal state of the autocomplete component.
But on one of the other views parent would like to also know about each keystrokes. This means the parent would need to hold the value in his state and also pass it as props. onChange props would be also needed to notify parent to change its state.
How to construct the autocomplete component so it handles both scenarios? Which properties should be props and which state?
Any suggestions appreciated.
In the second case, if the parent needs to know about the current value, it doesn't need to hold the variable, so I guess that if you pass the current value (from an internal state variable) as a parameter to the onChange prop function, the parent will notice the change and update accordingly.
Another way to do it is to use the value property and a internal state variable. What I mean is: if the parent wishes to control the value it will have an state entry for it and will be responsible for updating it, in other case the value property must not be provided because it would end as a constant. Your component will use this property as the current value every time its provided, and the parent should use onChange to update the va;ue that it holds.
You can check the source code of the auto complete component in material-ui.