I'm trying make a soundboard using React. I've pulled some code from other stack overflow answers but what I can't get to work is to have the audio restart when triggered again during playback. Instead it just continues to play to the end.
Here is my current code:
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
const Button = (props) => {
const useAudio = url => {
const [audio] = useState(new Audio(url));
const [playing, setPlaying] = useState(false);
const toggleSound = () => setPlaying(!playing);
useEffect(() => {
playing ? audio.play() : audio.pause();
},
[playing]
);
useEffect(() => {
audio.addEventListener('ended', () => setPlaying(false));
return () => {
audio.removeEventListener('ended', () => setPlaying(false));
};
}, []);
return [playing, toggleSound];
};
useEffect(() => {
function handleClick(e) {
if(e.key === props.trigger){
toggleSound();
}
}
window.addEventListener('keydown', handleClick);
return () => {
window.removeEventListener('keydown', handleClick);
};
}, []);
const [playing, toggleSound] = useAudio(`./sounds/${props.audio}`);
return (
<ButtonWrapper playing={playing} onClick={toggleSound}>
{props.children} - {props.trigger.toUpperCase()}
</ButtonWrapper>
)
}
export default Button;
const ButtonWrapper = styled.button`
background: ${props => props.playing ? 'red' : 'white'};
border: none;
font-size: 20px;
`
Did you try to use .load() method?
The reference says that .load() reloads the element https://www.w3schools.com/tags/av_met_load.asp
Related
I need some help on how I can update the DOM audio in real time.
I'm trying to make the audio muted and unmuted based on the user preference,
The recording is saved in the public folder.
The audio is working fine on the first load if you set mute to be true or false, then when you try to change it, the mute props do not update.
Here is a short sample of my code, I'm using next.js.
import { RECORDING } from '#/constants/media.constant';
import { useEffect, useState } from 'react';
function Sample() {
const [mute, setMute] = useState(false);
useEffect(() => {
(() => playWithAudio())();
}, []);
const playWithAudio = () => {
const tryToPlay = setInterval(() => {
// audio recording, format mp3
const audio = new Audio(RECORDING.HELLO);
audio.loop = true;
audio.muted = mute;
audio
.play()
.then(() => {
clearInterval(tryToPlay);
})
.catch(() => console.info('User has not interacted with DOM yet.'));
}, 1000);
};
return (
<>
<button onClick={() => setMute(!mute)}>Click</button>
</>
);
}
export default Sample;
Any help would be appreciated.
Give this a try
import { useEffect, useState } from "react";
import { RECORDING } from "#/constants/media.constant";
function Sample() {
const [mute, setMute] = useState(false);
const [audio, setAudio] = useState(null);
useEffect(() => {
setAudio(new Audio(RECORDING.HELLO));
}, []);
useEffect(() => {
if (audio) playWithAudio();
}, [audio]);
const playWithAudio = () => {
const tryToPlay = setInterval(() => {
// audio recording, format mp3
audio.loop = true;
audio.muted = mute;
audio
.play()
.then(() => {
clearInterval(tryToPlay);
})
.catch(() => console.info("User has not interacted with DOM yet."));
}, 1000);
};
return (
<>
<button
onClick={() => {
if (audio) audio.muted = !mute;
setMute(!mute);
}}
>
{mute ? "UnMute" : "Mute"}
</button>
</>
);
}
export default Sample;
I am using reactjs with nextjs, i am having the following problem.
When the page is loaded I have to set a variable which should tell me if user is using dark mode or not.
I did the following, but I'm not sure if it's correct.
I had to set a value, because if I use window inside useState without using useEffect, it gives me problems with nextjs.
const [darkMode, setDarkMode] = useState(false);
useEffect(() => {
setDarkMode(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
const modeMe = (e) => {
setDarkMode(!!e.matches);
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', modeMe);
return window.matchMedia('(prefers-color-scheme: dark)').removeListener(modeMe);
}, []);
or
const useDeviceMode = () => {
const [darkMode, setDarkMode] = useState(false);
useEffect(() => {
setDarkMode(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
const modeMe = e => { setDarkMode(!!e.matches); }
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', modeMe);
return window.matchMedia('(prefers-color-scheme: dark)').removeListener(modeMe);
}, []);
return [darkMode, setDarkMode]
}
const [darkMode, setDarkMode] = useDeviceMode();
Can you give me some advice
Consider this snippet—
const [darkMode, setDarkMode] = useState(false);
const modeMe = (e) => {
setDarkMode(!!e.matches);
};
useEffect(() => {
const matchMedia = window.matchMedia("(prefers-color-scheme: dark)");
setDarkMode(matchMedia.matches);
matchMedia.addEventListener("change", modeMe);
return () => matchMedia.removeEventListener("change", modeMe);
}, []);
Even better, come up with this custom hook—
import { useEffect, useState } from 'react';
const useDarkMode = () => {
const [darkMode, setDarkMode] = useState(false);
const modeMe = (e) => {
setDarkMode(!!e.matches);
};
useEffect(() => {
const matchMedia = window.matchMedia("(prefers-color-scheme: dark)");
setDarkMode(matchMedia.matches);
matchMedia.addEventListener("change", modeMe);
return () => matchMedia.removeEventListener("change", modeMe);
}, []);
return darkMode;
};
I have a React Native component where i'm calling for data in useEffect and rendering data accordingly. But i need to call for data after every 2 seconds and i'm doing that. But the whole component flickers after every two seconds. How can i stop that? Here's my component right now:
const Climate = () => {
const rooms: ControllerRoom<ClimateRoomItemType>[] =
useSelector(climateSelector);
const displayRoomList = useSelector(displayRoomListSelector);
//State
const [active, setActive] = useState<number>(-1);
const [count, setCount] = useState<number>(0);
const [isFetching, setIsFetching] = useState<boolean>(true);
//Handlers
const fetchClimate = useCallback(
() => dispatch(desktopAction.fetchClimate()),
[dispatch],
);
useLayoutEffect(() => {
fetchClimate();
fetchData();
}, []);
const fetchData = useCallback(
() => {
const interval = setInterval(() => {
fetchClimate();
}, 2000);
return () => {
fetchClimate();
clearInterval(interval);
};
},[]
);
useEffect(() => {
if (active < 0 && rooms.length > 0) {
setActive(newlist[0][0].id);
setIsFetching(false);
}
}, [rooms, isFetching]);
return(
<ControllerContainer>
{!displayRoomList ? (
<RoomsClimate
key={Math. floor(Math. random() * 100)}
rooms={rooms}
displayRoomList={displayRoomList}
/>
) : null}
</ControllerContainer>
)
}
export default memo(Climate);
How can i stop flickering of component and still ask for data every 2 seconds?
I am trying to make kind of flash message displayer to display success, error, warning messages at the top for a certain duration.
I have made the use of useRef hook to store timeouts so that I can clear it incase component unmounts before timeout completion.
Everything works as expected except, if the component unmounts before timeout callback, it does not clear the timeout which indeed is trying to setState which gives
Warning: Can't perform a React state update on an unmounted component
import React, { useEffect, useRef, useState } from 'react'
import SuccessGreen from '../../assets/SuccessGreen.svg'
import Cross from '../../assets/Cancel.svg'
import WarningExclamation from '../../assets/WarningExclamation.svg'
const ICONS_MAP = {
"warning": WarningExclamation,
"success": SuccessGreen,
"error": ""
}
export const FlashMessages = ({
duration=5000,
closeCallback,
pauseOnHover=false,
messageTheme='warning',
typoGraphy={className: 'text_body'},
firstIcon=true,
...props
}) => {
const [isDisplayable, setIsDisplayable] = useState(true)
const resumedAt = useRef(null)
const remainingDuration = useRef(duration)
const countDownTimer = useRef(null)
useEffect(() => {
countDownTimer.current = resumeDuration()
console.log(countDownTimer, "From mount")
return () => {clearTimeout(countDownTimer.current)}
}, [])
const resumeDuration = () => {
clearTimeout(countDownTimer.current)
resumedAt.current = new Date()
return setTimeout(() => forceCancel(), remainingDuration.current)
}
const pauseDuration = () => {
if(pauseOnHover){
clearTimeout(countDownTimer.current)
remainingDuration.current = remainingDuration.current - (new Date() - resumedAt.current)
}
}
const forceCancel = () => {
console.log(countDownTimer, "From force")
clearTimeout(countDownTimer.current);
setIsDisplayable(false);
closeCallback(null);
}
return isDisplayable ? (
<div onMouseEnter={pauseDuration} onMouseLeave={resumeDuration}
className={`flash_message_container ${messageTheme} ${typoGraphy.className}`} style={props.style}>
{ firstIcon ? (<img src={ICONS_MAP[messageTheme]} style={{marginRight: 8, width: 20}} />) : null }
<div style={{marginRight: 8}}>{props.children}</div>
<img src={Cross} onClick={forceCancel} style={{cursor: 'pointer', width: 20}}/>
</div>
):null
}
I have tried to mimic the core functionality of this npm package
https://github.com/danielsneijers/react-flash-message/blob/master/src/index.jsx
but whith functional component.
I think the problem is that when the mouseleave event happens, the timeout id returned by resumeDuration is not saved in countDownTimer.current, so the timeout isn't cleared in the cleanup function returned by useEffect.
You could modify resumeDuration to save the timeout id to countDownTimer.current instead of returning it:
countDownTimer.current = setTimeout(() => forceCancel(), remainingDuration.current)
and then, inside useEffect, just call resumeDuration, so the component would look like this:
import React, { useEffect, useRef, useState } from 'react'
import SuccessGreen from '../../assets/SuccessGreen.svg'
import Cross from '../../assets/Cancel.svg'
import WarningExclamation from '../../assets/WarningExclamation.svg'
const ICONS_MAP = {
"warning": WarningExclamation,
"success": SuccessGreen,
"error": ""
}
export const FlashMessages = ({
duration=5000,
closeCallback,
pauseOnHover=false,
messageTheme='warning',
typoGraphy={className: 'text_body'},
firstIcon=true,
...props
}) => {
const [isDisplayable, setIsDisplayable] = useState(true)
const resumedAt = useRef(null)
const remainingDuration = useRef(duration)
const countDownTimer = useRef(null)
useEffect(() => {
resumeDuration()
console.log(countDownTimer, "From mount")
return () => {clearTimeout(countDownTimer.current)}
}, [])
const resumeDuration = () => {
clearTimeout(countDownTimer.current)
resumedAt.current = new Date()
countDownTimer.current = setTimeout(() => forceCancel(), remainingDuration.current)
}
const pauseDuration = () => {
if(pauseOnHover){
clearTimeout(countDownTimer.current)
remainingDuration.current = remainingDuration.current - (new Date() - resumedAt.current)
}
}
const forceCancel = () => {
console.log(countDownTimer, "From force")
clearTimeout(countDownTimer.current);
setIsDisplayable(false);
closeCallback(null);
}
return isDisplayable ? (
<div onMouseEnter={pauseDuration} onMouseLeave={resumeDuration}
className={`flash_message_container ${messageTheme} ${typoGraphy.className}`} style={props.style}>
{ firstIcon ? (<img src={ICONS_MAP[messageTheme]} style={{marginRight: 8, width: 20}} />) : null }
<div style={{marginRight: 8}}>{props.children}</div>
<img src={Cross} onClick={forceCancel} style={{cursor: 'pointer', width: 20}}/>
</div>
):null
}
and it would then mimic the logic from https://github.com/danielsneijers/react-flash-message/blob/master/src/index.jsx
i want to return a function that uses useEffect from the usehook and i am getting error "useeffect is called in a function which is neither a react function component or custom hook.
what i am trying to do?
i have addbutton component and when user clicks add button i want to call the function requestDialog.
below is my code within addbutton file
function AddButton () {
const count = useGetCount();
const requestDialog = useRequestDialog(); //using usehook here
const on_add_click = () => {
requestDialog(count); //calling requestDialog here
}
return (
<button onClick={on_add_click}>add</button>
);
}
interface ContextProps {
trigger: (count: number) => void;
}
const popupContext = React.createContext<ContextProps>({
trigger: (availableSiteShares: number) => {},
});
const usePopupContext = () => React.useContext(popupContext);
export const popupContextProvider = ({ children }: any) => {
const [show, setShow] = React.useState(false);
const limit = 0;
const dismiss = () => {
if (show) {
sessionStorage.setItem(somePopupId, 'dismissed');
setShow(false);
}
};
const isDismissed = (dialogId: string) =>
sessionStorage.getItem(dialogId) === 'dismissed';
const context = {
trigger: (count: number) => {
if (!isDismissed(somePopupId) && count <= limit) {
setShow(true);
} else if (count > limit) {
setShow(false);
}
},
};
return (
<popupContext.Provider value={context}>
{children}
{show && (
<Popup onHide={dismiss} />
)}
</popupContext.Provider>
);
};
export function useRequestDialog(enabled: boolean,count: number) {
return function requestDialog() { //here is the error
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}
}, [count, trigger]);
}
How to solve the error ""useEffect is called in a function which is neither a react function component or custom hook."
i am not knowing how to use useeffect and the same time use it in the addbutton component.
could someone help me with this. thanks
useEffect method is like, useEffect(() => {}, []), But your usage in requestDialog is wrong. Try changing with following.
function requestDialog() {
const { trigger } = usePopupContext();
React.useEffect(() => {
trigger(count);
}, [count, trigger]);
}