// TuneContainer.js
import React, {useState} from 'react'
import './TuneContainer.css'
function TuneContainer(props) {
const[isPlaying, setIsPlaying] = useState(false)
const[isPaused, setIsPaused] = useState(true)
const audio = document.querySelector('audio')
const audioControls = () => {
if(isPaused) {
console.log(isPlaying)
console.log(isPaused)
setIsPlaying(!isPlaying)
setIsPaused(!isPaused)
console.log(isPlaying)
console.log(isPaused)
audio.play()
} else {
setIsPlaying(!isPlaying)
setIsPaused(!isPaused)
audio.pause()
}
}
return (
<>
<div className="tune-container">
<div className="info-class">
<img src={props.imgsrc} className="tune-img" alt={props.imgalt} onClick={audioControls}></img>
<audio src={props.audiosrc} id="tune" loop hidden></audio>
</div>
</div>
</>
)
}
export default TuneContainer
The above is the code for the container which consist of the image, which when clicked plays the song in an infinite loop, until paused again by clicking the image. Below given is the main page which is calling the TuneContainer and passing it props.
// HomePage.js
import React from 'react'
import NavigationBar from './NavigationBar'
import TuneContainer from './TuneContainer'
import Bird from '../images/Bird.svg'
import BirdWhistling from '../audios/Bird-whistling.mp3'
import Leaves from '../images/Leaves.svg'
import LeavesRustling from '../audios/Rustling-leaves.mp3'
function HomePage() {
return (
<>
<NavigationBar />
<div className="container">
<TuneContainer audiosrc={BirdWhistling} imgsrc={Bird} imgalt="Bird by Ana María Lora Macias from the Noun Project"/>
<TuneContainer audiosrc={LeavesRustling} imgsrc={Leaves} imgalt="leaves by KP Arts from the Noun Project"/>
</div>
</>
)
}
export default HomePage
So here, when I click on the bird image, I hear the the chirping sounds, since those are the props passed. The second TuneContainer has different image and audio altogether. However, when the leaf image is clicked, it still plays the chirping sound. So I believe the audio source is not properly getting updated. Can someone please highlight where am I doing a mistake?
P.S: Before someone asks, I have checked all the routes and filenames correctly, and no, both audio files have different songs in them.
Although I know the SO highly recommends asking one question in one post, I will just add my second question here, since it is highly related and requires no extra bit of code.
Q: When I check the console, the values getting printed (because of the console.log statements) are false, true, false, true. I believe it should print false, true, true, false, since I am printing once before the setState function and once after it. Why such behaviour?
Because document.querySelector('audio') will always return the first html audio element, which in your case is the bird chirping sound.
You can use a unique (id)entifier for each TuneContainer. Use that id on your audio tag and query select that id, which will point the correct audio element.
Another way would be to use a useRef to get the audio element.
// TuneContainer.js
...
const audioRef = React.useRef(null);
/* const audio = document.querySelector('audio') */
const audioControls = () => {
if(isPaused) {
console.log(isPlaying)
console.log(isPaused)
setIsPlaying(!isPlaying)
setIsPaused(!isPaused)
console.log(isPlaying)
console.log(isPaused)
// audio.play()
audioRef.current.play();
} else {
setIsPlaying(!isPlaying)
setIsPaused(!isPaused)
// audio.pause()
audioRef.current.pause();
}
};
...
...
return (
...
...
<audio ref={audioRef} src={props.audiosrc} id="tune" loop hidden></audio>
...
);
Related
So, I've basically tried everything with this one. I ran out of solutions or options. Thing is, I have a button. When you click on it your camera will open and you will see some filters that you can apply to your face. I am new to React. Made it work without the iframe to test the API first, but it's not working anymore inside this iframe. The react component needs to be inside this iframe. The code can be found here with what I did so far/tried: https://codesandbox.io/s/cool-fog-3k5si5?file=/src/components/button/button.jsx
The problem is that when I click the button, the canvas disappears from the page and I get this error in the console:
The DeepAR API fails initialization because the canvas is no longer on the page and it crashes. I really don't know what to search for as I considered this to be a react render error and I tried different ways to write the react code (functional/class). If you have any ideas or suggestions, please help. Thank you in advance.
Your use of useEffect in your Modal and App Component is incorrect.
To remind you, useEffect accepts a function which runs after the render is committed to the screen.
If the function returns a function (which is your case), this function is the "clean up" function which is run before the component is removed from the UI.
So what is happening is that your useEffect code is run only when your components are being unmounted.
Since we are not concerned with any clean up at this stage, a quick solution for you is to move the clean up expressions to the main effect function as follows:
useEffect(() => {
fetch(
"https://cors-anywhere.herokuapp.com/https://staging1.farmec.ro/rest/V1/farmec/deeparProducts/"
)
.then((response) => response.json())
.then((productsJson) => setProducts(productsJson));
}, []);
The same goes for your Modal component :
useEffect(() => {
let initializedDeepAR = new DeepAR({
licenseKey:
"6fda241c565744899d3ea574dc08a18ce3860d219aeb6de4b2d23437d7b6dcfcd79941dffe0e57f0",
libPath: DeepAR,
deeparWasmPath: deeparWasm,
canvas: canvas.current,
segmentationConfig: {
modelPath: segmentationMode
},
callbacks: {
onInitialize: () => {
// let filterName = colors[0].filterData[0]['Filter Binary Path'].match(new RegExp("[^/]+(?=\\.[^/.]*$)"))[0];
setDeepAR(initializedDeepAR);
initializedDeepAR.startVideo(true);
// initializedDeepAR.switchEffect(0, 'slot', `https://staging1.farmec.ro/media/deepArFilters/${filterName}.bin`);
}
}
});
/*#TODO: replace paths with server local path*/
initializedDeepAR.downloadFaceTrackingModel(models);
}, []);
With one additional fix concerning your use of useRef.
To target the element behind the useRef, you must use the .current property.
Finally, your Frame component is using useState to manage the mounting of the iframe. I would suggest using the useRef hook with a useState for your mountNode as follows:
export const Frame = ({
children,
styleSelector,
title,
...props
}) => {
const contentRef = useRef(null)
const [mountNode, setMountNode] = useState()
useEffect(() => {
setMountNode(contentRef.current.contentWindow.document.body)
}, [])
useEffect(() => {
const win = contentRef.current.contentWindow
const linkEls = win.parent.document.querySelectorAll(
styleSelector
)
if (linkEls.length) {
linkEls.forEach((el) => {
win.document.head.appendChild(el)
})
}
}, [styleSelector])
return (
<iframe title={title} {...props} ref={contentRef}>
{mountNode && createPortal(children, mountNode)}
</iframe>
)
}
I'm building my first React demo. I'd like to learn how to write a function and/or useEffect that will display an article based on which button was clicked. I have 4 buttons always visible and I'd like to have 1 article visible depending on which one is selected. Thanks in advance for the assistance!
Here's the setup I have so far:
import React from 'react'
import './main.css'
import mainData from './mainData'
import Button from './menuButtons'
import Article from './contentCard'
function Main() {
const [menuProps] = React.useState(mainData.menu)
const [buttons, setButtonActive] = React.useState(mainData.buttons)
const [articles, setArticle] = React.useState(mainData.articles[0])
function setActive(key) {
setButtonActive(prevActive => {
return prevActive.map((button) => {
return button.key === key ? {...button, active: true} : {...button, active: false}
})
})
}
const buttonElements = buttons.map((buttonProps) => (
<Button
key={buttonProps.key}
label={buttonProps.label}
active={buttonProps.active}
setactive={() => setActive(buttonProps.key)}
/>
))
return (
<main className='content-container flex'>
<section className='menu flex'>
<img src={menuProps.img} alt='Travelogue splash images'/>
<h1>{menuProps.title}</h1>
<p>{menuProps.description}</p>
<div className='menu-buttons flex'>
{buttonElements}
</div>
</section>
<Article
key={articles.key}
style={{backgroundImage: `url(${articles.img})`}}
title={articles.title}
description={articles.description}
/>
</main>
)
}
export default Main
I wanted to share the function structure that worked for me in this scenario...
[For a little more clarification, I'm using a separate data JS file that contains the content, mainData as shown above, that gets mapped in the main.js file for both the buttons and articles. Button keys are set in the data file with a value the same as an index]
function setButtonActive(key) {
setActive(prevActive => {
return prevActive.map((button, index) => {
if (key === index) {
setArticle(mainData.articles[index])
}
return button.key === key ?
{...button, active: true} : {...button, active: false}
})
})
}
The site is now live so everyone can see the finished result of what I was aiming for:
https://willfranck.github.io/react-demo-travel-site/
Thanks!
With your state, how are you determining which button is active?
const [buttons, setButtonActive] = React.useState(mainData.buttons)
const [articles, setArticle] = React.useState(mainData.articles[0])
Here it looks you are mixing two concepts - setting elements as buttons and setting a button as active. I'm not sure what your mainData.buttons is, but unless you are planning on changing the buttons based on user actions, you don't need to set them in state. You've imported them, you can use them.
You should use useState for setting a button as active - how you decide to do that is ultimately up to you. The same is true of articles. How are buttons and articles related? From there, you can determine how to structure your state.
i have a very similiar problem, to a problem, that i had before: How to execute a function AFTER an API-Call was made with setState within useEffect?.
It seems that the useEffect Function, that is calling my API, is running AGAIN after my second useEffect Function was called. So here is what is happening:
quizData is an empty array (which is fine for the beginning)
quizData is an Array filled with data from the API (but without the key, which is fine at this moment)
quizData is then an Array filled with Data from the API and also has the key-property added (so the second useEffect seems to be working). Perfect!
quizData is again the Array only filled with data from the API (the key-property is missing, it seems only the first useEffect is running again) Annoying.
Both solutions of the "old question" are unfortunately not working in this case. The only solution, that was working is to disable the strict-mode of react (then the component is not rendered twice). But its not recommended for beginners to do this, because this workaround seem to be a strong indication, that my code is very unstable.
If i change the dependency-array of the second-useEffect from quizData.length to only quizData. I am getting the infinite renderloop and my complete App is crashing.
If it stays quizData.length the second-useEffect is only running once. Which i dont understand, because i thought EVERY useEffect is running at the beginning independently what is in the dependency-array.
So, is there any solution to this, except to disable Strict-Mode? For me it seems a frequent case, that you call something from an API and want to change the data in it.
Here is my codebase:
import logo from './logo.svg';
import React from "react";
import './App.css';
import Start from "./components/Start.js"
import Questions from "./components/Questions.js"
import {nanoid} from "nanoid"
function App() {
const [start, setStart] = React.useState(true)
const [quizData, setQuizData] = React.useState(() => [])
const isInitialRender = React.useRef(true);
React.useEffect(() => { *//getting the data from the API and put it into an Array*
fetch("https://opentdb.com/api.php?amount=5&type=multiple")
.then(res => res.json())
.then((data) =>
{
isInitialRender.current = false;
setQuizData(data.results);
});
console.log("i am the Boss")
},[])
React.useEffect(() => { *//taking the Array that was created with the API-Call and adding an*
if(!isInitialRender.current) addKey();
}, [quizData.length])
console.log(quizData)
function addKey() {
setQuizData((prevData) => (prevData.map(element => ({...element, key: nanoid()}))))
}
function changeDisplay() {
setStart(prevState => !prevState)
}
return (
<div className="App">
{start ? <Start changeDisplay={changeDisplay}/> : <Questions quizData={quizData} />}
</div>
);
}
export default App;
Stuck on an issue that is probable easy to fix. I have the following React code (which can be found at CodeSandbox):
export const App = ()=> {
const [toggle,setToggle] = useState(false);
const setItemToggle = (e) => {
e.stopPropagation()
e.target.tagName==="H1" ? setToggle(!toggle) : setToggle(false)
e.preventDefault()
}
useEffect(()=>{
window.addEventListener("click",setItemToggle,false);
return () => window.removeEventListener("click",setItemToggle,false);
},[])
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Current Value Of toggle is {toggle ? "True" : "False"}</h2>
</div>
);
}
When you click on the <h1>Hello CodeSandbox</h> I expected the toggle to change accordingly; however, it changes once and doesn't toggle after that. Clicking outside of the element does change the toggle to false. I know it has to do with the eventListener added to the Window but I can't track it down. Thanks for any help.
You have a stale prop problem
since the event handler you assign to the event listener is using the initial closure it was created in (so it doesn't see changes to the closed-over toggle value),
and you don't set the function as a dependency to the useEffect invocation so a new version of the function never gets used.
Had you omitted the empty dependency array altogether, this would have worked, with the caveat that you would be continuously re-registering event handlers.
I recommend using the eslint-plugin-react-hooks linter – it would have caught this.
The easy way to fix this is to not close over (refer to) the toggle variable at all in the event handler at all by using the function form of setState: setToggle(toggle => !toggle).
You are simply changing the state value by doing setState(!state). This pattern does not guarantee that you have the correct value of state. You are better off using the functional pattern:
import { useEffect, useState } from "react";
import "./styles.css";
const App = () => {
const [toggle, setToggle] = useState(false);
const setItemToggle = (e) => {
e.stopPropagation();
e.target.tagName === "H1"
? setToggle((toggle) => !toggle)
: setToggle(false);
e.preventDefault();
};
useEffect(() => {
window.addEventListener("click", setItemToggle, false);
return () => window.removeEventListener("click", setItemToggle, false);
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Current Value Of toggle is {toggle ? "True" : "False"}</h2>
</div>
);
};
export default App;
setState(prevState => !prevState) is a sure way to ensure you have the correct previous value of state. This is the ideal way to set state when new value of state depends on previous value of state.
Sandbox Link
The problem is that your function is unaware of the new value of state, one more way to tackle is using the dependancy array. As the name suggests you are defining the dependencies of the your code in the array. Just pass it as the second param to useEffect and you should be good to go:
import { useEffect, useState } from "react";
import "./styles.css";
const App = () => {
const [toggle, setToggle] = useState(false);
useEffect(() => {
const setItemToggle = (e) => {
e.stopPropagation();
e.target.tagName === "H1" ? setToggle(!toggle) : setToggle(false);
e.preventDefault();
};
window.addEventListener("click", setItemToggle, false);
return () => window.removeEventListener("click", setItemToggle, false);
}, [toggle]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Current Value Of toggle is {toggle ? "True" : "False"}</h2>
</div>
);
};
export default App;
We are once again tackling the problem of incorrect state value but this time by rerunning the code inside the useEffect callback and hence redefining the function setItemToggle.
Link
Note: Also if a function is only relevant inside useEffect, best to define it there only. This is also one of the pros of using hooks instead of classes. Your relevant code is in one place.
You can just add onClick to h1 element and pass a function directly there.
<h1 onClick={() => setToggle(!toggle)}>Hello CodeSandbox</h1>
some good resource on the topic of Handling Events in react can be found here and working sandbox of what I present can be found here
I'm looking to play an audio file using custom controls to trigger the .play() method on a custom button. To be clear, I'm not trying to have the audio auto-play. Everything works perfectly in Chrome, but in Safari I get the error:
Unhandled Promise Rejection: NotAllowedError: The request is not
allowed by the user agent or the platform in the current context,
possibly because the user denied permission.
The project is built using React and React Router, so I'm wondering if it's possibly fixable in my useEffect() hook. I've tried enabling controls on the audio element and using CSS to remove them from the DOM, but no luck.
import React, { useState, useRef, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { gsap } from "gsap";
function RadioPlayerNav(props) {
const audioEl = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const playingTitle = document.querySelector(".radio-player-nav .title p");
const toPX = (value) => {
return (parseFloat(value) / 100) * (/vh/gi.test(value) ? window.innerHeight : window.innerWidth);
};
const radioPlayerGSAP = gsap.to(".radio-player-nav .title p", {
x: toPX("-5vw"),
duration: 4,
ease: "none",
yoyo: true,
repeat: -1,
delay: 1,
repeatDelay: 1,
paused: true,
});
useEffect(() => {
if (isPlaying) {
audioEl.current.play();
radioPlayerGSAP.play();
// radioPlayerGSAP.reversed(4, false);
} else {
audioEl.current.pause();
}
}, [isPlaying]);
return (
<div className="radio-player-nav">
<div className="radio-player-controls">
<audio src="src/current-radio-mix.mp3" ref={audioEl} preload="auto"></audio>
<i
className={isPlaying ? "fas fa-pause cursor-hover" : "fas fa-play cursor-hover"}
onClick={() => {
setIsPlaying(!isPlaying);
}}
></i>
<div className="title">
<p>MIXED FEELINGS M0001</p>
</div>
<a href="src/current-radio-mix.mp3" download="Mixed Feelings M0001">
<i className="fas fa-download cursor-hover"></i>
</a>
</div>
</div>
);
}
export default RadioPlayerNav;
You can find the full github repo for the project here: https://github.com/nallstott/mixed-feelings/tree/master
Turns out, safari requires you to use useLayoutEffect instead of useEffect to accomplish this. I'm leaving the post up since I didn't see anything previously that gave the answer, along with the article that solved it for me in case anyone else has this issue with <audio> on safari.
https://lukecod.es/2020/08/27/ios-cant-play-youtube-via-react-useeffect/
I also play audio in my app, and it cycles through them. I was able to get around this by placing my audio files in a map, and use a separate function to call the audio to play.
import hold_female from '../../assets/audio/Female/hold_female.mp3';
import exhale_female from '../../assets/audio/Female/exhale_female.mp3';
import inhale_female from '../../assets/audio/Female/inhale_female.mp3';
import hold_male from '../../assets/audio/Male/hold_male.mp3';
import exhale_male from '../../assets/audio/Male/exhale_male.mp3';
import inhale_male from '../../assets/audio/Male/inhale_male.mp3';
//Props here...
createAudio('Exhale_female', exhale_female); //These place the audio into a map under the name provided.
createAudio('Inhale_female', inhale_female);
createAudio('Hold_female', hold_female);
createAudio('Exhale_male', exhale_male);
createAudio('Inhale_male', inhale_male);
createAudio('Hold_male', hold_male);
const BreatheTest: FC<BreathingProps> = ({ gender }) => {
const [stageText, setStageText] = useState<string>('Inhale');
const [index, setIndex] = useState<number>(0);
const [milliseconds, setMilliseconds] = useState<number>(0); //Set to 0 so the audio plays right away and there is no delay.
const captions = ['Inhale', 'Hold', 'Exhale', 'Hold'];
const playAudioFiles = () => {
playAudio(`${stageText}_${gender}`);
};
useEffect(() => {
const timeout = setTimeout(() => {
stopAll(); //stop all the previous audio files if they are running.
setStageText(captions[index]);
setIndex(index === 3 ? 0 : index + 1);
setMilliseconds(isSafari ? 4500 : 4350);//Sets the timeout to the time of the audio files.
playAudioFiles(); //Plays the audio files as the useEffect runs
}, milliseconds);
return () => clearTimeout(timeout);
}, [index]);
//... render method and everything else.
}
My app is for controlling breathing, and this is how I have gotten past the error you are seeing. From what I have read, iOS just requires some kind of trigger to start any media, audio or video. Putting the play function into a series of play functions kind of satisfies Safari.
It may not work for you, or how your code works, but if this is where we can discuss how we got around iOS's audio control, this is another way.