How to set ref to an unrendered element - html

I have a login page where I'm placing a button "Watch the video". On click of the button, I'm hiding the button and displaying a video.
Now my requirement is to start playing the video as soon as it displays without having to click the play button.
I'm trying to use refs and somehow I'm unable to set the ref to my video element.
Is there any way to set the refs to unrendered elements in componentDidUpdate?
Please help me!
Following is my code
export default class NewLoginPage extends Component {
constructor(props) {
this.vidRef = React.createRef();
this.state = {
showVideo: false
};
}
handleVideoClick = () => {
this.setState({ showVideo: true })
this.vidRef.current.play();
}
handleCloseClick = () =>{
this.setState({ showVideo: false })
}
render() {
let language_labels = labels["english_labels"]
console.log(labels)
console.log(language_labels)
return (
<div className="container-fluid no-padding">
<div className="new-login-div">
<AppBar className="app-bar">
<Toolbar className="tool-bar">
<img src={pmiLogo} />
{/* <NavLink to="/" id="invoice_upload">LOG OUT</NavLink> */}
{/* <Button className="logout-btn">REGISTER</Button> */}
</Toolbar>
</AppBar>
<Grid container>
<Grid item xs={4}>
<h1 className="welcome-heading">Self Service Portal</h1>
<TextField
className="id-field"
placeholder="Email"
inputProps={{
style: {
color: "#fff",
paddingLeft: "5px"
}
}}
/>
<br />
<Button className="login-btn" endIcon={<ArrowForwardIcon />}>Log In</Button>
</Grid>
<Grid item xs={8}>
<div className="video-div">
{this.state.showVideo && <div>
<IconButton className="close-btn" onClick={(event) => this.handleCloseClick()}>
<CloseIcon />
</IconButton>
<video ref={ref => { this.vidRef = ref }} width="500" height="285" controls className="video-container">
<source src={exampleVid} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>}
{!this.state.showVideo && <div className="intro-div">
<h5 className="intro-text">
Supporting the vitality and survival of US small businesses—who employ nearly half of the American workforce—is especially critical now. Let’s not forget just how essential they are.
</h5>
<Button
disableRipple={true}
className="video-button"
startIcon={<PlayArrowIcon className="play-icon" />}
onClick={(event) => this.handleVideoClick()}
>
Watch video
</Button>
<br />
<Button
disableRipple={true}
className="reg-button"
startIcon={<ChevronRightIcon className="play-icon" />}>
Register
</Button>
</div>}
</div>
</Grid>
</Grid>
</div>
</div>
);
}
}

React ref property is used to access DOM element reference.
If element is not rendered then DOM element is not created (deleted).
Thus you can't get a ref of something that doesn't exist.

You don't need to use a ref to do that, you can add the autoplay html property to your <video>
for details on autoplay
You can't make use of a ref to an element that is not yet rendered as Anton explained in his answer, the ref.current will be null.
However, it is suggested that you create a separate component for the video and it's logic from that of the login, not only it will solve the issue (the this.state.showVideo condition will stay in the parent component) but it is also how React components should be, for more details refer to this.

Related

Why react function is called but the element has a negative Zindex

I have question why when I click on Grid not on the box which is {-5} the function is called but Grid is higher than the box so function should be ingored. How can I solve that.
<Box zIndex={-5} onClick={hideFormModal}>
<Grid
width="60vw"
placeItems="center"
position="fixed"
zIndex={1000}
top="50%"
left="50%"
transform="translate(-50%, -50%)"
textAlign="right"
>
<motion.div
variants={animation}
initial="initialPosition"
animate="onShow"
exit="onShowExit"
>
<form onSubmit={onCommentAdd}>
<Grid>
<Input
placeholder="Title"
onChange={(e) =>
onCommentChangeHandler(e, setCommentTitle)
}
bgColor="white"
borderEndRadius="0"
/>
<Textarea
}
/>
<Button type="submit" borderRadius="0">
Add
</Button>
</Grid>
</form>
</motion.div>
</Grid>
</Box>
)}
z-index property will work as you expect if both elements are siblings.
Here Box receives onClick events due to event propagation.
Events are propagated from the bottom to the top of the tree.
You should add onClick={(e) => e.stopPropagation()} on your grid item to stop event propagation.
For further information about this check:
https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Examples#example_5_event_propagation

Trying to get an overlay to toggle on a repeating element. Can't use getElementByID

