javascript - Infinite re-renders when updating useState from streaming audio function
问题描述
I'm building an app React Native where I'm sending an array of audio files into an Expo AV Audio.Sound object, loading them, playing them, and then attempting to update the display of the app itself with info about the audio file being played (specifically how far through the file the user is). I'm trying to update the display through the useState hook which is being called by a callback function from the audio player.
The problem I'm running into is that anytime I try to change the state from the audio player callback function I get thrown into an infinite re-render. Simplified code is below:
import React, { useState} from 'react';
import { Audio } from 'expo-av';
const AudioPlayer = ({ user }) => {
const [currentProgress, setCurrentProgress] = useState(0);
const soundObject = new Audio.Sound();
soundObject.setOnPlaybackStatusUpdate(playbackUpdate);
// sets a function that is called every 500 milliseconds as the audio is played
if(user) {
soundObject.loadAsync({user.message.path});
}
const play = () => {
soundObject.playAsync();
}
const playbackUpdate = (playbackObject) => {
setCurrentProgress(playbackObject.currentMillis);
// updating state with progress through audio file in milliseconds
}
return (
<View>
<Text>{currentProgress}</Text>
<Button title="play" onPress={play} />
</View>
)
}
export default AudioPlayer
解决方案
Remember that everything in your function body will run on every render - so in this case you are creating a new soundObject
and potentially running the soundObject.loadAsync
call on every single render. You'll need to take advantage of other hooks to avoid this - in your case likely useRef
and useEffect
. I would recommend getting familiar with these through the hooks api reference: https://reactjs.org/docs/hooks-reference.html
Here's a quick stab at how I would avoid the unnecessary effects. You'll probably want to review and tweak the dependency arrays depending on how you want things to function and when you want the various effects to be re-run. I'm not sure if you ever need the Sound
object to be re-created for example.
import React, { useState, useRef, useCallback, useEffect} from 'react';
import { Audio } from 'expo-av';
import { Button, View, Text } from 'react-native';
const AudioPlayer = ({ user }) => {
const [currentProgress, setCurrentProgress] = useState(0);
const soundObjectRef = useRef(new Audio.Sound());
useEffect(() => {
const playbackUpdate = (playbackObject) => {
setCurrentProgress(playbackObject.currentMillis);
// updating state with progress through audio file in milliseconds
}
soundObjectRef.current.setOnPlaybackStatusUpdate(playbackUpdate);
}, []); // do this only once per component mount
// sets a function that is called every 500 milliseconds as the audio is played
useEffect(() => {
if (user) {
soundObjectRef.current.loadAsync({user.message.path});
}
}, [user]); // run this anytime user changes but do not run again if user doesn't change
const play = () => {
soundObjectRef.current.playAsync();
}
return (
<View>
<Text>{currentProgress}</Text>
<Button title="play" onPress={play} />
</View>
)
}
export default AudioPlayer
推荐阅读
- c# - 如何使用 jmespath 对 ASC 和 DESC 进行排序
- android - 在 Android 上的多个片段上请求蓝牙适配器对象是否安全?
- javascript - 如何使用打字稿删除数组重复?
- javascript - 无法用 javascript 填充输入
- dgrid - dgrid 从 0.3 更新 - 值得吗?
- http - 用于大字符串有效负载的 HTTP GET
- javascript - 有没有办法在 Java Script 中从 WSDL 生成 Web 服务?
- sql - TOAD 数据点 Postgres SQL 字符串输出被截断
- forms - RenderDecoratedBox 需要合成尺寸:缺失
- javascript - Angular Reactive forms从指令验证控件