React Hooks UseRef issue - reactjs

I'm trying to use React hooks. I have a problem with this code:
class VideoItem extends Component {
handlePlayingStatus = () => {
this.seekToPoint();
...
}
seekToPoint = () => {
this.player.seekTo(30); // this worked - seek to f.ex. 30s
}
render() {
const { playingStatus, videoId } = this.state;
return (
<Fragment>
<ReactPlayer
ref={player => { this.player = player; }}
url="https://player.vimeo.com/video/318298217"
/>
<button onClick={this.handlePlayingStatus}>Seek to</button>
</Fragment>
);
}
}
So I want to get ref from the player and use a seek function on that. This works just fine but I have a problem to change it to hooks code.
const VideoItem = () => {
const player = useRef();
const handlePlayingStatus = () => {
seekToPoint();
...
}
const seekToPoint = () => {
player.seekTo(30); // this does not work
}
return (
<Fragment>
<ReactPlayer
ref={player}
url="https://player.vimeo.com/video/318298217"
/>
<button onClick={handlePlayingStatus}>Seek to</button>
</Fragment>
);
}
How can I remodel it?

From the documentation:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
Thus your code should be:
player.current.seekTo(30);
(optionally check whether player.current is set)
useCallback might also be interesting to you.

useRef is a hook function that gets assigned to a variable, inputRef, and then attached to an attribute called ref inside the HTML element you want to reference.
Pretty easy right?
React will then give you an object with a property called current.
The value of current is an object that represents the DOM node you’ve selected to reference.
So after using useRef player contains current object, and inside current object, your all methods from vimeo player would be available(in your case seekTo(number))
so you should be using player.current.seekTo(30) instead.
refer this to know more about useRef.

Related

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

Conditionally assign ref in react

I'm working on something in react and have encountered a challenge I'm not being able to solve myself. I've searched here and others places and I found topics with similar titles but didn't have anything to do with the problem I'm having, so here we go:
So I have an array which will be mapped into React, components, normally like so:
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr
return (<>
{arr.map((item, id) => {<ChildComponent props={item} key={id}>})}
</>)
}
but the thing is, there's a state in the parent element which stores the id of one of the ChildComponents that is currently selected (I'm doing this by setting up a context and setting this state inside the ChildComponent), and then the problem is that I have to reference a node inside of the ChildComponent which is currently selected. I can forward a ref no problem, but I also want to assign the ref only on the currently selected ChildComponent, I would like to do this:
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr and there's a state which holds the id of a selected ChildComponent called selectedObjectId
const selectedRef = createRef();
return (<>
<someContextProvider>
{arr.map((item, id) => {
<ChildComponent
props={item}
key={id}
ref={selectedObjectId == id ? selectedRef : null}
>
})}
<someContextProvider />
</>)
}
But I have tried and we can't do that. So how can dynamically assign the ref to only one particular element of an array if a certain condition is true?
You can use the props spread operator {...props} to pass a conditional ref by building the props object first. E.g.
export default ParentComponent = () => {
const selectedRef = useRef(null);
return (
<SomeContextProvider>
{arr.map((item, id) => {
const itemProps = selectedObjectId == id ? { ref: selectedRef } : {};
return (
<ChildComponent
props={item}
key={id}
{...itemProps}
/>
);
})}
<SomeContextProvider />
)
}
You cannot dynamically assign ref, but you can store all of them, and access by id
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr and theres a state wich holds the id of a selected ChildComponent called selectedObjectId
let refs = {}
// example of accessing current selected ref
const handleClick = () => {
if (refs[selectedObjectId])
refs[selectedObjectId].current.click() // call some method
}
return (<>
<someContextProvider>
{arr.map((item, id) => {
<ChildComponent
props={item}
key={id}
ref={refs[id]}
>
})}
<someContextProvider />
</>)
}
Solution
Like Drew commented in Medets answer, the only solution is to create an array of refs and access the desired one by simply matching the index of the ChildElement with the index of the ref array, as we can see here. There's no way we found to actually move a ref between objects, but performance cost for doing this should not be relevant.

react: Convert from Class to functional component with state

