javascript - Cum să re-renderizezi imediat componenta copil după actualizarea sessionStorage folosind un hook personalizat
Scopul meu este să construiesc un sistem simplu de recenzii ale produselor folosind React, Next.JS și sessionStorage-ul browser-ului. Utilizatorul ar trebui să poată face clic pe un buton pentru a „Adăuga o recenzie”. Această acțiune va declanșa afișarea unui câmp de text și a unui buton de trimitere. Odată ce utilizatorul apasă butonul de trimitere, conținutul recenziei ar trebui să fie salvat în sessionStorage și să apară imediat într-o listă de recenzii.
Problema mea este că, deși pot actualiza sessionStorage după trimiterea recenziei, aplicația nu afișează imediat lista recenziilor existente. Dacă părăsesc pagina și revin, recenziile vor apărea, ceea ce înseamnă că hook-ul meu personalizat pare să funcționeze corect.
Iată codul ReviewForm.tsx:
export const ReviewForm: React.FC<Props> = ({ productId }): JSX.Element => {
const [showForm, setShowForm] = useState<boolean>(false);
const [storedValues, setStoredValues] = useSessionStorage<SessionStorage[]>(
"products-reviews",
[]
);
const registerReview = (event: any) => {
event.preventDefault();
const reviewText = event.target.review.value;
const productIndex = storedValues?.findIndex(
(review) => review.productId === productId
);
if (productIndex === -1 || productIndex === undefined) {
setStoredValues([...storedValues!, { productId, reviews: [reviewText] }]);
} else {
const reviews = [...storedValues![productIndex].reviews, reviewText];
const updatedReviews = [...storedValues!];
updatedReviews[productIndex].reviews = reviews;
setStoredValues(updatedReviews);
}
setShowForm(false);
};
return (
<div className={styles.reviewsContainer}>
<button
className={styles.addReviewButton}
onClick={() => setShowForm(true)}
>
<span>Add a review</span>
</button>
{showForm && (
<form
className={styles.reviewForm}
onSubmit={(event) => registerReview(event)}
>
<textarea className={styles.reviewInput} name="review" required />
<button className={styles.reviewSubmitButton} type="submit">
Submit
</button>
</form>
)}
<ReviewList productId={productId} />
</div>
);
};
Și iată componenta ReviewList.tsx, redată în interiorul ReviewForm.tsx:
export const ReviewList: React.FC<Props> = ({ productId }): JSX.Element => {
const [reviews, _] = useSessionStorage<SessionStorage[]>(
"products-reviews",
[]
);
const productReviews = reviews?.find(
(review) => review.productId === productId
)?.reviews;
return (
<ul>
{productReviews?.map((review) => (
<li key={Math.random() * 10000}>{review}</li>
))}
</ul>
);
};
În cele din urmă, iată hook-ul meu personalizat useSessionStorage:
export const useSessionStorage = <T>(
key: string,
initialValue?: T
): SessionStorage<T> => {
const [storedValue, setStoredValue] = useState<T | undefined>(() => {
if (!initialValue) return;
try {
const value = sessionStorage.getItem(key);
return value ? JSON.parse(value) : initialValue;
} catch (error) {
return initialValue;
}
});
useEffect(() => {
if (storedValue) {
try {
sessionStorage.setItem(key, JSON.stringify(storedValue));
} catch (error) {
console.log(error);
}
}
}, [storedValue, key]);
return [storedValue, setStoredValue];
};
Titlul întrebării mele spune „cum să refac randarea componentului copil” pentru că am observat că dacă șterg complet componenta ReviewList.tsx, aducând toată logica sa de randare în ReviewForm.tsx, aplicația mea se va comporta așa cum mă aștept.
Deci poate problema este legată de această relație dintre componente?
Orice sfat este binevenit.
Răspuns #1
Problema
Problema este în hook-ul tău useSessionStorage. Nu este de fapt sincronizat cu sesiunea de stocare, deoarece starea este de fapt stocată cu useState, fiind populată doar la montare.
Cum funcționează în cazul tău:
- Inițializezi PRIMUL STAT folosind
useState(în interiorul hook-ului personalizatuseSessionStorage) cu valoarea curentă din session storage la montarea componentului înReviewList.tsx - Inițializezi AL DOILEA STAT folosind
useState(în interiorul hook-ului personalizatuseSessionStorage) cu valoarea curentă din session storage la montarea componentului înReviewForm.tsx - Modifici AL DOILEA STAT și împingi schimbările în stocarea sesiunii cu
useEffect
Așadar, PRIMA STARE nu este actualizată cu noua valoare până nu re-montezi componenta.
Soluția 1 (Va funcționa doar pentru sincronizarea între diferite file de browser)
Trebuie să inversăm fluxul de date de la useState -> sessionStorage la sessionStorage -> useState
export const useSessionStorage = <T>(
key: string,
initialValue?: T
): SessionStorage<T> => {
const [storedValue, setStoredValue] = useState<T | undefined>(() => {
if (!initialValue) return;
try {
const value = sessionStorage.getItem(key);
return value ? JSON.parse(value!) : initialValue;
} catch (error) {
return initialValue;
}
});
const setStorageValue = useCallback((newValue: T) => {
try {
sessionStorage.setItem(key, JSON.stringify(newValue));
} catch (error) {
console.log(error);
}
}, []);
/** This `useEffect` will make sure `storedValue` is always in sync with the `sessionStorage` */
useEffect(() => {
const listenToStorageEvent = (event: StorageEvent) => {
if (event.storageArea === sessionStorage && event.key === key) {
try {
const newValue = JSON.parse(event.newValue!);
if (storedValue !== newValue) {
setStoredValue(newValue);
}
} catch (error) {
console.log(error);
}
}
};
window.addEventListener("storage", listenToStorageEvent);
return () => {
window.removeEventListener("storage", listenToStorageEvent);
};
}, [key]);
// We expose `setStorageValue` which works with `sessionStorage` instead of `setStoredValue` which works with local state
return [storedValue, setStorageValue];
};
Soluția 2
Folosește evenimente personalizate pentru a putea sincroniza și același tab https://github.com/imbhargav5/rooks/blob/main/src/hooks/useSessionstorageState.ts
Soluția 3
Analizează întregul stocaj de sesiune la pornirea aplicației și pune-l ca stare într-un context. După aceea, la fiecare „set”, actualizează atât starea contextului, cât și sessionStorage. Această soluție are multe dezavantaje, cum ar fi predispoziția la erori datorită sincronizării manuale a stării cu stocajul de sesiune, re-redarea excesivă a întregului arbore de componente sub providerul de stocaj de sesiune la fiecare actualizare a valorii stocajului. Așadar, nici măcar nu voi adăuga exemple de cod aici.
