0

I have the following code, how do I prevent AsyncStorage.setItem from being called 2 times on initial render? It's called with [] and whatever is loaded from AsyncStorage because logs was updated. The perfect solution should not call setItem at all, because the logs was just retrieved from AsyncStorage.

const AsyncStorage = require("@react-native-community/async-storage") const useStore = () => { const [logs, setLogs] = useState([]) useEffect(() => { AsyncStorage.getItem("logs").then((newLogs) => setLogs(newLogs)); }, []) useEffect(() => { //Don't want to setItem on initial load or when `logs` was just loaded. AsyncStorage.setItem("logs", JSON.stringify(logs)); }, [logs]) const addLog = (newText) => { setLogs(logs => [ {text: newText, createdAt: new Date().getTime()}, ...logs, ]); } return { logs, addLog, //...many other functions that update logs } } 
2
  • so, essentially you want to use it like a componentDidUpdate hook? Commented Aug 30, 2020 at 15:23
  • You could write a hook that ignores the first change..? Commented Aug 30, 2020 at 15:24

4 Answers 4

1

I think wrapping your set state method is a cleaner way to control when logs persisting should take place. Something like this:

const AsyncStorage = require("@react-native-community/async-storage") const useStore = () => { const [logs, setLogsState] = useState([]) const persistLogsRef = useRef(false) const setLogs = (updatedLogs) => { persistLogsRef.current = true setLogsState(updatedLogs) } useEffect(() => { AsyncStorage.getItem("logs").then((newLogs) => setLogsState(newLogs)); }, []) useEffect(() => { //Don't want to setItem on initial load or when `logs` was just loaded. if (persistLogsRef.current) { AsyncStorage.setItem("logs", JSON.stringify(logs)); } }, [logs]) const addLog = (newText) => { setLogs(logs => [ {text: newText, createdAt: new Date().getTime()}, ...logs, ]); } return { logs, addLog, //...many other functions that update logs } } 
Sign up to request clarification or add additional context in comments.

Comments

0

You could use a ref that acts as a flag whether to allow settings logs or not.

const AsyncStorage = require("@react-native-community/async-storage") const useStore = () => { const [logs, setLogs] = useState([]); const allowSettingLogs = React.useRef(false); useEffect(() => { AsyncStorage.getItem("logs").then((newLogs) => setLogs(newLogs)); }, []) useEffect(() => { //Don't want to setItem on initial load or when `logs` was just loaded. if (allowSettingLogs.current) AsyncStorage.setItem("logs", JSON.stringify(logs)); }, [logs]) const addLog = (newText) => { allowSettingLogs.current = true; setLogs(logs => [ {text: newText, createdAt: new Date().getTime()}, ...logs, ]); } return { logs, addLog, //...many other functions that update logs } } 

3 Comments

This will still call AsyncStorage.setItem("logs", JSON.stringify(logs)); after the first useEffect sets logs.
You could use a ref to check for the updates. I have updated the code above.
I'm reluctant to add allowSettingLogs.current = true to all my functions that update logs though, there is addLog, deleteLog, updateLog.... Is there a way to just add mark it on the AsyncStorage.getItem line?
0

You may want to wrap useEffect and make it ignore the first change.

const useEffectIgnoringFirstChange = (fn, deps) => { const firstCallRef = useRef(true); useEffect(() => { if (firstCallRef.current) { firstCallRef.current = false; return; }; fn() }, deps); } const useStore = () => { const [logs, setLogs] = useState([]) useEffect(() => { AsyncStorage.getItem("logs").then((newLogs) => setLogs(newLogs)); }, []) useEffectIgnoringFirstChange(() => { //Don't want to setItem on initial load or when `logs` was just loaded. AsyncStorage.setItem("logs", JSON.stringify(logs)); }, [logs]) const addLog = (newText) => { setLogs(logs => [ {text: newText, createdAt: new Date().getTime()}, ...logs, ]); } return { logs, addLog, //...many other functions that update logs } } 

2 Comments

This will still call it one time with the logs that were just retrieved from AsyncStorage.
It could be modified to use a count instead. I think this is a sign of a larger issue of how you're propagating data. Have you looking into using redux?
0

This is what I ended up with, I'm still wondering if this is the best solution, so more elegant solutions are welcome:

const AsyncStorage = require("@react-native-community/async-storage") const useStore = () => { const justLoaded = useRef(true); const [logs, setLogs] = useState([]) useEffect(() => { AsyncStorage.getItem("logs").then((newLogs) => { justLoaded.current = true; setLogs(newLogs)); } }, []) useEffect(() => { //Don't want to setItem on initial load or when `logs` was just loaded. if (justLoaded.current === false) AsyncStorage.setItem("logs", JSON.stringify(logs)); if (justLoaded.current) justLoaded.current = false; }, [logs]) const addLog = (newText) => { setLogs(logs => [ {text: newText, createdAt: new Date().getTime()}, ...logs, ]); } return { logs, addLog, //...many other functions that update logs } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.