How would one convert the class component to a functional one?
import React, { Component } from 'react'
class Test extends Component {
state = {
value: "test text",
isInEditMode: false
}
changeEditMode = () => {
this.setState({
isInEditMode: this.state.isInEditMode
});
};
updateComponentValue = () => {
this.setState({
isInEditMode: false,
value: this.refs.theThexInput.value
})
}
renderEditView = () => {
return (
<div>
<input type="text" defaultValue={this.state.value} ref="theThexInput" />
<button onClick={this.changeEditMode}>X</button>
<button onClick={this.updateComponentValue}>OK</button>
</div>
);
};
renderDefaultView = () => {
return (
<div onDoubleClick={this.changeEditMode}
{this.state.value}>
</div>
};
render() {
return this.state.isInEditMode ?
this.renderEditView() :
this.renderDefaultView()
}}
export default Test;
I assume one needs to use hooks and destructioning, but not sure how to implement it.
Is there a good guidline or best practice to follow?
I gave a brief explanation of what is going on:
const Test = () => {
// Use state to store value of text input.
const [value, setValue] = React.useState("test text" /* initial value */);
// Use state to store whether component is in edit mode or not.
const [editMode, setEditMode] = React.useState(false /* initial value */);
// Create function to handle toggling edit mode.
// useCallback will only generate a new function when setEditMode changes
const toggleEditMode = React.useCallback(() => {
// toggle value using setEditMode (provided by useState)
setEditMode(currentValue => !currentValue);
}, [
setEditMode
] /* <- dependency array - determines when function recreated */);
// Create function to handle change of textbox value.
// useCallback will only generate a new function when setValue changes
const updateValue = React.useCallback(
e => {
// set new value using setValue (provided by useState)
setValue(e.target.value);
},
[setValue] /* <- dependency array - determines when function recreated */
);
// NOTE: All hooks must run all the time a hook cannot come after an early return condition.
// i.e. In this component all hooks must be before the editMode if condition.
// This is because hooks rely on the order of execution to work and if you are removing
// and adding hooks in subsequent renders (which react can't track fully) then you will
// get warnings / errors.
// Do edit mode render
if (editMode) {
return (
// I changed the component to controlled can be left as uncontrolled if prefered.
<input
type="text"
autoFocus
value={value}
onChange={updateValue}
onBlur={toggleEditMode}
/>
);
}
// Do non-edit mode render.
return <div onDoubleClick={toggleEditMode}>{value}</div>;
};
and here is a runnable example
I have released this npm package command line to convert class components to functional components.
It's open source. Enjoy.
https://www.npmjs.com/package/class-to-function

Correct way to pass useRef hook to ref property

I'm not sure how to formulate the question less vaguely, but it's about pass-by-value and pass-by-reference cases in react. And Hooks.
I am using gsap to animate a div slide-in and out is the context for this, but I'm going to guess that what the ref is used for shouldn't matter.
So, this works fine, even though this is a more class-component-typical way of passing a ref as i understand it:
const RootNavigation = () => {
var navbar = useRef();
const myTween = new TimelineLite({ paused: true });
const animate = () => {
myTween.to(navbar, 0.07, { x: "100" }).play();
};
return(
<div className="nav-main" ref={div => (navbar = div)}> // <<<<<<<<<< pass as a callback
...
</div>
)}
And this elicits a "TypeError: Cannot add property _gsap, object is not extensible" error, even though this is how the React Hooks guide would have me do it:
const RootNavigation = () => {
var navbar = useRef();
const myTween = new TimelineLite({ paused: true });
const animate = () => {
myTween.to(navbar, 0.07, { x: "100" }).play();
};
return(
<div className="nav-main" ref={navbar}> //<<<<<<<<<<<<< not passing a callback
...
</div>
)}
Could somebody explain to me what's going on here or even toss a boy a link to where it's already been explained? I'm sure some sort of Dan character has written about it somewhere, i'm just not sure what to google. Thank you!
In the first example you aren't using a ref, you are reassigning navbar through the ref callback so navbar is the DOM element.
It's the same as
let navbar = null;
return <div ref={node => (navbar = node)} />
In the second example you are using the ref object which is an object with a current property that holds the DOM element
const navbar = useRef(null)
return <div ref={navbar} />
navbar is now
{ current: the DOM element }
So you are passing the object into myTween.to() instead of the DOM element inside navbar.current
Now in the second example gsap is trying to extend the ref object itself and not the DOM element.
Why do we get the TypeError: Cannot add property _gsap, object is not extensible`?
If you look at the source code of useRef you will see on line 891
if (__DEV__) {
Object.seal(ref);
}
that React is sealing the ref object and JavaScript will throw an error when we try to extend it using Object.defineProperty() which is probably what gsap is doing.
The solution for using a ref will be to pass ref.current into tween.to()
const RootNavigation = () => {
const navbar = useRef()
const myTween = new TimelineLite({ paused: true });
const animate = () => {
myTween.to(navbar.current, 0.07, { x: "100" }).play()
}
return (
<div className="nav-main" ref={navbar}>
...
</div>
)
}

React refs not being evaluated

I have a simple component in React, witch renders another component as follows.
_getResultsListComponent: function () {
var data = {
...blabla
};
return
<Popout
id='popout'
ref="searchResults"
closeResults={this._closeResults}
pointerAlign="center"
>
<this.props.resultsList data={data} />
</Popout>
},
render: function () {
return (
<div className='blabla'>
{this._getResultsListComponent()}
</div>
);
}
However, if i print 'this.refs' after mount ill get the exact string ive put into refs, for example:
ref="searchResults"
printing this.refs after mount gets me
searchResults
If i change the refs to
ref="{(compo) => {this.component = compo}}"
ill get when printing:
{(compo) => {this.component = compo}}
Like if the refs are not being evaluated.
Am i missing something ?
React no longer sets a "refs" object on the class but instead uses a callback so that you can assign a class property a unique name for the element. In this case this.component will contain the value of the compo prop passed to the callback.
ref accepts a callback function and you need to remove the quotes for it to be evaluated
ref={(compo) => {this.component = compo}}
You can later access the ref like this.component

Resources