Created
December 13, 2023 11:46
-
-
Save thomheymann/2eee203a3a8c7d10ebb4f0ca647704e5 to your computer and use it in GitHub Desktop.
State management discussion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useState } from "react"; | |
import "./styles.css"; | |
export default function App() { | |
useEffect( | |
() => { | |
console.log("First of all, sync the URL (async operation)"); | |
}, | |
[ | |
// May have deps | |
] | |
); | |
useEffect( | |
() => { | |
console.log("Second, restore something from storage"); | |
}, | |
[ | |
// May have other deps | |
] | |
); | |
// This can quickly become long and we cannot mix all these options in a single effect, | |
// since this would imply keeping dependencies listed which can change the behaviour of this flow or repeat it/ | |
/** | |
* useEffect( | |
() => { | |
console.log("First of all, sync the URL (async operation)"); | |
console.log("Second, restore something from storage"); | |
}, | |
[ | |
// May have sync deps, | |
// May have restore step deps | |
] | |
); | |
*/ | |
return <ObsPage />; | |
} | |
function ObsPage() { | |
useEffect(() => { | |
console.log("Now init page state"); | |
}, []); | |
return <ObsComponent />; | |
} | |
function ObsComponent() { | |
useEffect(() => { | |
console.log("Fetch some specific component data that updates the url"); | |
}, []); | |
return <h1>Hello CodeSandbox</h1>; | |
} | |
// Expected behaviour | |
// 1. First of all, sync the URL | |
// 2. Now init page state | |
// 3. Fetch some specific component data that updates the url | |
// but it's not (react first invoke children effects) | |
// 1. Fetch some specific component data that updates the url | |
// 2. Now init page state | |
// 3. First of all, sync the URL | |
// To make the above flow working as expected, | |
// we should handle a new state to conditionally render the child component after a step is correctly executed, | |
// which become more complex when involves taking extra care of error handling and multiple conditions | |
function App2() { | |
const [isSynced, setSynced] = useState(false); | |
useEffect(() => { | |
console.log("First of all, sync the URL"); | |
setSynced(true); | |
}, []); | |
useEffect(() => { | |
if (isSynced) { | |
console.log("Second, restore something from storage"); | |
} | |
}, [ | |
isSynced, | |
// May have other deps | |
]); | |
return isSynced ? <ObsPage2 /> : null; | |
} | |
function ObsPage2() { | |
const [isInit, setInit] = useState(false); | |
useEffect(() => { | |
console.log("Now init page state"); | |
setInit(true); | |
}, []); | |
return isInit ? <ObsComponent2 /> : null; | |
} | |
function ObsComponent2() { | |
const [data, setData] = useState(null); | |
useEffect(() => { | |
console.log("Fetch some specific component data that updates the url"); | |
setData({}); | |
}, []); | |
return data ? <h1>Hello CodeSandbox 2</h1> : null; | |
} | |
// This can quickly become difficult to handle when more states are involved, each with different dependencies and behaviors. | |
// With a state machine, we can control deterministically the flow in a more graphic way | |
// and prevent undesired behaviors like the one above since it makes easier | |
// to graphically control when we transition into a new state of our flow. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useAsync } from "react-use"; | |
// I don't understand this point you're making: | |
// > This can quickly become long and we cannot mix all these options in a single | |
// > effect, since this would imply keeping dependencies listed which can change the | |
// > behaviour of this flow or repeat it. | |
// | |
// Why are we not able to put our initialisation logic into an async function? | |
// | |
// Based on the example you've given we can create an async function to initialise | |
// the state and then pass that promise to a hook that creates loading and error | |
// states for us to get the same behaviour in fewer lines code. | |
// | |
// We don't need to create a dependency list at all if the initialisation step should | |
// only run once when the component mounts. | |
// | |
// I'm using `react-use` in this example but we could also use `react-query` or | |
// countless other helper libraries. | |
export function LogExplorerApp() { | |
const state = useAsync(async () => { | |
// First of all, sync the URL (async operation) | |
// Second, restore something from storage | |
return { | |
query: localStorage.getItem("query"), | |
anyOtherAppState: {}, | |
}; | |
}); | |
if (!state.value || state.loading) { | |
return <div>Spinner</div>; | |
} | |
return <div>Log Explorer (query: {state.value.query})</div>; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment