I have a video tag () in my webpage, and a "play/pause" button that when the user clicks on it, the video starts/stops playing . How can I do so in react if I'm not allowed to use js in order to call "getElementById" and then to use play()/pause() build-in methods.
Any idea?
Updated example for React Function Components:
import React, { useRef} from 'react'
function myComponent(props) {
const vidRef = useRef(null);
const handlePlayVideo = () => {
vidRef.current.play();
}
return (
<video ref={vidRef}>
<source src={[YOUR_SOURCE]} type="video/mp4" />
</video>
)
}
The most straightforward way would be to use refs which is a React feature that will let you invoke methods on the component instances that you have returned from a render().
You can read up a little more on them in the docs: https://facebook.github.io/react/docs/more-about-refs.html
In this case just add a ref string to your video tag like so:
<video ref="vidRef" src="some.mp4" type="video/mp4"></video>
That way when you add click handlers to your buttons:
<button onClick={this.playVideo.bind(this)}>PLAY</button>
The playVideo method will have access to your video reference through refs:
playVideo() {
this.refs.vidRef.play();
}
Here is a working DEMO so you can check out a full example.
Accepted answer was using old react style, if you want to do with ES6
A simple component to auto play pause along with manual controls playing Polestar intro:
import React from "react";
class Video extends React.Component {
componentDidMount = () => {
this.playVideo();
};
componentWillUnmount = () => {
this.pauseVideo();
};
playVideo = () => {
// You can use the play method as normal on your video ref
this.refs.vidRef.play();
};
pauseVideo = () => {
// Pause as well
this.refs.vidRef.pause();
};
render = () => {
return (
<div>
<video
ref="vidRef"
src="https://assets.polestar.com/video/test/polestar-1_09.mp4"
type="video/mp4"
/>
<div>
<button onClick={this.playVideo}>
Play!
</button>
<button onClick={this.pauseVideo}>
Pause!
</button>
</div>
</div>
);
};
}
export default Video;
Video from https://www.polestar.com/cars/polestar-1
This answer adds to #mheavers, which I upvoted.
There are a few differences:
One can pass noControls as a prop to the Video component, and apply the click event only if the <video> doesn't have the default controls (which will be the case when noControls is passed).
The handler function is a toggler; enabling one to play or pause according to its current state.
One can create a play button overlay style through the class video__play-button, whilst the same handler hides it through the class is-playing.
It also shows how to use two or more ref and pass them as a parameter to a pure render function.
import React, { useRef } from 'react';
import PropTypes from 'prop-types';
const renderVideo = ({
noControls,
src,
vidButtonRef,
vidRef,
handleToggleVideo,
}) => (
<>
{noControls ? (
<div ref={vidButtonRef} className="video video__play-button">
<video
ref={vidRef}
src={src}
onClick={handleToggleVideo}
></video>
</div>
) : (
<video
src={src}
controls
controlsList="nodownload"
></video>
)}
</>
);
const Video = props => {
const vidRef = useRef(null);
const vidButtonRef = useRef(null);
const { noControls, src } = props;
const handlePlay = () => {
vidRef.current.play();
// hide overlay play button styles, set by 'video__play-button'
vidButtonRef.current.classList.add('is-playing');
};
const handlePause = () => {
vidRef.current.pause();
// show overlay play button styles, set by 'video__play-button'
vidButtonRef.current.classList.remove('is-playing');
};
const handleToggleVideo = () => (vidRef.current.paused ? handlePlay() : handlePause());
return (
<>
{renderVideo({
noControls,
src,
vidButtonRef,
vidRef,
handleToggleVideo,
})}
</>
);
};
Video.propTypes = {
noControls: PropTypes.bool,
videoUrl: PropTypes.string,
};
export default Video;
Use ref attribute to create a link to the video and using that reference we can able to use video controls on the video component
Try this code,
import React from "react";
class VideoDemo extends React.Component {
getVideo = elem => {
this.video = elem
}
playVideo = () => {
this.video.play()
};
pauseVideo = () => {
this.video.pause();
};
render = () => {
return (
<div>
<video
ref={this.getVideo}
src="http://techslides.com/demos/sample-videos/small.mp4"
type="video/mp4"
/>
<div>
<button onClick={this.playVideo}>
Play!
</button>
<button onClick={this.pauseVideo}>
Pause!
</button>
</div>
</div>
);
};
}
export default VideoDemo;
import React, { useRef, useState } from 'react';
export const Video = () => {
const videoRef = useRef();
const [stop, setStop] = useState(false);
const handleVideo = () => {
setStop(!stop);
if (stop === true) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
};
return (
<div onClick={handleVideo}>
<video ref={videoRef} poster={HeroImg} controls>
<source src={coverVideo} type="video/mp4" />
</video>
</div>
);
};
Related
I'm creating a simple React App and I've stumbled upon something I can't solve.
I've created a button component which I've exported like any other component.
At the moment, I've imported the Button component in my main part because I need two buttons
The problem is that the labels won't render so i have 2 plain buttons..
The label the button should show is Search
Any fixes?
The Button component
import React from 'react';
import './Button.css';
const Button = ({state = "active"}) => {
return (
<button className={`.btn--${state}`}></button>
);
};
export default Button;
My Main component
import React from 'react';
import './Input.css';
import { useState } from 'react';
import Button from '../Button/Button';
const Input = () => {
const [value, setValue] = useState("");
const SearchButton = (e) => {
e.preventDefault();
console.log("click");
};
const ResetButton = (e) => {
e.preventDefault();
setValue("");
};
return (
<main>
<form className='inputfield'>
<h2 className='input-text'>Zoek een Github user</h2>
<div className='input'>
<input className='search' type='text' placeholder='Typ hier een gebruikersnaam...' value={value} onChange={(e) => setValue(e.target.value)}></input>
<div className='button-field'>
<Button state="inactive" className='search-now' onClick={SearchButton}>Search</Button>
<Button className='reset' onClick={ResetButton}></Button>
</div>
</div>
</form>
</main>
);
};
export default Input;
You have two straight forward ways of this doing what you want.
The first solution would be to use children React Docs Here
Your button then would look like:
const Button = ({state = "active"}) => {
const {children} = props
return (
<button className={`.btn--${state}`}>{children}</button>
);
};
A second approach is to pass the Value through props to the component.
<Button
state="inactive"
className='search-now'
onClick={SearchButton}
textValue={"Search"} />
// Button
const Button = ({state = "active"}) => {
const {textValue} = props
return (
<button className={`.btn--${state}`}>{textValue}</button>
);
};
How would you add a component inside an useRef object (which is refering to a DOM element)?
const Red = () => {
return <div className="color">Red</div>;
};
const Black = () => {
return <div className="color">Black</div>;
};
const Green = () => {
return <div className="color">Green</div>;
};
const Button = (params) => {
const clickHandler = () => {
let boolA = Math.random() > 0.5;
if (boolA) {
params.containerRef.current.appendChild(<Red />);
} else {
let boolB = Math.random() > 0.5;
if (boolB) {
params.containerRef.current.appendChild(<Black />);
} else {
params.containerRef.current.appendChild(<Green />);
}
}
};
return <button onClick={clickHandler}>Click</button>;
};
export default function App() {
const containerRef = useRef(null);
return (
<div className="App">
<Button containerRef={containerRef} />
<div ref={containerRef} className="color-container">
Color components should be placed here !
</div>
</div>
);
}
params.containerRef.current.appendChild(); -> throws an error. I`ve put it to show what I would like to happen.
Also is what I`m doing an anti-pattern/stupid ? Is there another (smarter) way of achieving the above ?
codesandbox link
edit :
I forgot some important information to add.
Only Button knows and can decide what component will be added.
expecting you want to add multiple colors, something like this would work and don't need the ref:
import { useState } from "react";
import "./styles.css";
const Color = () => {
return <div className="color">Color</div>;
};
const Button = (params) => {
return <button onClick={params.onClick}>Click</button>;
};
export default function App() {
const [colors, setColors] = useState([]);
return (
<div className="App">
<Button onClick={() => setColors((c) => [...c, <Color />])} />
<div className="color-container">
{colors}
</div>
</div>
);
}
It's better to have a state that is changed when the button is clicked.
const [child, setChild] = useState(null);
const clickHandler = () => {
setChild(<Color />);
};
const Button = (params) => {
return <button onClick={params.onClick}>Click</button>;
};
<Button onClick={clickHandler} />
<div className="color-container">
Color components should be placed here !
{child}
</div>
Working sandbox
Edit: Refer to #TheWuif answer if you want multiple Colors to be added upon clicking the button repeatedly
There're several things from your code I think are anti-pattern:
Manipulate the real dom directly instead of via React, which is virtual dom
Render the Color component imperatively instead of declaratively
Here's the code that uses useState (state displayColor) to control whether <Color /> should be displayed
import { useState } from "react";
import "./styles.css";
const Color = () => {
return <div className="color">Color</div>;
};
const Button = (props) => {
return <button onClick={props.clickHandler}>Click</button>;
};
export default function App() {
const [displayColor, setDisplayColor] = useState(false);
const clickHandler = () => {
setDisplayColor(true);
};
return (
<div className="App">
<Button clickHandler={clickHandler} />
<div className="color-container">
Color components should be placed here !{displayColor && <Color />}
</div>
</div>
);
}
Codesandbox
I'm pretty new on this.
As an exercise I did an App that renders images of cats when clicking on a button (the images are from an API and that works fine).
My idea was to make the button refresh new images when pressed, and I know I have to be using hooks, but I'm not sure if I should use useState, setState or something else.
Here is the code
import React, { useState } from "react";
import { CatsGrid } from "./components/CatsGrid";
const RandomCatApp = () => {
return (
<div className="catContainer">
<h1>Random kittens</h1>
<button onClick={'Some code here'} className="catBtn">
Generate random kitten
</button>
<CatsGrid />
</div>
);
};
export default RandomCatApp;
The button must refresh component to show new images from the API.
CatsGrid component works fine, I just tested it. My problem is with the "onClick" and useState or something else in the code above.
Here is CatsGrid component just in case:
import React, { useState, useEffect } from "react";
export const CatsGrid = () => {
const [imagen, setImagen] = useState([]);
useEffect(() => {
getCats();
}, []);
const getCats = async () => {
const url = "someApiKey";
const resp = await fetch(url);
const data = await resp.json();
const catImg = data.map((img) => {
return {
id: img.id,
url: img.url,
};
});
setImagen(catImg);
};
return (
<div className="imgContainer">
{imagen.map(({ id, url }) => (
<img className="catImg" key={id} src={url} alt="" />
))}
</div>
);
};
Ok, assuming the fetch in getCats in CatsGrid always returns a new set of data then I suggest just using a React key on the CatsGrid component so React will unmount/mount a new instance of it. When the React key changes React will interpret this as a new component to render.
const RandomCatApp = () => {
const [catsKey, setCatsKey] = React.useState(0);
return (
<div className="catContainer">
<h1>Random kittens</h1>
<button
onClick={() => setCatsKey(key => key + 1)}
className="catBtn"
>
Generate random kitten
</button>
<CatsGrid key={catsKey} />
</div>
);
};
Redirect happens before onClick() on REACT { Link }
Hello. I'm trying the whole day to play a sound onClick Button Event. It works when I remove the Link (URL), but with the Link, the page goes to the new URL without firing the onClick event.
I already tried like this: onClick={() => this.playAudioHandler}.
I really appreciate if someone can fix my code in order to fire the “playAudioHandler” before going to the new URL.
This is my actual code:
import React, { Component } from "react";
import { Link } from "react-router-dom";
import classes from "./Button.module.css";
import clickSound from "../assets/click.mp3";
class Button extends Component {
playAudioHandler = (event) => {
const audioEl = document.getElementsByClassName("audio-element")[0];
audioEl.play();
// event.preventDefault();
};
render() {
return (
<>
<audio className="audio-element">
<source src={clickSound}></source>
</audio>
<Link to={this.props.linkToGo}>
<button
type="submit"
onClick={this.playAudioHandler}
className={[classes.Button, classes[this.props.btnType]].join(" ")}
// join() to transform the array in a string
>
{this.props.children}
</button>
</Link>
</>
);
}
}
export default Button;
Link is firing each click because is the way it works. In your case, button is wrapped by Link, so Link gets fired.
An easy way to achieve that is to avoid using Link and, since looks like your are using react-router-dom.
<button
type="submit"
onClick={this.playAudioHandler}
className={[classes.Button,
classes[this.props.btnType]].join(" ")}
>
{this.props.children}
</button>
And your handler will redirect after sound was played:
playAudioHandler = (event) => {
event.preventDefault();
const audioEl = document.getElementsByClassName("audio-element")[0];
audioEl.play();
const waitSecs = 3000 // 3 secs
setTimeout(() => {
history.push('/url');
OR
window.location.href = 'url'
}, waitSecs);
};
How about trying setting a state that defines where to redirect and setting that state inClick the button:
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import classes from "./Button.module.css";
import clickSound from "../assets/click.mp3";
class Button extends Component {
constructor(super) {
super(props);
this.state = {
redirect: null
}
}
playAudioHandler = (event) => {
const audioEl =
document.getElementsByClassName("audio-element")[0];
audioEl.play();
// event.preventDefault()
this.setState({ redirect: this.props.linkToGo })
};
render() {
if (this.state.redirect) {
return <Redirect to={redirect} />
}
return (
<>
<audio className="audio-element">
<source src={clickSound}></source>
</audio>
<button
type="submit"
onClick={ this.playAudioHandler}
className={[classes.Button,
classes[this.props.btnType]].join(" ")}
// join() to transform the array in a string
>
{this.props.children}
</button>
</>
);
}
}
export default Button;
Let me know if it works!
my setState doesn't chance the state in the handleClick event handler.
I'm sure the handleClick works because it logs the param.
I'm kind of new to React so I must be overlooking something.
Does this mean there is something wrong with my handleClick function?
Any advice would be really appreciated!
import React from 'react';
import './Projects.css';
import Footer from '../../Components/Footer/Footer.js';
import ProjectPage from
'../../Components/ProjectPage/ProjectPage.js';
import { Redirect, Link } from 'react-router-dom';
class Projects extends React.Component {
constructor(props) {
super(props);
this.state= {
title: "kaufmann house",
content: "charles",
}
this.getImages = this.getImages.bind(this);
}
getImages() {
var VisibilitySensor = require('react-visibility-sensor');
return this.props.projectList.map((post,index) =>
<div>
<div className="projects">
<VisibilitySensor onChange={isVisible =>
this._onChange(isVisible, post.title)}>
<img key={post.id} src={post.featureImage}
className='projectImage' alt='projectImage' onClick= .
{this.handleClick.bind(this, post.content)}/>
</VisibilitySensor>
</div>
</div>
)
}
_onChange = (isVisible, param) => {
isVisible && this.setState({title: param});
};
handleClick = (param) => {
console.log(param);
this.setState({content: param});
};
render() {
return (
<div>
<Link to={{pathname: `/ProjectPage/${this.state.title}`,
state: {
info: `${this.state.content}`}
}}>{this.getImages()}</Link>
<Link to={{pathname: `/ProjectPage/${this.state.title}`,
state: {
info: `${this.state.content}`}
}}>
<Footer title={this.state.title}/>
</Link>
</div>
)
}
}
export default Projects;
this.state= {
title: "kaufmann house",
content: "charles",
}
Your state contains title and content. You have to setState like below. Otherwise, your new state will not update correctly because you replaced the whole state object.
_onChange = (isVisible, param) => {
isVisible && this.setState({
...this.state,
title: param
});
};
handleClick = (param) => {
console.log(param);
this.setState({
...this.state,
content: param
});
};
I would suggest the following changes:
1) Move var VisibilitySensor = require('react-visibility-sensor');
to the top of your file to keep your component clean
import React from 'react';
import './Projects.css';
import Footer from '../../Components/Footer/Footer.js';
import ProjectPage from
'../../Components/ProjectPage/ProjectPage.js';
import { Redirect, Link } from 'react-router-dom';
import VisibilitySensor from 'react-visibility-sensor';
2) Regarding your click handler, it is a bad practice to create handler functions using bind, because this may cause a performance issue since a new function will be created on each render. you can use an arrow function and set data-[attribute]
to add data to your component
getImages() {
//var VisibilitySensor = require('react-visibility-sensor'); remove this line
return this.props.projectList.map((post,index) => (
<div key={post.id}>
<div className="projects">
<VisibilitySensor onChange={isVisible =>
this._onChange(isVisible, post.title)}>
<img src={post.featureImage}
data-content={post.content}
className='projectImage'
alt='projectImage'
onClick={this.handleClick}/>
</VisibilitySensor>
</div>
</div>
))
}
handleClick = (e) => {
var content = e.target.dataset.content;
this.setState((state) => ({
...state,
content
}))
}