在React Native中更新页面上的列表而不重置状态。

huangapple go评论69阅读模式
英文:

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 = &quot;&quot;;
let roomID = &quot;&quot;;
export default function Host({ navigation }) {
if (roomID == &quot;&quot;) {
roomID = createRoom();
}
const [trackList, setTrackList] = useState([]);
if (authToken == &quot;&quot;) {
getAuthAccessToken().then((t) =&gt; (authToken = t));
}
useEffect(() =&gt; {
setTrackListener(roomID, (t) =&gt; {
if (t != null) {
console.log(&quot;Track Name: &quot; + t.name);
console.log(&quot;Current trackList: &quot; + trackList);
const newArray = [...trackList];
newArray.push(t.name);
console.log(&quot;NewArray: &quot; + newArray);
setTrackList(newArray);
console.log(&quot;trackList after set: &quot; + trackList);
}
});
}, []);
return (
&lt;View style={styles.container}&gt;
&lt;Text style={styles.title}&gt;Hosting {roomID}&lt;/Text&gt;
&lt;FlatList
data={trackList}
renderItem={({ item }) =&gt; &lt;Text style={styles.item}&gt;{item}&lt;/Text&gt;}
/&gt;
&lt;/View&gt;
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: &quot;#fff&quot;,
alignItems: &quot;center&quot;,
justifyContent: &quot;center&quot;,
},
title: {
color: &quot;#590&quot;,
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) =&gt; {
let tracksRef = ref(database, `rooms/${id}/tracks`);
onChildAdded(tracksRef, (snapshot) =&gt; {
const data = snapshot.val();
console.log(&quot;Change detected in setTrackListener&quot;);
onChange(data);
});
};

If I instead update the trackList with button like the following, it does update:

&lt;Button
title={&quot;Test&quot;}
onPress={() =&gt; {
const newArray = [...trackList, &quot;TEST&quot;];
setTrackList(newArray);
console.log(trackList);
}}
&gt;&lt;/Button&gt;

答案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) =&gt; {
if (t != null) {
console.log(&quot;Track Name: &quot; + t.name);
console.log(&quot;Current trackList: &quot; + trackList);
const newArray = [...trackList];
newArray.push(t.name);
console.log(&quot;NewArray: &quot; + newArray);
setTrackList(newArray);
console.log(&quot;trackList after set: &quot; + trackList);
}
},[trackList]);

Change your useEffect hook with the following

  useEffect(() =&gt; {
if (!initialized) {
setListener(roomID, onChangeCallback);
setInitialized(true);
}
}, [onChangeCallback,initialized]);

Test, if it's working

  // Only for testing
useEffect(() =&gt; {
setTimeout(() =&gt; {
console.log(trackList);
}, 1000)
})

Hope it helps.
Thanks 在React Native中更新页面上的列表而不重置状态。

答案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,这可能会导致一些性能问题,因此最好重新编写setTrackListeneruseEffect如下:

// 在单独的文件中,从`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) =&gt; [...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) =&gt; [...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) =&gt; {
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) =&gt; {
  let tracksRef = ref(database, `rooms/${id}/tracks`);
  return onChildAdded(tracksRef, (snapshot) =&gt; {
    const data = snapshot.val();
    console.log(&quot;Change detected in setTrackListener&quot;);
    onChange(data);
  });
};

// In the useEffect hook, return the unsubscribe function which will get called during unmount
  useEffect(() =&gt; {
    const unsubscribe = setTrackListener(roomID, (t) =&gt; {
      if (t != null) {
        console.log(&quot;Track Name: &quot; + t.name);
        console.log(&quot;Current trackList: &quot; + trackList);
        const newArray = [...trackList];
        newArray.push(t.name);
        console.log(&quot;NewArray: &quot; + newArray);
        setTrackList(newArray);
        console.log(&quot;trackList after set: &quot; + 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(() =&gt; {
setTrackListener(roomID, (t) =&gt; {
if (t != null) {
console.log(&quot;Track Name: &quot; + t.name);
console.log(&quot;Current trackList: &quot; + trackList);
const newArray = [...trackList, t.name];
console.log(&quot;NewArray: &quot; + 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(&quot;trackList after set: &quot; + 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(() =&gt; {
console.log(&quot;Current trackList: &quot; + trackList);
}, [trackList]);

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年5月26日 10:46:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76337347.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定