Skip to content

Instantly share code, notes, and snippets.

@thomheymann
Created December 13, 2023 11:46
Show Gist options
  • Save thomheymann/2eee203a3a8c7d10ebb4f0ca647704e5 to your computer and use it in GitHub Desktop.
Save thomheymann/2eee203a3a8c7d10ebb4f0ca647704e5 to your computer and use it in GitHub Desktop.
State management discussion
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.
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