As per the title I want an overlay to trigger when an image is clicked on, but I then want it to disappear if anywhere other than 3 buttons on the overlay are clicked.
Unfortunately using getElementbyID won't work as the items repeat on a masonry layout.
<Masonry
breakpointCols={breakpointColumnsObj}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column">
{this.state.data.map((data) => (
<div>
<div className="tilebutton" key="" style={{width:'100%',position:'relative'}} href={data.URL} >
<div className="tileoverlay" id="overlay" onClick={overlayoff} onclickout key={data.URL} style={{display:'none',width:'100%',zindex:'2',position:'absolute'}}>
<a className="button1" href={data.URL} onClick>{data.Name}</a>
<a className="button2" href={data.CompURL}>{data.Company}</a>
<a className="button3" href={'instagram.com/'+data.insta}>{data.Company}<img src="\img\Icons\instagram.svg" className='instalogo'/></a>
</div>
<img src={data.img} onClick={overlayon} style={{width:'100%'}}/>
</div>
</div>
))}
</Masonry>
)
function overlayon() {
document.getElementById("overlay").style.display = "block";
}
function overlayoff() {
document.getElementById("overlay").style.display = "none";
}
Unfortunately using the id "overlay" means if I click any version of the masonry it will trigger the overlay on the first image. Is there some way to:
a) identify the element clicked so it will be the one with the toggling overlay
b) have an "onclickout" I could apply to the overlay's buttons
this is about 5 days into my first ever web build so frankly I haven't got a clue what I am doing - any help is appreciated.
Thanks in advance.
The idioms used in React discourages you to manipulate the DOM directly, unless you are doing something special, such as animation. And thus I don't recommend "identifying the element clicked".
With that said, you can manipulate the data, and trigger a redraw accordingly, by invoking some setSate function (in the example below, I've defined a setShouldShowOverlay, that, when invoked, will result in a redraw).
What I recommend is for you to pull out the code inside this.state.data.map() into its own component, like so:
import React, { useState } from 'react';
function Data({ data }) {
const [ shouldShowOverlay, setShouldShowOverlay ] = useState(false);
return (
<div>
<div className="tilebutton" key="" style={{width:'100%',position:'relative'}} href={data.URL} >
<div className="tileoverlay" id="overlay" onClick={() => { setShouldShowOverlay(false); }} onclickout key={data.URL} style={{display:'none',width:'100%',zindex:'2',position:'absolute'}}>
<a className="button1" href={data.URL} onClick>{data.Name}</a>
<a className="button2" href={data.CompURL}>{data.Company}</a>
<a className="button3" href={'instagram.com/'+data.insta}>{data.Company}<img src="\img\Icons\instagram.svg" className='instalogo'/></a>
</div>
<img src={data.img} onClick={() => {
setShouldShowOverlay(true);
}} style={{width:'100%'}}/>
</div>
</div>
);
}
Then finally, update your Masonry code like so:
<Masonry
breakpointCols={breakpointColumnsObj}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column">
{this.state.data.map((data) => (
<Data data={data} />
))}
</Masonry>

React how to: replace only one component on click (dynamic UI)

Using react.js I want to make a dynamic UI, I have something like this:
<div>
<appbar component />
<drawer (sidebar) comopnent /> <!-- <--has buttons -->
<content component> <!-- <-- component to replace or re-render -->
<footer component>
</div>
Then I try to use an state to save and change the "" like this:
const [renderView, setRenderView] = useState("<Home />");
/* onclick call this function and pass referenced component ej: <reports /> */
function changeContent(view){
setRenderView(view);
return(
<Suspense fallback={renderLoader()}>
{view}
</Suspense>
);
}
Now, how to replace the " content component " with the new one?.
Thanks for your comments.
i made this bassed on suggestions, its working, but im not using class just functions
function Dashboard(props) {
/* lazy loading */
const renderLoader = () => <p>Loading...!?¡?</p>;
/* lazy components */
const Initial = () => (
<Suspense fallback={renderLoader()}>
<InitialComponent />
</Suspense>
)
const Reports = () => (
<Suspense fallback={renderLoader()}>
<ReportsComponent />
</Suspense>
)
const Cards = () => (
<Suspense fallback={renderLoader()}>
<CardsComponent />
</Suspense>
)
/* set view state */
const [renderView, setRenderView] = useState(Initial());
function changeContent(view){
setRenderView(view);
}
const test = (cosa) => {
console.log(cosa);
}
/* style things*/
const classes = useStyles();
const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight);
return (
<div className={classes.root}>
<CssBaseline />
<UIappbar />
<UIsidebar />
<main className="contentWrapper">
<Grid container spacing={3}>
<Grid item xs={12} md={8} lg={9}>
<div>
<Button onClick={() => changeContent(Reports())}>Reports view</Button>
</div>
<div>
<Button onClick={() => changeContent(Cards())}>Cards view</Button>
</div>
</Grid>
<Grid item xs={12} md={8} lg={9}>
<Paper className={fixedHeightPaper}>
{renderView}
</Paper>
</Grid>
</Grid>
</main>
</div>
);
}
Now i need to call "changeContent()" that has the method to change {renderView} need to call it from a child
dont know how to pass props without class format (im using functions only)
should i change to class with constructor format?

