英文:
Update list on page without resetting state in React Native
问题
I have a Host.js
file which has a track list which is updated as defined by the setTrackListener() function. My issue is that everytime the function is called, the trackList isn't actually updated. Here is an example print out:
Change detected in setTrackListener
LOG Track Name: TEST
LOG Current trackList:
LOG NewArray: TEST
LOG trackList after set:
Here is my code:
let authToken = "";
let roomID = "";
export default function Host({ navigation }) {
if (roomID == "") {
roomID = createRoom();
}
const [trackList, setTrackList] = useState([]);
if (authToken == "") {
getAuthAccessToken().then((t) => (authToken = t));
}
useEffect(() => {
setTrackListener(roomID, (t) => {
if (t != null) {
console.log("Track Name: " + t.name);
console.log("Current trackList: " + trackList);
const newArray = [...trackList];
newArray.push(t.name);
console.log("NewArray: " + newArray);
setTrackList(newArray);
console.log("trackList after set: " + trackList);
}
});
}, []);
return (
<View style={styles.container}>
<Text style={styles.title}>Hosting {roomID}</Text>
<FlatList
data={trackList}
renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
title: {
color: "#590",
fontSize: 32,
},
});
setTrackListener
is defined in a separate file, where I listen to changes on a specific Google Firebase Database. Here is that code:
let setTrackListener = (id, onChange) => {
let tracksRef = ref(database, `rooms/${id}/tracks`);
onChildAdded(tracksRef, (snapshot) => {
const data = snapshot.val();
console.log("Change detected in setTrackListener");
onChange(data);
});
};
If I instead update the trackList with a button like the following, it does update:
<Button
title={"Test"}
onPress={() => {
const newArray = [...trackList, "TEST"];
setTrackList(newArray);
console.log(trackList);
}}
></Button>
英文:
I have a Host.js
file which has a track list which is updated as defined by the setTrackListener() function. My issue is that everytime the function is called, the trackList isn't actually updated. Here is an example print out:
Change detected in setTrackListener
LOG Track Name: TEST
LOG Current trackList:
LOG NewArray: TEST
LOG trackList after set:
Here is my code:
let authToken = "";
let roomID = "";
export default function Host({ navigation }) {
if (roomID == "") {
roomID = createRoom();
}
const [trackList, setTrackList] = useState([]);
if (authToken == "") {
getAuthAccessToken().then((t) => (authToken = t));
}
useEffect(() => {
setTrackListener(roomID, (t) => {
if (t != null) {
console.log("Track Name: " + t.name);
console.log("Current trackList: " + trackList);
const newArray = [...trackList];
newArray.push(t.name);
console.log("NewArray: " + newArray);
setTrackList(newArray);
console.log("trackList after set: " + trackList);
}
});
}, []);
return (
<View style={styles.container}>
<Text style={styles.title}>Hosting {roomID}</Text>
<FlatList
data={trackList}
renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
title: {
color: "#590",
fontSize: 32,
},
});
setTrackListener
is defined in a seperate file, where I listen to changes on a specific Google Firebase Database. Here is that code:
let setTrackListener = (id, onChange) => {
let tracksRef = ref(database, `rooms/${id}/tracks`);
onChildAdded(tracksRef, (snapshot) => {
const data = snapshot.val();
console.log("Change detected in setTrackListener");
onChange(data);
});
};
If I instead update the trackList with button like the following, it does update:
<Button
title={"Test"}
onPress={() => {
const newArray = [...trackList, "TEST"];
setTrackList(newArray);
console.log(trackList);
}}
></Button>
答案1
得分: 1
以下是已翻译的内容:
[Tyson-Z](https://stackoverflow.com/users/8581245/tyson-z) 解释的原因似乎是合适的。
我建议以下更改。
创建一个状态变量来跟踪初始化状态。
const [initialized, setInitialized] = useState(false);
创建一个单独的回调函数
const onChangeCallback = useCallback((t) => {
if (t != null) {
console.log("Track Name: " + t.name);
console.log("Current trackList: " + trackList);
const newArray = [...trackList];
newArray.push(t.name);
console.log("NewArray: " + newArray);
setTrackList(newArray);
console.log("trackList after set: " + trackList);
}
}, [trackList]);
将您的 useEffect 钩子更改为以下内容
useEffect(() => {
if (!initialized) {
setListener(roomID, onChangeCallback);
setInitialized(true);
}
}, [onChangeCallback, initialized]);
测试,看看它是否工作
// 仅用于测试
useEffect(() => {
setTimeout(() => {
console.log(trackList);
}, 1000)
})
希望对您有帮助。
谢谢 :)
如果您需要更多翻译,请告诉我。
英文:
The reason explained by Tyson-Z seems appropriate.
I would suggest the following changes.<br>
Create a state variable to keep track of initialization state.
const [initialized, setInitialized] = useState(false);
Create a separate callback function
const onChangeCallback = useCallback((t) => {
if (t != null) {
console.log("Track Name: " + t.name);
console.log("Current trackList: " + trackList);
const newArray = [...trackList];
newArray.push(t.name);
console.log("NewArray: " + newArray);
setTrackList(newArray);
console.log("trackList after set: " + trackList);
}
},[trackList]);
Change your useEffect hook with the following
useEffect(() => {
if (!initialized) {
setListener(roomID, onChangeCallback);
setInitialized(true);
}
}, [onChangeCallback,initialized]);
Test, if it's working
// Only for testing
useEffect(() => {
setTimeout(() => {
console.log(trackList);
}, 1000)
})
Hope it helps.
Thanks
答案2
得分: 1
将useEffect
函数内的setTrackList(newArray);
转换为:
setTrackList((trackList) => [...trackList, t.name]);
我认为你对setTrackListener
的实现是正确的。
所以我认为主要原因是useEffect
中的trackList
变量被包裹在一个陈旧的闭包中,这是JavaScript的一个特性,当你定义一个函数,并且它在函数外部使用一个变量时,它会缓存该变量在函数定义时的值,并始终返回相同的值(在这种情况下为空数组)。
要解决这个问题,你需要将trackList
变量放入useEffect
的依赖数组中,或者将回调函数传递给setState
函数,以避免在useEffect
内部访问trackList
的需要。
建议将useEffect
函数中的setTrackList(newArray);
转换为:
setTrackList((trackList) => [...trackList, t.name]);
并且不要在回调函数中使用console.log(trackList)
(因为它始终是[]
),要么将控制台日志放到函数体内(仅用于调试,相当不干净),要么在setTrackList
的回调内部打印值:
setTrackList((trackList) => {
const newArray = [...trackList, t.name]
console.log(newArray)
return newArray
});
如果这不起作用,你没有看到更新的trackList
在输出中的另一个可能原因是React不会立即更新状态,setState
函数将触发重新渲染,并且状态在下一个重新渲染中以批处理方式更新。
但在许多情况下,更新会立即生效,但你不能依赖于这一点。
注意一件事是,当组件卸载时,useEffect
不会取消订阅onChildAdded
,这可能会导致一些性能问题,因此最好重新编写setTrackListener
和useEffect
如下:
// 在单独的文件中,从`onChildAdded`返回取消订阅函数
const setTrackListener = (id, onChange) => {
let tracksRef = ref(database, `rooms/${id}/tracks`);
return onChildAdded(tracksRef, (snapshot) => {
const data = snapshot.val();
console.log("Change detected in setTrackListener");
onChange(data);
});
};
// 在`useEffect`钩子中,返回在卸载期间将被调用的取消订阅函数
useEffect(() => {
const unsubscribe = setTrackListener(roomID, (t) => {
if (t != null) {
console.log("Track Name: " + t.name);
console.log("Current trackList: " + trackList);
const newArray = [...trackList];
newArray.push(t.name);
console.log("NewArray: " + newArray);
setTrackList(newArray);
console.log("trackList after set: " + trackList);
}
});
return unsubscribe
}, []);
参考链接:
https://dmitripavlutin.com/react-hooks-stale-closures/#3-state-closure-in-useeffect
https://blog.logrocket.com/why-react-doesnt-update-state-immediately/
英文:
TLDR:
convert your setTrackList(newArray);
inside the useEffect
function to
setTrackList((trackList) => [...trackList, t.name]);
I think your implementation on setTrackListener is correct.
So I think the main reason is that the trackList
variable in useEffect
is wrapped in a stale closure, this is a trait of Javascript that,
when you define a function, and it uses a variable outside of that function,
it will cache the value of that variable at the time the function is defined, and always return the same value (i.e. empty array in this case).
To fix that, you need to either put trackList
variable into the dependency array of useEffect
, or, pass a callback function to the setState function to avoid the need for access trackList
inside useEffect
.
Suggestion:
convert your setTrackList(newArray);
in the useEffect function to
setTrackList((trackList) => [...trackList, t.name]);
and do not use console.log(trackList)
in the callback function (you won't see the value since it is always []
), either put the console log to the function body (quite dirty, only use for debugging), or print the value inside the setTrackList
's callback
setTrackList((trackList) => {
const newArray = [...trackList, t.name]
console.log(newArray)
return newArray
});
If that doesn't work, another possible reason you didn't see the updated trackList in your output is React does not update the state immediately, the setState
function will trigger a re-render, and the states are updated in a batch in the next re-render.
But in many cases, the update takes effects immediately, but you cannot rely on that.
P.S.
One thing to notice is that the useEffect
does not unsubscribe to onChildAdded
when the component is unmounted, this might cause some performance issues, so it will be better to re-write the setTrackListener
and useEffect
to
// In the separate file, return the unsubscribe function from `onChildAdded`
const setTrackListener = (id, onChange) => {
let tracksRef = ref(database, `rooms/${id}/tracks`);
return onChildAdded(tracksRef, (snapshot) => {
const data = snapshot.val();
console.log("Change detected in setTrackListener");
onChange(data);
});
};
// In the useEffect hook, return the unsubscribe function which will get called during unmount
useEffect(() => {
const unsubscribe = setTrackListener(roomID, (t) => {
if (t != null) {
console.log("Track Name: " + t.name);
console.log("Current trackList: " + trackList);
const newArray = [...trackList];
newArray.push(t.name);
console.log("NewArray: " + newArray);
setTrackList(newArray);
console.log("trackList after set: " + trackList);
}
});
return unsubscribe
}, []);
References:
https://dmitripavlutin.com/react-hooks-stale-closures/#3-state-closure-in-useeffect
https://blog.logrocket.com/why-react-doesnt-update-state-immediately/
答案3
得分: 0
为了观察更新后的状态值,您可以使用useEffect
钩子,并将其依赖设置为trackList
状态。
通过在useEffect
钩子中将[trackList]添加为依赖数组,每当trackList
状态发生变化时,回调函数都将被调用。
useEffect(() => {
setTrackListener(roomID, (t) => {
if (t != null) {
console.log("Track Name: " + t.name);
console.log("Current trackList: " + trackList);
const newArray = [...trackList, t.name];
console.log("NewArray: " + newArray);
setTrackList(newArray);
}
});
}, [trackList]);
英文:
To observe the updated state value, you can make use of the useEffect hook with a dependency on the trackList state.
By adding [trackList] as the dependency array in the useEffect hook, the callback function will be called whenever the trackList state changes.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
useEffect(() => {
setTrackListener(roomID, (t) => {
if (t != null) {
console.log("Track Name: " + t.name);
console.log("Current trackList: " + trackList);
const newArray = [...trackList, t.name];
console.log("NewArray: " + newArray);
setTrackList(newArray);
}
});
}, [trackList]);
<!-- end snippet -->
答案4
得分: 0
使用空数组 [] 的 useEffect 只会运行一次。您需要添加变化的依赖项,例如 [t]、t、t.name 或任何应该触发 setTrackListener 的 props 变化。
此外,在 "setTrackList(newArray)" 旁边添加以下代码
console.log("trackList after set: " + trackList)
不会显示 trackList 的最新状态。
尝试以下作为单独的 useEffect:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
useEffect(() => {
console.log("Current trackList: " + trackList);
}, [trackList]);
<!-- end snippet -->
英文:
The useEffect with [] will only run once. You need to add the changing dependencies e.g. [t]. t, t.name, or any props that their changes should trigger the setTrackListener.
Also
console.log("trackList after set: " + trackList)
next to
setTrackList(newArray)
would not show the latest state of the trackList.
Try below as a seperate useEffect:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
useEffect(() => {
console.log("Current trackList: " + trackList);
}, [trackList]);
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论