unable to show remaining time in wavesurfer.js - reactjs

import React,{ useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from 'react-redux'
import { format, formWaveSurferOptions } from '../../utils/generic'
import { receiveCurrentTrack, receiveTrackRef , togglePlay} from '../../redux/action/currentTrack'
import './_playlist.scss'
import WaveSurfer from "wavesurfer.js";
import {Link} from 'react-router-dom';
const PlayList = (props) => {
const dispatch = useDispatch()
const { track, queue, currentTrack, index } = props
const waveformRef = useRef(null);
const waveRef = useSelector((state)=>state.currentTrack.waveRef)
const wavesurfer = useRef(null);
const currentTime = useRef(null)
const [duration, setDuration] = useState()
useEffect(() => {
const options = formWaveSurferOptions(waveformRef.current);
wavesurfer.current = WaveSurfer.create(options);
wavesurfer.current.load(track.url);
wavesurfer.current.on("ready", ()=> {
if(wavesurfer.current){
wavesurfer.current.setVolume(0)
setDuration(format(wavesurfer.current.getDuration()))
}
});
return () => wavesurfer.current.destroy();
}, [track.url]);
useEffect(()=>{
if(currentTrack){
dispatch(receiveTrackRef(wavesurfer))
}
return ()=> waveRef?.current.stop()
},[currentTrack.track?.url])
const handleClick = () => {
dispatch(receiveTrackRef(wavesurfer))
if(currentTrack){
dispatch(togglePlay())
}
else {
waveRef?.current.stop()
dispatch(receiveCurrentTrack(track, queue));
}
};
return (
<div
className={ currentTrack ? "playlist-item selected playlist" : "playlist-item playlist"}
onClick={handleClick}
>
<div># {index} :</div>
<Link to='/track'>{track.title}</Link>
<div>{track.artist}</div>
<div><span>0.00</span> / {duration && duration}</div>
<div className="wave-form" id="waveform" onChange={()=>console.log('hhh')} ref={waveformRef} />
<div>{duration && duration}</div>
</div>
);
};
export default PlayList;
React-Wavesurfer was unmaintained, so i have moved on to the wavesurfer.js but the issue arrises the now how i can detect the audio current time and show to the page e.g. 0:01 / 3.02, i have use wavesurfer.on('audioprocess') but i can not detect it, so if any one knows the solution please help, thankyou :)

From the Official Doc :
getCurrentTime() – Returns current progress in seconds.
There's also a audioprocess event as you mentioned that fires continuously as the audio plays.
So by combining these together, we have this:
let current;
wavesurfer.on('audioprocess', () => {
current = wavesurfer.getCurrentTime();
});
Note that your wavesurfer player is assigned to the name wavesurfer.current. so the code should be wavesurfer.current.on('audioprocess', () => {});

Related

change background image from parent with child data