How to avoid Bootstrap 4 NavBar from expanding when implementing an autocomplete field in React?

I use React 16.5.2 and Bootstrap 4.1.3 on my project and I want to have a typeahead on a NavBar. I have implemented the autocomplete field on the NavBar but it did not turn out the way I want it.
It seems that when the list of suggestion came out it will expand the NavBar too.
Another problem is the list of suggestion is on the right side of the input text instead of on the left side. For your information, I'm a React newbie and I'm using react-places-autocomplete to get the list of suggestion.
Most of the typeahead that I found is used outside of Navbar.
Here is the image of the NavBar with the autocomplete field.
Here are some packages that I use:-
React Places Autocomplete - https://www.npmjs.com/package/react-places-autocomplete.
bootstrap 4.1.3 - https://www.npmjs.com/package/bootstrap
Create React App - https://github.com/facebook/create-react-app
Below is a snippet of the code from my SearchForm component:-
render() {
return (
<form className="mx-2 my-auto d-inline w-100" onSubmit={this.handleSubmit}>
{this.state.gmapsLoaded && (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div className="input-group">
<input
{...getInputProps({
placeholder: 'Search Places ...',
className: 'form-control border border--0',
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? 'suggestion-item--active'
: 'suggestion-item';
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
<span className="input-group-append">
<button className="btn btn-outline-secondary border border-left-0" type="button" onClick={this.handleOnClick}>
<FontAwesomeIcon icon={faSearch} />
</button>
</span>
</div>
)}
</PlacesAutocomplete>
)}
</form>
)
}

'clientHeight' not counting all element - React

The DOM-tree rendered was
<Grid fluid ={true}>
<Row>
<NavigationBar
buttonClass='nav-button'
className='nav-bar'
/>
</Row>
<section id={sectionList[0]}>
<Row className='logo-row'>
<Col className='logo-wrap' xs={3} sm={3} md={2}>
{Logo}
</Col>
<Col xs={9} sm={9} md={10}>
{ProgrammerName}
</Col>
</Row>
{backgroundImg}
</section>
</Grid>
And I was trying to check the clientHeight of the <section> by using following method:
const height = document.getElementById(sectionList[0]).clientHeight;
However, it seems that this call would only give out the height of the <Row> contained in the <section>, ignoring the {backgroundImg} expression, which itself called to render another <Row> component.
<Row>
<FullRowImage src={this.props.src} alt={this.props.alt} />
<h2>
<span>
Some Text
</span>
</h2>
</Row>
What might be the cause of this issue, that clientHeight counts only part of the <section> while leaving out the other?
Thanks.
So I finally figured this out.
As <FullRowImage /> renders an <img> itself, the clientHeight is called before the <img> is loaded and this would leads to a zero height of <img> as well as <FullRowImage>.
In this case, method componentDidMount() would not be enough since a mounted component does not guarantee a loaded image.
On the other hand, onLoad event will come in handy:
class FullRowImage extends React.Component {
constructor(props){
super(props);
this.state = {
loaded: false,
};
this.handleLoaded = this.handleLoaded.bind(this);
}
handleLoaded(){
console.log(document.getElementById('test').offsetHeight);
this.setState(
{loaded: true,}
);
}
render(){
return(
<img id='test'
onLoad={this.handleLoaded}
src={this.props.src}
alt= {this.props.alt} />
);
}
}
And this will print the height of <img> after it is loaded.
Thanks to this article Detect when images have finished loading with React