javascript - Spread operatörü setState ile çalışmıyor (React, Typescript ve Material-ui)
Material-ui (ve Typescript) kullanarak React'te özel bir Snackbar bileşeni oluşturdum. Spread operatörünün ve await'in nasıl çalıştığını anlamakta zorlanıyorum. (Örnek burada: https://codesandbox.io/s/gifted-hopper-tkkpi?file=/src/App.tsx:1311-1380)
Tam kod:
import React, { FunctionComponent, useEffect } from "react";
import Button from "@material-ui/core/Button";
import Snackbar from "@material-ui/core/Snackbar";
import { Color } from "@material-ui/lab";
import { Alert } from "@material-ui/lab";
interface SnackbarModel {
open: boolean;
severity?: Color;
message: string;
}
const CustomSnackbar: FunctionComponent<SnackbarModel> = (props) => {
const [opened, setOpened] = React.useState(false);
useEffect(() => {
if (props.open) {
setOpened(true);
}
}, [props.open]);
const handleClose = (event: React.ChangeEvent<object>, reason: string) => {
if (reason === "clickaway") {
return;
}
setOpened(false);
};
return (
<div>
<Snackbar
open={opened}
autoHideDuration={2000}
onClose={handleClose}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
>
<Alert severity={props.severity}>{props.message}</Alert>
</Snackbar>
</div>
);
};
export default function App() {
const [snackbar, setSnackbar] = React.useState<SnackbarModel>({
open: false,
message: ""
});
async function handleClick() {
await fetch(Stuff...)
// Working as desired
//setSnackbar({open: true, severity: "success", message: "Snackbar"});
//setSnackbar({open: false, severity: "success", message: "Snackbar"});
// Somehow not working
setSnackbar({ open: true, severity: "success", message: "Snackbar" });
setSnackbar({ ...snackbar, open: false });
}
return (
<div>
<CustomSnackbar
open={snackbar.open}
message={snackbar.message}
severity={snackbar.severity}
/>
<Button variant="contained" onClick={handleClick}>
Snackbar
</Button>
</div>
);
}
Snackbar'ı açmak için tetikleyiciye basıyorum
const [snackbar, setSnackbar] = React.useState<SnackbarModel>({
open: false,
message: ""
});
async function handleClick() {
await fetch(Stuff)
setSnackbar({open: true, severity: "success", message: "Snackbar"})
setSnackbar({open: false, severity: "success", message: "Snackbar"});
}
Snackbar, belirli bir süre sonra otomatik olarak kapanır. "open" sadece Snackbar'ı tetikler ve Snackbar'ın setOpen durumunu kontrol etmez.
Şimdi denedim:
setSnackbar({ open: true, severity: "success", message: "Snackbar" });
console.log(snackbar); //gives you: Object {open: false, message: ""}
setSnackbar({ ...snackbar, open: false });
Bu, Snackbar'ı başarıyla tetikliyor ancak mesaj/metin kayboluyor.
Soru 1: '...snackbar' hâlâ 'message: 'Snackbar'' olarak kaydedilmemiş olmalı mı?
Nedense, eğer "await fetch(Stuff...)" kullanmazsam, fonksiyonu async yapmak ve ilk "setSnackbar"'ı beklemek zorundayım.
async function handleClick() {
//working as desired
await setSnackbar({open: true, severity: "success", message: "Snackbar"});
setSnackbar({open: false, severity: "success", message: "Snackbar"});
// OR
//not working as desired
await setSnackbar({ open: true, severity: "success", message: "Snackbar" });
setSnackbar({ ...snackbar, open: false });
}
Görünüşe göre, eğer önce await ettiğiniz başka bir kod satırınız varsa, "setSnackbar" üzerinde await kullanmaya gerek yok. Benim kullanım durumumda bu şekilde çalışıyor:
async function handleClick() {
await fetch(Stuff...)
setSnackbar({open: true, severity: "success", message: "Snackbar"});
setSnackbar({open: false, severity: "success", message: "Snackbar"});
}
Soru 2: Birisi neden await'in gerekli/neden gerekli olmadığını açıklayabilir mi?
Cevap #1
Uygulamanızdaki asıl sorun, setSnackbar'ın eşzamanlımış gibi ele alınmasıdır, ancak aslında "bir şekilde" öyle değildir. Bunun nedenleri burada ayrıntılı olarak açıklanmıştır: useState set yöntemi değişikliği hemen yansıtmıyor (özellikle ikinci cevap).
Cevap 1:
Spread operatörü çalışmıyor, çünkü çoğu zaman ilk setSnackbar tamamlanmamış oluyor.
Bunu düzeltmek için, snackback nesnesinin open özelliğini bağımlılık olarak dizide geçerek useEffect metodunu kullanmalısınız.
CustomSnackbar'ınızda yaptığınız aynı şeyi yapmalısınız:
useEffect(() => {
if (snackbar.open) {
setSnackbar((_snackbar) => ({ ..._snackbar, open: false }));
}
}, [snackbar.open]);
_snackbar'a dikkat edin, setSnackbar'ın geri çağırmasında open özniteliğinin o andaki değerini almak için kullanılır.
Cevap 2: Await hakkında.
API çağrıları için await kullanmanız gerekir çünkü API çağrıları asenkron/Promise'tir, bu da programın tamamlanmadan önce bir sonraki komutu çalıştıracağı anlamına gelir, bu yüzden onlar için "await" kullanmanız veya tamamlandıklarında bir şeyler yapmak için .then() kullanmanız gerekir.
setSnackbar bir promise değildir, ancak yine de hemen etkili olmaz, bu yüzden React'in setState ve useEffect için callback'leri vardır. Bu tür durumları ele almak için. Bu nedenle, setSnackbar öncesinde await kullanmak bir fark yaratmaz
