I have been getting an inconsistent error popping up while interacting with controls my audio player using wavesurfer.js.
Here is the error:
I'm not sure but i think it is to do with the way i have the wavesurfer import inside my useEffect. This was the only way i could get the wavesurfer container to render correctly without throwing a different error.
Here is my component:
import { useSelector } from "react-redux";
import { useEffect } from "react";
import { useState } from "react";
import styles from "../../styles/audioPlayer.module.css";
let WaveSurfer = null;
const AudioPlayer = () => {
// Get the current song and song data from the Redux store
const songCurrent = useSelector((state) => state.songCurrent);
const songData = useSelector((state) => state.songData);
// Get the audioUrl from the songData array
const audioUrl = songCurrent ? songData[songCurrent].songUrl : "";
const artistName = songCurrent ? songData[songCurrent].screenName : "";
const songName = songCurrent ? songData[songCurrent].songName : "";
// State to store the WaveSurfer instance and the current volume
const [wavesurfer, setWaveSurfer] = useState(null);
function timeCalculator(value) {
minute = Math.floor(value / 60);
second = Math.floor(value - minute * 60);
if (second < 10) {
second = "0" + second;
}
return minute + ":" + second;
}
useEffect(() => {
if (!audioUrl) return;
// Destroy the Wavesurfer instance if it already exists
if (wavesurfer) {
wavesurfer.destroy();
}
// Import Wavesurfer
import("wavesurfer.js").then((WaveSurfer) => {
// Initialize Wavesurfer
const wavesurfer = WaveSurfer.default.create({
container: "#wave",
waveColor: "#261b1b",
progressColor: "#f44336",
height: 40,
length: 1000,
scrollParent: false,
hideScrollbar: true,
skipLength: 30,
cursorWidth: 0,
barWidth: 3,
barHeight: 1,
barGap: 1,
});
// Load audio file
wavesurfer.load(audioUrl);
console.log("Audio URL:", audioUrl); // Log the audio URL
// Store the Wavesurfer instance in state
setWaveSurfer(wavesurfer);
wavesurfer.on("ready", function () {
// Initialize DOM elements
/* const duration = document.querySelector("#duration");
const current = document.querySelector("#current"); */
const playPause = document.querySelector("#playPause");
const skipBack = document.querySelector("#skipBack");
const skipForward = document.querySelector("#skipForward");
const volumeOn = document.querySelector("#volumeon");
const volumeSlider = document.querySelector("#volumeSlider");
wavesurfer.on("ready", function () {
wavesurfer.play();
});
//change play button to pause on playing
wavesurfer.on("play", function (e) {
if (playPause) {
playPause.classList.remove("fa-play");
playPause.classList.add("fa-pause");
}
});
//change pause button to play on pause
wavesurfer.on("pause", function (e) {
if (playPause) {
playPause.classList.add("fa-play");
playPause.classList.remove("fa-pause");
}
});
if (playPause) {
playPause.addEventListener("click", function (e) {
wavesurfer.playPause();
});
}
if (skipBack) {
skipBack.addEventListener("click", function (e) {
wavesurfer.skipBackward();
});
}
if (skipForward) {
skipForward.addEventListener("click", function (e) {
wavesurfer.skipForward();
});
}
//Add volumeOn button
if (volumeOn) {
volumeOn.addEventListener("click", function (e) {
wavesurfer.setMute(!wavesurfer.getMute());
});
}
//Add volumeSlider
if (volumeSlider) {
volumeSlider.addEventListener("input", function (e) {
wavesurfer.setVolume(e.target.value / 100);
});
}
});
});
}, [audioUrl]);
return (
<div className={styles.audioContainer}>
<div className={styles.innerContainer}>
<div className={styles.controlContainer}>
<div className={styles.controls}>
<div className={styles.buttons}>
<i className="fa-solid fa-backward-step fa-2x" id="skipBack"></i>
<i className="fa-solid fa-play fa-2x" id="playPause"></i>
<i
className="fa-solid fa-forward-step fa-2x"
id="skipForward"
></i>
</div>
</div>
<div className={styles.volume}>
<input
id="volumeSlider"
className="volume-slider"
type="range"
name="volume-slider"
min="0"
max="100"
defaultValue="75"
></input>
</div>
</div>
<div className={styles.songInfo}>
<div className={styles.artistName}>{artistName}</div>
<div className={styles.songName}>{songName}</div>
</div>
<div className={styles.waveContainer}>
<div id="wave"></div>
</div>
</div>
</div>
);
};
export default AudioPlayer;
Most of the javascipt is pretty standard implementation.
Any of you had this error before or can advise on this error please.
The audioUrl is fed to the component via redux.
Thanks in advance.
Wavesurfer uses WEB Apis that are not accessible during the server render.
When using some libraries with NextJS, you may run into this issue, since NextJS pre-renders the page on the server. On the server, there is no window object, no WebAudio API and many other things.
By placing these browser-only libraries in the useEffect they are ran after the component mounts on the client (browser).
useEffect hook is not ran on the server.
Related
This question already has an answer here:
Gatsby Failed Build - error "window" is not available during server side rendering
(1 answer)
Closed 7 months ago.
Trying to use Isotope on Gatsby. Installed via npm install isotope-layout, then getting this error when gatsby-build :
failed Building static HTML for pages - 2.369s
ERROR #95312
"window" is not available during server side rendering.
See our docs page for more info on this error: https://gatsby.dev/debug-html
57 | }
58 |
> 59 | }( window, function factory( window, Outlayer, getSize, matchesSelector,
utils,
| ^
60 | Item, LayoutMode ) {
61 |
62 | 'use strict';
WebpackError: ReferenceError: window is not defined
- isotope.js:59
[gatsby-starter-blog]/[isotope-layout]/js/isotope.js:59:2
- [...]
Tried to use a useEffect() as mentionned in Gatsby doc :
import * as React from 'react'
import { graphql } from "gatsby"
import Layout from '../components/layout'
import Seo from "../components/seo"
import Isotope from 'isotope-layout/js/isotope';
import { useEffect } from 'react';
const Explore = ({ data, location }) => {
const siteTitle = data.site.siteMetadata?.title || `Title`
const allConcepts = data.allStrapiConcept?.edges
const GridConstruction = () => {
useEffect(() => {
// Isotope initialisation
let iso = new Isotope('.grid', {
itemSelector: '.element-item',
layoutMode: 'fitRows'
})
// Filtering functions
let filters = {
showConcepts: function (itemElem) {
let type = itemElem.querySelector('.type').textContent
return type.match(/Concept/)
},
}
// Button click binding
let filtersBlock = document.querySelector('.filters');
filtersBlock.addEventListener('click', (e) => {
// Bind to filtering function
let filterValue = e.target.getAttribute('data-filter')
filterValue = filters[filterValue] || filterValue
// Filtering
iso.arrange({ filter: filterValue })
})
})
return (
<div className='grid' style={{ border: '1px solid #333', minHeight: '531.251px' }}>
{
allConcepts ?
allConcepts.map(concept => (
<a href={concept.node.slug} key={concept.node.id}>
<span style={{ display: 'block', padding: '15px' }} className='element-item' key={concept.node.id}>
<h3 className='name'>{concept.node.titre}</h3>
<p className='type'>Concept</p>
</span>
</a>
)) : ''
}
</div>
)
}
return (
<Layout location={location} title={siteTitle}>
<Seo title='Explore' />
<div className='button-group filters'>
<button className='button' data-filter='*'>Tout montrer</button>
<button className='button' data-filter='showConcepts'>Concepts</button>
</div>
<GridConstruction />
</Layout>
)
}
export default Explore
export const pageQuery = graphql`
query {
site {
siteMetadata {
title
}
allStrapiConcept {
edges {
node {
id
slug
titre
}
}
}
}
`
Landing on the same error. Also tried the second solution mentioned in the same doc and in that question:
const isBrowser = typeof window !== "undefined"
const Explore = ({ data, location }) => {
// Getting values from from GraphQL PageQuery
const siteTitle = data.site.siteMetadata?.title || `Title`
const allConcepts = data.allStrapiConcept?.edges
// Grid construction
const GridConstruction = () => {
if (isBrowser) {
// Isotope initialisation
let iso = new Isotope('.grid', {
itemSelector: '.element-item',
layoutMode: 'fitRows'
})
// Filtering functions
let filters = {
showConcepts: function (itemElem) {
let type = itemElem.querySelector('.type').textContent
return type.match(/Concept/)
},
}
// Button click binding
let filtersBlock = document.querySelector('.filters');
filtersBlock.addEventListener('click', (e) => {
// Bind to filtering function
let filterValue = e.target.getAttribute('data-filter')
filterValue = filters[filterValue] || filterValue
// Filtering
iso.arrange({ filter: filterValue })
})
}
return (
<div className='grid' style={{ border: '1px solid #333', minHeight: '531.251px' }}>
{
allConcepts ?
allConcepts.map(concept => (
<a href={concept.node.slug} key={concept.node.id}>
<span style={{ display: 'block', padding: '15px' }} className='element-item' key={concept.node.id}>
<h3 className='name'>{concept.node.titre}</h3>
<p className='type'>Concept</p>
</span>
</a>
)) : ''
}
</div>
)
}
Which lands on the same error.
I understand that window because there is no browser during build time, but it felt like at least useEffect() should work. Also, code works fine with gatsby develop?.
Feels like I'm missing something. Any hint ?
You have to use the isBrowser inside the useEffect dependency array.
import React, { useEffect } from "react";
import { render } from "react-dom";
import Head from "next/head";
const App = () => {
const isBrowser = typeof window !== "undefined";
useEffect(() => {
if (isBrowser) {
// Your code here
}
}, [isBrowser]);
return (
<div>
<Head>
<title>Trying out next.js</title>
</Head>
<h2 className="boxed">Testing Example</h2>
</div>
);
};
render(<App />, document.getElementById("root"));
building this lightbox in React, the problem I am facing is that when I goto the next image in the array, it's empty and just showing "Object object" as the error. I am grabbing both the image and text from the array of objects. Anyone have any ideas? The showNext() function is where it's not working. Thanks
import { useState } from 'react';
import Ant from '../../img/ant.jpeg';
import Bee from '../../img/bee.jpeg';
import Bedbug from '../../img/bedbug.jpeg';
import Wasp from '../../img/wasp.jpeg';
import Mouse from '../../img/mouse.jpeg'
// const images = [Bee, Ant, Bedbug, Wasp, Mouse];
const images_arr = [
{ image: Ant, text: "Ant" },
{ image: Bee, text: "Bee" },
{ image: Bedbug, text: "Bedbug" },
{ image: Mouse, text: "Mice" },
{ image: Wasp, text: "Wasp" },
]
function PestsGallery() {
const [imageToShow, setImageToShow] = useState({});
const [lightboxDisplay, setLightBoxDisplay] = useState(false);
//looping through our images array to create img elements
const imageCards = images_arr.map(({ image, text }) => (
<>
<span>{text}</span>
<img key={image} alt={image} className="image-card" onClick={() => showImage(image)} src={image} />
</>
));
//function to show a specific image in the lightbox, amd make lightbox visible
const showImage = (image) => {
setImageToShow(image);
setLightBoxDisplay(true)
}
//hide lightbox
const hideLightBox = () => {
setLightBoxDisplay(false)
}
//show next image in lightbox - this is where the error is happening, when i clicke the arrow the next image is empty.
const showNext = (e) => {
e.stopPropagation();
let currentIndex = images_arr.indexOf(imageToShow);
if (currentIndex >= images_arr.length - 1) {
setLightBoxDisplay(false)
} else {
let nextImage = images_arr[currentIndex + 1];
setImageToShow(nextImage)
}
}
const showPrev = (e) => {
e.stopPropagation();
let currentIndex = images_arr.indexOf(imageToShow);
if (currentIndex <= 0) {
setLightBoxDisplay(false);
} else {
let nextImage = images_arr[currentIndex - 1];
setImageToShow(nextImage);
}
};
return (
<>
<h2> Bugs we treat</h2>
<div>{imageCards}</div>
{
lightboxDisplay ?
<div id="lightbox" onClick={hideLightBox}>
<button onClick={showPrev}>⭠</button>
<img alt="gallery" id="lightbox-img" src={imageToShow}></img>
<button onClick={showNext}>⭢</button>
</div>
: ""
}
</>
);
}
export default PestsGallery;
Inconsistent types of imageToShow local state seems to be at fault (cannot test this without the actual repo). Typescript is great at catching this early.
imageToShow state has this type: { image: .jpeg, text: string }.
In imageCards callback on the img you pass { image: .jpeg } into showImage function, which then uses setImageToShow to set the image to show. Instead, you should be passing the correct state shape i.e. { image, text }.
Also, in your jsx, you need to access the image component in img tag like so:
<img alt="gallery" id="lightbox-img" src={imageToShow.image}></img>
If you look at your setImageToShow calls in showNext and showPrev, the nextImage variable is the entire image object e.g. { image: Bee, text: "Bee" }, since you access images_arr at a specific index (btw, keep you naming consistent to camelCase).
Code snippet aligned with above below for copy paste check:
import { useState } from 'react'
import Ant from '../../img/ant.jpeg'
import Bee from '../../img/bee.jpeg'
import Bedbug from '../../img/bedbug.jpeg'
import Wasp from '../../img/wasp.jpeg'
import Mouse from '../../img/mouse.jpeg'
// const images = [Bee, Ant, Bedbug, Wasp, Mouse];
const images_arr = [
{ image: Ant, text: 'Ant' },
{ image: Bee, text: 'Bee' },
{ image: Bedbug, text: 'Bedbug' },
{ image: Mouse, text: 'Mice' },
{ image: Wasp, text: 'Wasp' },
]
function PestsGallery() {
const [imageToShow, setImageToShow] = useState({})
const [lightboxDisplay, setLightBoxDisplay] = useState(false)
//looping through our images array to create img elements
const imageCards = images_arr.map(({ image, text }) => (
<>
<span>{text}</span>
<img
key={image}
alt={image}
className='image-card'
onClick={() => showImage({ image, text })}
src={image}
/>
</>
))
//function to show a specific image in the lightbox, amd make lightbox visible
const showImage = (image) => {
setImageToShow(image) // image is the object hence it will work
setLightBoxDisplay(true)
}
//hide lightbox
const hideLightBox = () => {
setLightBoxDisplay(false)
}
//show next image in lightbox - this is where the error is happening, when i clicke the arrow the next image is empty.
const showNext = (e) => {
e.stopPropagation()
let currentIndex = images_arr.indexOf(imageToShow)
if (currentIndex >= images_arr.length - 1) {
setLightBoxDisplay(false)
} else {
let nextImage = images_arr[currentIndex + 1]
setImageToShow(nextImage)
}
}
const showPrev = (e) => {
e.stopPropagation()
let currentIndex = images_arr.indexOf(imageToShow)
if (currentIndex <= 0) {
setLightBoxDisplay(false)
} else {
let nextImage = images_arr[currentIndex - 1]
setImageToShow(nextImage)
}
}
return (
<>
<h2> Bugs we treat</h2>
<div>{imageCards}</div>
{lightboxDisplay ? (
<div id='lightbox' onClick={hideLightBox}>
<button onClick={showPrev}>⭠</button>
<img alt='gallery' id='lightbox-img' src={imageToShow.image}></img>
<button onClick={showNext}>⭢</button>
</div>
) : (
''
)}
</>
)
}
export default PestsGallery
I am new to Vuejs. I am using Primevue library to build the api using the composition vuejs 3.
my problem is that menu is not updating. I want to hide the show button when the element is shown and vice versa. I search all the internet and tried all the solutions I found but in vain.
Any help is appreciated, thank you
export default {
name: "Quote",
components: {
loader: Loader,
"p-breadcrumb": primevue.breadcrumb,
"p-menu": primevue.menu,
"p-button": primevue.button,
},
setup() {
const {
onMounted,
ref,
watch,
} = Vue;
const data = ref(frontEndData);
const quoteIsEdit = ref(false);
const toggle = (event) => {
menu.value.toggle(event);
};
const quote = ref({
display_item_date: true,
display_tax: true,
display_discount: false,
});
const menu = ref();
const items = ref([
{
label: data.value.common_lang.options,
items: [{
visible: quote.value.display_item_date,
label: data.value.common_lang.hide_item_date,
icon: 'pi pi-eye-slash',
command: (event) => {
quote.value.display_item_date = !quote.value.display_item_date;
}
},
{
visible: !quote.value.display_item_date,
label: data.value.common_lang.unhide_item_date,
icon: 'pi pi-eye',
command: () => {
quote.value.display_item_date = !quote.value.display_item_date;
}
}
]
]);
}
return {
data,
quoteIsEdit,
menu,
items,
toggle
};
},
template:
`
<div class="container-fluid" v-cloak>
<div class="text-right">
<p-menu id="overlay_menu" ref="menu" :model="items" :popup="true"></p-menu>
<p-button icon="pi pi-cog" class="p-button-rounded p-button-primary m-2" #click="toggle" aria-haspopup="true" aria-controls="overlay_menu"></p-button>
<p-button :label="data.common_lang.save + ' ' + data.common_lang.quote" class=" m-2" /></p-button>
</div>
</div>
`
};
The problem is the items subproperty change is not reactive, so the items.value.items[].visible props are not automatically updated when quote.value.display_item_date changes.
One solution is to make items a computed prop, so that it gets re-evaluated upon changes to the inner refs:
// const items = ref([...])
const items = computed(() => [...])
demo
I am currently converting a RGB color guessing game I made in vanilla HTML, CSS, and JavaScript into React.
When I click on one of the six divs with the class coloredSquare, I want it to grab the backgroundColor of that square and compare it to the rgb color displayed on the screen, coming from the element with the mainColor id.
In vanilla JS it is so simple, you just do this.style.backgroundColor inside of the eventListener but for some reason with React I cannot figure it out. I feel really dumb and I am probably overthinking it and it's actually really simple.
Here's the code:
import React, {Component} from "react";
class RGBGuesser extends Component {
constructor(){
super();
this.state = {
correctCount: 0,
displayCorrect: 0,
colors: "",
chosenResult: "",
chosenCorrect: 0,
}
}
componentDidMount = () => {
this.startGame();
}
initialGameState = () => {
this.setState({
colors: this.displayRandom(6)
})
}
restart = () => {
this.initialGameState();
this.setState({
chosenResult: "",
chosenCorrect: 0,
displayCorrect: 0
})
}
pickSquare = () => {
let colorRan = Math.floor(Math.random() * this.state.colors.length);
return this.state.colors[colorRan]
}
displayRandom = amountSquares => {
const colorArr = [];
for(let i = 0; i < amountSquares; i++){
colorArr.push(this.chooseRandom());
}
return colorArr;
}
chooseRandom = () => {
let rColor = Math.floor(Math.random() * 256);
let gColor = Math.floor(Math.random() * 256);
let bColor = Math.floor(Math.random() * 256);
return `rgb(${rColor}, ${gColor}, ${bColor})`;
}
chooseSquare = () => {
//where i would want to do the logic of clicking the square and comparing it with the rgb color displayed on screen
}
startGame = () => {
this.initialGameState();
this.restart();
}
render(){
let correctColor = this.pickSquare();
return(
<div>
<h1 id="header">RGB Color Guesser</h1>
<h3 id="mainColor">{correctColor}</h3>
<h3 id="result"></h3>
<h3 id="showCorrect">Number Correct: <span id="correctCount">0</span></h3>
<button id="startOver" onClick={this.restart}>Start Over</button>
<div id="colorGrid">
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[0]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[1]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[2]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[3]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[4]}}></div>
<div className="coloredSquare" onClick={this.chooseSquare} style={{backgroundColor: this.state.colors[5]}}></div>
</div>
</div>
)
}
}
export default RGBGuesser;
chooseSquare = (e) => {
console.log(e.currentTarget.style.background)
}
I think passing the event into your event handler and currentTarget \ target is what you're missing
Also don't forget to bind your event handler in your constructor!
constructor() {
// snip
this.chooseSquare = this.chooseSquare.bind(this);
}
I found lots of libraries that somehow marry an external library (and their DOM elements) with Vue.js. All of them though seem to only add child elements to the Vue.js-managed DOM node.
I wrote Vue-Stripe-Elements to make the use of the new Stripe V3 easier but struggled to mount Stripes elements to the Vue component.
The obvious way would be a .vue component like this:
<template>
</template>
<script>
export default {
// could also be `mounted()`
beforeMount () {
const el = Stripe.elements.create('card')
el.mount(this.$el)
}
}
</script>
This would work if Stripe only adds children to the element it is mounted too but it looks like Stripe instead transcludes or replaces the given DOM node. Stripe of course also doesn't support any VNodes.
My current solution to the problem is to create a real DOM node and add it as a child:
<template>
</template>
<script>
export default {
mounted () {
const dom_node = document.createElement('div')
const el = Stripe.elements.create('card')
el.mount(dom_node)
this.$el.appendChild(el)
}
}
</script>
It works but it feels like I'm fighting against Vue.js here and I might create odd side effects here. Or am I just doing what other appending libraries do manually and it is the best way to go?
Is there an "official" way to do this?
Thanks in advance for any helpful comment about it.
Stripe Elements Vuejs 2
Use refs to get DOM elements in vuejs.
HTML
<div ref="cardElement"></div>
JS
mounted() {
const stripe = Stripe('pk');
const elements = stripe.elements();
const card = elements.create('card');
card.mount(this.$refs.cardElement);
}
I faced the same problem, so the method mounted is correct to add, but for the big applications where u call a specific vuejs i got the error
"please make sure the element you are attempting to use is still mounted."
HTML Snippet :
<div style="min-height:100px;">
<div class="group">
<h4><span class="label label-default"> Enter Card Details</span> </h4>
<label class="cardlabel">
<span>Card number</span>
<div id="card-number-element" class="field"></div>
<span class="brand"><i class="pf pf-credit-card" id="brand-icon"></i></span>
</label>
<label class="cardlabel">
<span>Expiry date</span>
<div id="card-expiry-element" class="field"></div>
</label>
<label class="cardlabel">
<span>CVC</span>
<div id="card-cvc-element" class="field"></div>
</label>
</div>
</div>
Vue.js
(function () {
let stripe = Stripe('keyhere');
elements = stripe.elements(),
cardNumberElementStripe = undefined;
cardExpiryElementStripe = undefined;
cardCvcElementStripe = undefined;
var style = {
base: {
iconColor: '#666EE8',
color: '#31325F',
lineHeight: '40px',
fontWeight: 300,
fontFamily: 'Helvetica Neue',
fontSize: '15px',
'::placeholder': {
color: '#CFD7E0',
},
},
};
var purchase= new Vue({
el: '#purchase',
mounted() {
cardNumberElementStripe = elements.create('cardNumber', {
style: style
});
cardExpiryElementStripe = elements.create('cardExpiry', {
style: style
});
cardCvcElementStripe = elements.create('cardCvc', {
style: style
});
cardNumberElementStripe.mount('#card-number-element');
cardExpiryElementStripe.mount('#card-expiry-element');
cardCvcElementStripe.mount('#card-cvc-element');
cardNumberElementStripe.on('change', function (event) {
// Switch brand logo
if (event.brand) {
if (event.error) { setBrandIcon("unknown"); } else { setBrandIcon(event.brand); }
}
//setOutcome(event);
});
function setBrandIcon(brand) {
var brandIconElement = document.getElementById('brand-icon');
var pfClass = 'pf-credit-card';
if (brand in cardBrandToPfClass) {
pfClass = cardBrandToPfClass[brand];
}
for (var i = brandIconElement.classList.length - 1; i >= 0; i--) {
brandIconElement.classList.remove(brandIconElement.classList[i]);
}
brandIconElement.classList.add('pf');
brandIconElement.classList.add(pfClass);
}
var cardBrandToPfClass = {
'visa': 'pf-visa',
'mastercard': 'pf-mastercard',
'amex': 'pf-american-express',
'discover': 'pf-discover',
'diners': 'pf-diners',
'jcb': 'pf-jcb',
'unknown': 'pf-credit-card',
}
},
created() {
//on the buttn click u are calling this using v-on:click.prevent="payment"
payment: function (e) {
stripe.createToken(cardNumberElementStripe).then(function (result) {
debugger;
if (result.token) {
cardId = result.token.id;
// $("#paymentform").get(0).submit();
} else if (result.error) {
errorElement.textContent = result.error.message;
return;
}
});
}
}