We decided to use wavesurferJS in our React project. I have multiple audio files. How do I make the others stop when I play one of them? We tried almost all the solutions over the web. Nothing helped. Any help is appreciated.
import React, { useRef, useEffect, useState } from "react";
import WaveSurfer from "wavesurfer.js";
const VoiceItem= ({url }) => {
const waveformRef = useRef();
const [wavesurfer, setWavesurfer] = useState(null);
useEffect(() => {
if (waveformRef.current) {
const wave = WaveSurfer.create({
container: waveformRef.current,
});
wave.load(url);
setWavesurfer(wave);
}
}, []);
const togglePlayPause = () => {
wavesurfer.playPause();
};
return (
<div>
<button onClick={togglePlayPause}>
</button>
<div ref={waveformRef}></div>
</div>
);
};
export default VoiceItem;
Related
I get the code from https://videojs.com/guides/react/.
If I update the state, my video rerender and the video start playing from first, how to solve.
Example code:
Videojs code:
import React from 'react';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
export const VideoJS = (props) => {
const videoRef = React.useRef(null);
const playerRef = React.useRef(null);
const {options, onReady} = props;
React.useEffect(() => {
// Make sure Video.js player is only initialized once
if (!playerRef.current) {
// The Video.js player needs to be _inside_ the component el for React 18 Strict Mode.
const videoElement = document.createElement("video-js");
videoElement.classList.add('vjs-big-play-centered');
videoRef.current.appendChild(videoElement);
const player = playerRef.current = videojs(videoElement, options, () => {
videojs.log('player is ready');
onReady && onReady(player);
});
// You could update an existing player in the `else` block here
// on prop change, for example:
} else {
const player = playerRef.current;
player.autoplay(options.autoplay);
player.src(options.sources);
}
}, [options, videoRef]);
// Dispose the Video.js player when the functional component unmounts
React.useEffect(() => {
const player = playerRef.current;
return () => {
if (player && !player.isDisposed()) {
player.dispose();
playerRef.current = null;
}
};
}, [playerRef]);
return (
<div data-vjs-player>
<div ref={videoRef} />
</div>
);
}
export default VideoJS;
App.js
import React from 'react';
// This imports the functional component from the previous sample.
import VideoJS from './VideoJS'
const App = () => {
const playerRef = React.useRef(null);
const [timestamp,setTimestamp]= useState(0)
const videoJsOptions = {
autoplay: true,
controls: true,
responsive: true,
fluid: true,
sources: [{
src: '/path/to/video.mp4',
type: 'video/mp4'
}]
};
const handlePlayerReady = (player) => {
playerRef.current = player;
// You can handle player events here, for example:
player.on('waiting', () => {
videojs.log('player is waiting');
});
player.on('dispose', () => {
videojs.log('player will dispose');
});
player.on('timeupdate', function(){
setTimestamp (player.currentTime())
});
};
return (
<>
<div>Rest of app here</div>
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
<div>Rest of app here</div>
</>
);
}
In the App.js, I update the timestamp in timeupdate listener, I get rerender and video again start playing from first.
Please help me to solve
The video is re-rendering because of the onReady(player) in the Videojs file. onReady is prop which is coming to VideoJS from the App.js file which is handlePlayerReady.
When you try to set the state of timestamp on the App.js file with player.on('timeupdate') function the function runs and the prop value goes to Video js and then it again re-renders because the Videojs is wrapped in useEffect. So instead of passing the data from App.js i did changed some codes.
VideoJS
import React from "react";
import videojs from "video.js";
import "video.js/dist/video-js.css";
export const VideoJS = (props) => {
const videoRef = React.useRef(null);
const playerRef = React.useRef(null);
const { options, setTimestamp1 } = props;
React.useEffect(() => {
// Make sure Video.js player is only initialized once
if (!playerRef.current) {
// The Video.js player needs to be _inside_ the component el for React 18 Strict Mode.
const videoElement = document.createElement("video-js");
videoElement.classList.add("vjs-big-play-centered");
videoRef.current.appendChild(videoElement);
const player = (playerRef.current = videojs(videoElement, options, () => {
player.on("waiting", () => {
videojs.log("player is waiting");
});
player.on("dispose", () => {
videojs.log("player will dispose");
});
player.on("timeupdate", () => {
setTimestamp1(player.currentTime());
});
}));
// You could update an existing player in the `else` block here
// on prop change, for example:
} else {
const player = playerRef.current;
player.autoplay(options.autoplay);
player.src(options.sources);
}
}, []);
// Dispose the Video.js player when the functional component unmounts
React.useEffect(() => {
const player = playerRef.current;
return () => {
if (player && !player.isDisposed()) {
player.dispose();
playerRef.current = null;
}
};
}, [playerRef]);
return (
<div data-vjs-player>
<div ref={videoRef} />
</div>
);
};
export default VideoJS;
App.js
import React,{ useState, useEffect} from 'react';
// This imports the functional component from the previous sample.
import VideoJS from './VideoJS'
const App = () => {
const playerRef = React.useRef(null);
const [timestamp1,setTimestamp1]= useState(null);
const videoJsOptions = {
autoplay: true,
controls: true,
responsive: true,
fluid: true,
sources: [{
src: 'dandelions.mp4',
type: 'video/mp4'
}]
};
return (
<>
<div>{timestamp1}</div>
<VideoJS options={videoJsOptions} setTimestamp1={setTimestamp1}/>
<div>{timestamp1}</div>
</>
);
}
export default App
here you can see i removed onReady function and put the data directly into VideoJs where OnReady was rendering and i am sending the setTimestamp as a prop and changing the value from VideoJs file. So re rendering problem is solved and you can use state value in App.js.
I'm using the Intersection Observer API in React to add some animations. I am adding as Intersection Entries some elements.
The problem is that I have the app in multiple languages, and due to the implementation that the tool I am using to translate has, I need to wrap all my components into React.Suspense to wait for languages to load.
When useEffect queries for the elements, they aren't still in the DOM, and therefore they are not assigned as entries.
This is my custom hook:
hooks/useObserver.js
import { useState } from "react";
import { useEffect, useRef } from "react";
export function useObserver(config = {}) {
const [elements, setElements] = useState([]);
const [entries, setEntries] = useState([]);
const observer = useRef(
new IntersectionObserver(observedEntries => {
setEntries(observedEntries);
}, config)
);
useEffect(() => {
const { current: currentObserver } = observer;
currentObserver.disconnect();
if (elements.length > 0) {
elements.forEach(el => currentObserver.observe(el));
}
return () => {
if (currentObserver) {
currentObserver.disconnect();
}
};
}, [elements]);
return { observer: observer.current, setElements, entries };
}
and this is my main component:
App.jsx
import Header from "./components/Header";
import Hero from "./components/Hero";
import Footer from "./components/Footer";
import { Loader } from "./components/shared/Loader";
import { useObserver } from "./hooks/useObserver";
import { useEffect, Suspense } from "react";
function App() {
const { entries, setElements } = useObserver({});
useEffect(() => {
const sections = document.querySelectorAll("section.animated-section");
setElements(sections);
};
}, [setElements]);
useEffect(() => {
entries.forEach(entry => {
entry.target.classList.toggle("section-visible", entry.isIntersecting);
});
}, [entries]);
return (
<Suspense fallback={<Loader />}>
<Header />
<Hero />
<Footer />
</Suspense>
);
}
export default App;
I tried to set a timeout to wait some seconds and then add the elements as entries, and it works correctly:
useEffect(() => {
const observeElements = () => {
const sections = document.querySelectorAll("section.animated-section");
setElements(sections);
};
const observeElementsTimeout = setTimeout(observeElements, 3000);
return () => clearTimeout(observeElementsTimeout)
}, [setElements]);
I want to know if:
There is a way to know when React.Suspense is ready
There is a better approach to solve my problem
Thanks in advance!!
I have a button outside of React Video Player in my project and I should implement the following logic: when we click on this button, we get a full screen for the video. Here's the simplified example of the code that I'm trying to use which is not working. What do I do wrong? :) Thanks for help in advance
import "./styles.css";
import React, { useState, useEffect } from "react";
import ReactPlayer from "react-player";
export default function App() {
const [vidRef, setVidRef] = useState(null);
const setVideoToFullScreen = () => {
const el = vidRef.current;
console.log(el);
if (el.requestFullscreen) {
el.requestFullscreen();
} else if (el.msRequestFullscreen) {
el.msRequestFullscreen();
} else if (el.mozRequestFullScreen) {
el.mozRequestFullScreen();
} else if (el.webkitRequestFullscreen) {
el.webkitRequestFullscreen();
}
};
useEffect(() => {
setVidRef(document.querySelector(".react-player"));
}, []);
return (
<div className="App">
<button onClick={setVideoToFullScreen}>Full Screen</button>
<ReactPlayer
className="react-player"
controls
ref={vidRef}
url="https://www.youtube.com/watch?v=OIFASfPkw9g"
/>
</div>
);
}
I want to make a draggable modal from scratch. Found this tutorial on youtube but it's still using static HTML and vanilla javascript. Tried to use useRef & useEffect on React but I found when clicking the element that using onDrag event inside onMouseDown will only trigger onMouseDown.
The code in vanilla javascript
header.addEventListener("mousedown", () => {
header.classList.add("active");
header.addEventListener("mousemove", onDrag);
});
Code in React
import React, { useRef, useEffect } from 'react'
import ReactDOM from 'react-dom'
import { ModalStyled } from './ModalComponentStyled'
import { ReactComponent as DragIconSVG } from '../../images/drag-icon.svg'
const modalRoot = document.getElementById('modal')
const ModalComponent = ({ close, children, zIndexProps }) => {
let modalWrapper = undefined
const modalRef = useRef()
const moveRef = useRef()
useEffect(() => {
modalWrapper = window.getComputedStyle(modalRef.current)
}, [modalRef])
const dragModalHandler = (e) => {
// const left = parseInt(modalWrapper.left)
// const top = parseInt(modalWrapper.top)
// modalRef.current.style.left = `${left + movementX}px`
// modalRef.current.style.top = `${top + movementY}px`
console.log(e)
}
const mouseDownModalHandler = (e) => {
dragModalHandler(e)
}
return ReactDOM.createPortal(
<ModalStyled zIndexProps={zIndexProps}>
<div className="overlay" onClick={close}></div>
<div ref={modalRef} className="modal-container">
<div className="modal-children">
{children}
<div
className="drag"
ref={moveRef}
onMouseDown={mouseDownModalHandler}
onDrag={dragModalHandler}
>
<DragIconSVG />
</div>
</div>
</div>
</ModalStyled>,
modalRoot
)
I've converted the static vanilla code to sandbox so you guys can see clearly about my context.
https://codesandbox.io/s/draggablediv-pm33s
Got this solution using useState only
https://codesandbox.io/s/draggablemodalreact-7gp38
Hey everyone pretty new to React hooks. I am simply trying to set some reviews that I retrieve from Firebase but cant seem to get it working. I tried a few solutions and I am struggling to get it working any help would be appreciated.
import React, {useContext, useEffect, useState} from 'react';
import firebase from "firebase";
import ReviewsContext from "./review-context";
const Reviews = () => {
const db = firebase.firestore();
let reviews = useContext(ReviewsContext);
let [reviewsLoaded, setReviewsLoaded] = useState(false);
function getReviews(){
db.collection('reviews')
.get()
.then((snapshot) => {
let dataArray = [];
snapshot.docs.forEach(doc => {
dataArray.push(doc.data());
});
reviews = dataArray;
setReviewsLoaded(true);
console.log('reviews', reviews); // logs the correct amount of reviews
})
}
function renderReviews() {
console.log('renderReviews reviewsLoaded', reviewsLoaded); // is true
console.log('renderReviews reviews length', reviews.length); // is 0
if(reviewsLoaded) {
reviews.map((data) => {
return (
<li key={data.name}>
<h3>{data.name}</h3>
<p>{data.position}</p>
</li>
)
});
}
else {
return false
}
}
useEffect(() => {
getReviews(); // this seems to fire before renderReviews
}, []);
return (
<div>
<ul>
{renderReviews()}
</ul>
</div>
)
};
export default Reviews;
In this case, the context should be stateful. The way you're doing it currently won't work since context on render will always revert to reviews being empty. Your Provider component that gives that ReviewContext should be patterned like below.
import React, { createContext, useState } from "react"
const ReviewContext = createContext()
const ReviewProvider = ({children}) => {
const [reviews, setReviews] = useState([])
return (
<ReviewContext.Provider value={{
reviews: reviews,
setReviews: reviews => setReviews(reviews),
}}>
{children}
</ReviewContext.Provider>
)
}
export default ReviewProvider
export { ReviewContext }
Now, you may do const { reviews, setReviews } = useContext(ReviewContext); Just call setReviews whenever you want to update reviews in the context.
It's actually stated in the docs as well as I searched it. https://reactjs.org/docs/context.html#dynamic-context