im doing a weather app, and i need to change the background-image when the weather change.
in the parent i get my coords, and pass the coords to my weatherApp.jsx
(parent)
import { useEffect, useState } from 'react'
import './App.css'
import WeatherApp from './components/WeatherApp'
function App() {
const [coords, setCoords] = useState()
useEffect(() => {
const success = (pos) => {
const location = {
lat: pos.coords.latitude,
lon: pos.coords.longitude
}
setCoords(location)
}
navigator.geolocation.getCurrentPosition(success)
}, [])
console.log(coords);
return (
<div className="App">
<WeatherApp lon={coords?.lon} lat={coords?.lat}/>
</div>
)
}
export default App
in the child i use axios to get my data form openweathermap, but i need to change the background image from App.jsx
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const WeatherApp = ({lon, lat}) => {
const [weather, setWeather] = useState()
const [temperature, setTemperature] = useState()
const [isCeslsius, setIsCeslsius] = useState(true)
useEffect(() => {
if (lat) {
const APIKey = "***"
const URL = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${APIKey}`
axios.get(URL)
.then(res => {
setWeather(res.data)
const temp = {
celsius: {
celsius_temp: `${Math.round(res.data.main.temp - 273.15)}°C`,
celsius_min: `${Math.round(res.data.main.temp_min - 273.15)}°C`,
celsius_max: `${Math.round(res.data.main.temp - 273.15)}°C`
},
farenheit: {
farenheit_temp: `${Math.round((res.data.main.temp - 273.15) * 9/5 + 32)}°F`,
farenheit_min: `${Math.round((res.data.main.temp_min - 273.15) * 9/5 + 32)}°F`,
farenheit_max: `${Math.round((res.data.main.temp_max - 273.15) * 9/5 + 32)}°F`
}
}
setTemperature(temp)
// trying to change the background image depending on the weather description react
if (weather.weather[0].description === "clear sky") {
let bgImg = {
backgrounImage: "url(../public/clear-sky.jpg)"
}
}
})
.catch(err => console.log(err))
}
}, [lat, lon])
console.log(weather);
const changeTemperature = () => setIsCeslsius(!isCeslsius)
maybe i could just use one component but i know there is a way to pass information from child to parent i just dont know how to
You need to lift the state up. You can do something like this.
App.jsx
const [bgImg, setBgImg] = useState('');
<div className="App" style={{backgroundImage: bgImg}}>
Pass setBgImage to child
<WeatherApp setBgImg={setBgImg} lon={coords?.lon} lat={coords?.lat}/>
In WeatherApp.jsx
let bgImg = "url(../public/clear-sky.jpg)";
props.setBgImg(bgImg);
This should set the background image of parent.
You can use useState hook and initialize it with the default value in parent component. Then pass the setter function to the WeatherApp component as p props.
In parent component
const [bgImgUrl, setBgImgUrl] = useState('your default URL here');
Pass the props to the WeatherApp component
<WeatherApp lon={coords?.lon} lat={coords?.lat} setBgImgUrl={setBgImgUrl} />
Then in the weather App component, you use to write your function
....
setTemperature(temp)
if (weather.weather[0].description === "clear sky") {
let bgImg = {
backgrounImage: "url(../public/clear-sky.jpg)"
}
// update the state
props.setBgImgUrl(bgImg)
}
....

Why is there a delay when I do useState and useEffect to update a variable?

I saw quite a few posts about a delay between setting the state for a component, however, I'm running into this issue in a custom hook that I built. Basically, the classNames that I'm returning are being applied, just with a delay after the component first renders. I've tried using callback functions and useEffect with no luck. Does anyone have any ideas about why there is a small delay?
import * as React from 'react';
import classNames from 'classnames';
import debounce from 'lodash/debounce';
const useScrollStyling = ref => {
const [isScrolledToBottom, setIsScrolledToBottom] = React.useState(false);
const [isScrolledToTop, setIsScrolledToTop] = React.useState(true);
const [isOverflowScrollingEnabled, setIsOverflowScrollingEnabled] = React.useState(false);
const { current } = ref;
React.useEffect(() => {
if (current) {
const { clientHeight, scrollHeight } = current;
setIsOverflowScrollingEnabled(scrollHeight > clientHeight);
}
}, [current]);
const handleScroll = ({ target }) => {
const { scrollHeight, scrollTop, clientHeight } = target;
const isScrolledBottom = scrollHeight - Math.ceil(scrollTop) === clientHeight;
const isScrolledTop = scrollTop === 0;
setIsScrolledToBottom(isScrolledBottom);
setIsScrolledToTop(isScrolledTop);
};
return {
handleScroll: React.useMemo(() => debounce(handleScroll, 100), []),
scrollShadowClasses: classNames({
'is-scrolled-top': isOverflowScrollingEnabled && isScrolledToTop,
'is-scrolled-bottom': isScrolledToBottom,
'is-scrolled': !isScrolledToTop && !isScrolledToBottom,
}),
};
};
export default useScrollStyling;

React-Leaflet: Cannot create a Polyline DYNAMICALLY from React Context

UPDATE!
As Seth Luke asked, why a ref instead of a state, so I did that, and now the lines get drawn! but one step behind. Check out these lines:
useEffect(()=>{
if (drawing) {
setZonePolygon((prev)=>[...prev, [clickLocation.lat, clickLocation.lng]]);
setContextData((prevContext)=>({...prevContext, lines: zonePolygon}));
addZoneMarker();
}
}, [clickLocation]);
"lines" in context is getting updated one step behind the local state "zonePolygon"... how do I correct this? Even if I switch the calls, it's the same, the Context gets updated with a delay...
ORIGINAL POST:
I'm connected to a context in my main map component which contains a . I'm changing the context from another component expecting my map container component to update and re-render the polyline, but it is not happening. What am I doing wrong here? I'm really tired of reading and trying all sort of stuff for over 15 hours no rest now. Could anybody help please? I'd really appreciate it.
My goal is to let the user click different points in the map and have those joined with a line, so that then I can save that as an area or "zone".
This is not being called, I wonder why! I'm using react dev tools to debug and the context does indeed gets the changes, but it's not triggering in the component... so weird.
useEffect(()=>{
console.log('Lines updated in Map component via Context.', lines);
}, [lines]); // This is not being called, I wonder why!!! ****
This is the code I have:
import React, {useState, useEffect, useContext, useRef} from 'react';
import {MapContainer, Marker, Polyline, Polygon, useMapEvent} from 'react-leaflet';
import 'leaflet-rotatedmarker';
import {MapContext} from '../../context/MapProvider';
import Layers from './Layers';
import Ships from '../Ships';
const Map = () => {
const [map, setMap] = useState(null);
const {contextData, setContextData} = useContext(MapContext);
const {clickLocation, drawing, lines} = contextData;
const [shipData, setShipData] = useState();
useEffect(()=>{
console.log('Lines updated in Map component via Context.', lines);
}, [lines]); // This is not being called, I wonder why!!! ****
useEffect(()=>{
if (!map) return;
setContextData({...contextData, mapRef: map});
}, [map]);
useEffect(() => {
setShipData(contextData.vessels);
}, [contextData.vessels]);
function MapEvents() {
const map = useMapEvent('click', (e) => {
setContextData({...contextData, clickLocation: e.latlng});
});
return null;
}
// const ZONE = [
// [-41.95208616893812, -73.52483926124243],
// [-42.246913395396184, -73.17047425039003],
// [-42.19905906325171, -72.68013196793146],
// [-41.936746304733255, -72.81473573174362],
// [-41.8118450173935, -73.22404105435608],
// ]
return (
<MapContainer
center={[-42, -73]}
zoom={10}
style={{height: '100%', width: '100%'}}
whenCreated={setMap}>
<MapEvents />
<Layers />
<Ships data={shipData} />
{
(drawing & lines.length > 1) ? <Polyline positions={lines} /> : null
}
</MapContainer>
)
}
export default Map;
And this is where I'm modifying the context at:
import React, {useState, useEffect, useRef, useContext} from 'react';
import L from 'leaflet';
import styles from '../../styles.module.scss';
import ZoneItem from './ZoneItem';
import { MapContext } from './../../../../context/MapProvider';
const ZonesBar = () => {
const {contextData, setContextData} = useContext(MapContext);
const {mapRef, drawing, lines, clickLocation} = contextData;
const [zones, setZones] = useState([]);
const [zoneMarkers, setZoneMarkers] = useState([]);
let zonePolygon = useRef([]);
useEffect(()=>{
if (drawing) {
setContextData((contextData)=>({...contextData, lines: []}));
zonePolygon.current = [];
} else if (!drawing) {
if (zonePolygon.current.length > 2) {
setContextData((prevContext)=>({...prevContext, zones: [...prevContext.zones, contextData.lines]}));
setZones((prevZones)=>([...prevZones, zonePolygon.current]));
clearMarkers();
}
}
}, [drawing]);
useEffect(()=>{
if (drawing) {
zonePolygon.current.push([clickLocation.lat, clickLocation.lng]);
setContextData((prevContext)=>({...prevContext, lines: zonePolygon.current}));
addZoneMarker();
}
}, [clickLocation]);
function toggleDrawing() {
setContextData((prevContext)=>({...prevContext, drawing: !prevContext.drawing}))
}
function addZoneMarker() {
const newMarker = L.marker([clickLocation.lat, clickLocation.lng])
.addTo(mapRef);
setZoneMarkers((prevMarkers)=>([...prevMarkers, newMarker]));
}
function clearMarkers() {
zoneMarkers.forEach(m => mapRef.removeLayer(m));
}
return (
<div className={styles.zones}>
<button
className={`${styles.btn_add} ${drawing ? styles.btn_drawing : ''}`}
onClick={toggleDrawing}
>
{drawing ? 'Agregar zona' : 'Definir zona'}
</button>
<span style={{fontSize: '0.7rem', fontStyle: 'italic', marginLeft: '0.5rem',}}>
{drawing ? 'Dar clicks en el mapa para definir la zona, luego presionar el botón otra vez.' : ''}
</span>
<div className={styles.list}>
{
zones.length > 0 ?
zones.map(zone => <ZoneItem data={zone} />)
:
'Lista vacía.'
}
</div>
</div>
)
}
export default ZonesBar;
I've changed things up so much now since 9 am today, that I don't know anything else anymore. There's obviously a way of doing this, and I do need some help. If you could take your time to go through this issue that'd be life saving for me.
This is what is looks like, see when I render it with a hard-coded array the polyline comes up.
This is my Context:
import React, {useState, useEffect, createContext, useContext} from 'react'
import io from 'socket.io-client'
import axios from 'axios';
export const MapContext = createContext();
const socket = io("http://localhost:3001");
const MapProvider = ({children}) => {
const [contextData, setContextData] = useState({
mapRef: null,
clickLocation: [],
markers: [],
zones: [],
drawing: false,
lines: [],
vessels: []
});
// Bring vessels info from API and store in Context.
useEffect(()=>{
axios.get('http://localhost:3001/vessel/search/all')
.then(res => {
setContextData((prevContext)=>({...prevContext, vessels: res.data}));
})
.then(()=>{
socket.on('vessels', data => {
setContextData((prevContext)=>({...prevContext, vessels: data}));
})
})
.catch(err => console.log(err.message));
}, []);
return (
<MapContext.Provider value={{contextData, setContextData}}>
{children}
</MapContext.Provider>
)
}
export default MapProvider;
I can't see anything out of the ordinary. But try moving MapEvents outside the Map component. Something like
function MapEvents() {
const {contextData, setContextData} = useContext(MapContext);
const map = useMapEvent('click', (e) => {
setContextData({...contextData, clickLocation: e.latlng});
});
return null;
}
const Map = () => {
const [map, setMap] = useState(null);
const {contextData, setContextData} = useContext(MapContext);
const {clickLocation, drawing, lines} = contextData;
const [shipData, setShipData] = useState();
const linesForPolyline = useRef();
useEffect(()=>{
console.log('Lines updated in Map component via Context.', lines);
}, [lines]);
useEffect(()=>{
if (!map) return;
setContextData({...contextData, mapRef: map});
}, [map]);
useEffect(() => {
setShipData(contextData.vessels);
}, [contextData.vessels]);
// const ZONE = [
// [-41.95208616893812, -73.52483926124243],
// [-42.246913395396184, -73.17047425039003],
// [-42.19905906325171, -72.68013196793146],
// [-41.936746304733255, -72.81473573174362],
// [-41.8118450173935, -73.22404105435608],
// ]
return (
<MapContainer
center={[-42, -73]}
zoom={10}
style={{height: '100%', width: '100%'}}
whenCreated={setMap}>
<MapEvents />
<Layers />
<Ships data={shipData} />
{
(drawing & lines.length > 1) ? <Polyline positions={lines} /> : null
}
</MapContainer>
)
}
export default Map;
It looks like what Seth Lutske suggested in the comments to the original question plus some adjustments did the trick. I wish he could post it as an answer so that I accept it as the solution.
Basically the solution was to use a state hook:
const [zonePolygon, setZonePolygon] = useState([]);
Instead of a useRef:
const zonePolygon = useRef();
Then to have the local state and the global Context update in order I split them in different useEffects. This is the working code, but I believe it needs a refactor:
useEffect(()=>{
if (drawing) {
setZonePolygon((prev)=>[...prev, [clickLocation.lat, clickLocation.lng]]);
addZoneMarker();
}
}, [clickLocation]);
useEffect(()=>{
setContextData((prevContext)=>({...prevContext, lines: zonePolygon}));
}, [zoneMarkers]);

The React useState() hooks stores undefined always even the data that is to be stored logs correctly using console.log();

Here is the where I am having the problem,
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId);
console.log(videoId);
}
Here when I am trying to log the 'parsedId' it logs the data correctly
ioNng23DkIM
And after using the setVideoId() function when I try to log the value it returns undefined
undefined
Here is a snap shot of the log output.
Home.js code:
import React, { useRef, useState } from "react";
import { Link } from "react-router-dom";
import getYouTubeID from 'get-youtube-id';
function Home(props) {
const [videoLink, setVideoLink] = useState();
const [isBool, setBool] = useState(false);
const [videoId, setVideoId] = useState();
const urlRef = useRef();
const handleChange = (event) => {
setVideoLink(event.target.value);
if (urlRef.current.value === '') {
alert('Please enter a URL');
setBool(true);
} else {
setBool(false);
}
}
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId);
console.log(videoId);
}
return (
<section className="homeLayout">
<div className="logo-display">
<img className="logo-img" alt="logo" src="./logo.png" />
<h1>WatchIt</h1>
</div>
<div className="searchlayer">
<form>
<input ref={urlRef} id="videoLink" placeholder="Enter the youtube video URL:" onBlur={handleChange} required />
<Link style={{ pointerEvents: isBool ? 'none' : 'initial' }} to={`/play?=${videoId}`} onClick={handleCLick}>Play</Link>
</form>
</div>
</section>
);
}
export default Home;
You can use useEffect to solve your problem.
Use effect will listen to you state change n then you can perform logic in there.
The problem you're facing is because setState will set the value eventually, not immediately (Usually this means the update will be visible when the component is rendered again). If you want to do something after the value is set, you need to use useEffect.
Splitting your handleClick we get,
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId); // Queue the change for `videoId`
}
useEffect(() => {
console.log(videoId);
}, [videoId]); // Call this function when the value of `videoId` changes

Oscillator built with react hooks won't stop

I'm trying to play around for the first time with the WebAudio API together with React.
My idea was to build a simple button that, once clicked, would start or stop a sound.
With the following code, I'm always getting the error "Failed to execute 'disconnect' on 'AudioNode': the given destination is not connected."
How can I fix it?
Thanks!
import { useState } from 'react';
function App() {
const [ dataPlaying, setDataPlaying ] = useState(false)
const AudioContext = window.AudioContext || window.webkitAudioContext
const audioContext = new AudioContext()
let osc = audioContext.createOscillator()
osc.type = 'sine'
osc.frequency.value = 880
const createOscillator = () => {
if (dataPlaying === false) {
osc.start()
osc.connect(audioContext.destination)
setDataPlaying(true)
} else {
osc.disconnect(audioContext.destination)
setDataPlaying(false)
}
}
return (
<div className="App">
<button
onClick={() => createOscillator() }
data-playing={ dataPlaying }>
<span>Play/Pause</span>
</button>
</div>
);
}
export default App;
Here's my attempt to resolve the connection error.
Create the AudioContext external to the component.
Use an useRef hook to store an audio context to persist through rerenders.
Use an useEffect hook to instantiate an oscillator and manage audo context connection.
Use start/stop toggler to suspend or resume the context instead of connect/disconnect from it.
Updated Code
import React, { useEffect, useRef, useState } from "react";
const AudioContext = window.AudioContext || window.webkitAudioContext;
export default function App() {
const [dataPlaying, setDataPlaying] = useState(false);
const audioContextRef = useRef();
useEffect(() => {
const audioContext = new AudioContext();
const osc = audioContext.createOscillator();
osc.type = "sine";
osc.frequency.value = 880;
// Connect and start
osc.connect(audioContext.destination);
osc.start();
// Store context and start suspended
audioContextRef.current = audioContext;
audioContext.suspend();
// Effect cleanup function to disconnect
return () => osc.disconnect(audioContext.destination);
}, []);
const toggleOscillator = () => {
if (dataPlaying) {
audioContextRef.current.suspend();
} else {
audioContextRef.current.resume();
}
setDataPlaying((play) => !play);
};
return (
<div className="App">
<button onClick={toggleOscillator} data-playing={dataPlaying}>
<span>{dataPlaying ? "Pause" : "Play"}</span>
</button>
</div>
);
}

Resources