React中LocalStorage失效时如何持久化Composition状态?
React Context 结合 LocalStorage 持久化状态失效问题
我正在开发一个React项目,通过Context Provider为用户提供基于所选参数生成的Composition链接,希望持久化Composition状态,但使用LocalStorage实现后,刷新页面状态无法保留。以下是Context Provider及相关组件的代码:
Context Provider 代码
import {createContext, useContext, useState, useEffect} from 'react'; import { storage } from '../../../Config/firebaseConfig'; import {ref, deleteObject} from 'firebase/storage' const CompositionsContext = createContext(['There are no Compositions']); const useCompositions = () => useContext(CompositionsContext); const CompositionContextProvider = ({children}) => { const[compositions, setCompositions] = useState([]); useEffect(() => { const storedState = localStorage.getItem('compositions'); if (storedState) { setCompositions(JSON.parse(storedState)); } }, []); useEffect(() => { localStorage.setItem('compositions', JSON.stringify(compositions)); }, [compositions]); const addNewComposition = (newComposition) => { setCompositions(compositions => compositions.concat(newComposition)); } const deleteComposition = (indexValue) => { setCompositions(compositions => compositions.filter((_,index) => index !== indexValue)) const storageRef = ref(storage, `pdfs/${compositions[indexValue ]}.pdf`) deleteObject(storageRef); } return ( <CompositionsContext.Provider value={{addNewComposition, compositions, deleteComposition}}> {children} </CompositionsContext.Provider> ) } export {useCompositions}; export default CompositionContextProvider;
CreateComposition 组件代码
import React, { useState } from 'react'; import { useCompositions } from './Contexts/CompositionContextProvider'; const CreateComposition = () =>{ const {addNewComposition} = useCompositions(); const [taal, setTaal] = useState(''); const [bpm, setBpm] = useState(''); const [name, setName] = useState(''); const options = [ {label : "", value: ""}, {label: "Teentaal", value: "Teentaal"}, {label: "Jhaptaal", value: "Jhaptaal"}, {label: "Ektaal", value: "Ektaal"}, {label: "Rupak", value: "Rupak"} ] const sayKayeda = () => { alert(taal + " " + bpm + " " + name); } const changeTaal = (event) => { setTaal(event.target.value); } const changeBPM = (event) => { setBpm(event.target.value); } const changeName = (event) => { setName(event.target.value); } const addNewCompositionHandler = () => { const newComposition = `${taal}_${bpm}_${name}`; addNewComposition(newComposition) setTaal(''); setBpm(''); setName(''); } return ( <> <h2>Input Kayeda Details Below</h2> <br></br> <br></br> <label> <b>Select a Taal</b> {/* <input onChange={changeTaal} value={taal}></input> */} <select onChange = {changeTaal}> {options.map(option =>( <option value={option.value}>{option.label}</option> ))} </select> </label> <p></p> <label> <b>BPM</b> <input onChange={changeBPM} value={bpm}></input> </label> <p></p> <label> <b>Name</b> <input onChange={changeName} value={name}></input> </label> <p></p> <button onClick={sayKayeda}>Current Kayeda</button> <button onClick={addNewCompositionHandler}>Add Composition</button> <p></p> </> ); } export default CreateComposition;
Compositions 组件代码
// Compositions.js import React, { useEffect, useState } from 'react'; import { useCompositions } from './Contexts/CompositionContextProvider'; import TeentaalPDF from '../../PDFs/TeentaalE.pdf'; import JhaptaalPDF from '../../PDFs/JhaptaalE.pdf'; import EktaalPDF from '../../PDFs/EktaalE.pdf'; import RupakPDF from '../../PDFs/RupakE.pdf'; import { storage } from '../../Config/firebaseConfig'; import { ref, getDownloadURL, uploadBytes} from 'firebase/storage'; const Compositions = () => { const {compositions, deleteComposition} = useCompositions(); const [pdfLinks, setPdfLinks] = useState({}); const [fileUpload, setFileUpload] = useState(null); useEffect(() => { const fetchPDFLinks = async () => { const links = {}; for (const composition of compositions) { const downloadURL = await getPDFDownloadURLFromStorage(composition); links[composition] = downloadURL || getDefaultPDF(composition); } setPdfLinks(links); }; fetchPDFLinks(); }, [compositions]); const getPDFDownloadURLFromStorage = async (composition) => { const storageRef = ref(storage, `pdfs/${composition}.pdf`); try { const downloadURL = await getDownloadURL(storageRef); return downloadURL; } catch (error) { console.error(error); return null } }; const getDefaultPDF = (composition) => { if (composition.includes('Teentaal')) { return TeentaalPDF; } else if (composition.includes('Jhaptaal')) { return JhaptaalPDF; } else if (composition.includes('Ektaal')) { return EktaalPDF; } else if (composition.includes('Rupak')) { return RupakPDF; } else { return null; // Return null or a default PDF for unmatched compositions } }; const onSubmitComposition = async (newCompositionName) => { if(!fileUpload) return; const storageRef = ref(storage, `pdfs/${newCompositionName}.pdf`); try { await uploadBytes(storageRef, fileUpload); // Uploading file to Firebase Storage } catch (error) { console.error('Error uploading file:', error); } }; return ( <> <h2>List of Compositions</h2> <h2>{compositions.length}</h2> <ul> {compositions.map((composition, index) => ( <li key={index}> <a href={pdfLinks[composition]} target="_blank" rel="noreferrer">{composition} </a> <button onClick={() => deleteComposition(index)}>Delete Composition</button> <p> <input type = "file" onChange={(e) => setFileUpload(e.target.files[0])}/> <button onClick={() => onSubmitComposition(composition)}> Store Composition </button> </p> </li> ))} </ul> </> ); }; export default Compositions;
问题原因及修复方案
1. LocalStorage 初始覆盖问题
当前代码中,组件挂载时会先执行第二个useEffect,将初始空数组[]存入LocalStorage,直接覆盖之前存储的有效数据。后续第一个useEffect读取到的是被覆盖后的空值,导致刷新后状态丢失。
修复:初始化State时直接读取LocalStorage
将State初始化改为函数式,直接从LocalStorage读取数据,避免初始覆盖:
const[compositions, setCompositions] = useState(() => { const storedState = localStorage.getItem('compositions'); // 解析存储的数据,不存在则返回空数组 return storedState ? JSON.parse(storedState) : []; });
同时移除第一个useEffect,因为初始化已经完成了读取操作。
2. 删除操作中的状态异步问题
deleteComposition函数中,setCompositions是异步更新,直接访问compositions[indexValue]拿到的是旧状态的数组,可能导致删除错误的文件。
修复:在函数式更新中获取最新状态
修改deleteComposition,在setCompositions的回调中获取当前最新数组,确保删除正确的文件:
const deleteComposition = (indexValue) => { setCompositions(currentCompositions => { // 获取要删除的Composition名称 const targetComposition = currentCompositions[indexValue]; // 删除Firebase Storage中的对应文件 const storageRef = ref(storage, `pdfs/${targetComposition}.pdf`); deleteObject(storageRef).catch(err => console.error('删除文件失败:', err)); // 返回过滤后的新数组 return currentCompositions.filter((_, index) => index !== indexValue); }); }
3. 可选:添加输入校验
在addNewCompositionHandler中,添加输入校验,避免空值或无效值被添加到数组:
const addNewCompositionHandler = () => { // 校验必填字段 if (!taal || !bpm || !name) { alert('请填写完整的Composition信息'); return; } const newComposition = `${taal}_${bpm}_${name}`; addNewComposition(newComposition); setTaal(''); setBpm(''); setName(''); }
修改后的完整Context Provider代码
import {createContext, useContext, useState, useEffect} from 'react'; import { storage } from '../../../Config/firebaseConfig'; import {ref, deleteObject} from 'firebase/storage' const CompositionsContext = createContext({ addNewComposition: () => {}, compositions: [], deleteComposition: () => {} }); const useCompositions = () => useContext(CompositionsContext); const CompositionContextProvider = ({children}) => { // 初始化时直接读取LocalStorage const[compositions, setCompositions] = useState(() => { const storedState = localStorage.getItem('compositions'); return storedState ? JSON.parse(storedState) : []; }); useEffect(() => { // 状态变化时存入LocalStorage localStorage.setItem('compositions', JSON.stringify(compositions)); }, [compositions]); const addNewComposition = (newComposition) => { setCompositions(compositions => compositions.concat(newComposition)); } const deleteComposition = (indexValue) => { setCompositions(currentCompositions => { const targetComposition = currentCompositions[indexValue]; const storageRef = ref(storage, `pdfs/${targetComposition}.pdf`); deleteObject(storageRef).catch(err => console.error('删除文件失败:', err)); return currentCompositions.filter((_, index) => index !== indexValue); }); } return ( <CompositionsContext.Provider value={{addNewComposition, compositions, deleteComposition}}> {children} </CompositionsContext.Provider> ) } export {useCompositions}; export default CompositionContextProvider;
内容的提问来源于stack exchange,提问作者51014
相关产品推荐
相关产品推荐

