I want to use react suspense.
The suspense behavior I want is to not show the fallback until a certain amount of time.
Using react with next works as intended. However, react alone flickers.
Why does this only work when using next ?
What's the difference?
After changing the react dom generation code, it worked as expected.
How can I do this in react-native as well?
Example
I made a simple todo app.
We made a 100ms delay to get the todos list with an asynchronous request.
recoil
import { selector, atom } from "recoil";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export const todosDelayState = selector({
key: "todosDelayState",
get: async ({ get }) => {
await sleep(100);
const todos = get(todosState);
return todos;
}
});
export const todosState = atom({
key: "todosState", // unique ID (with respect to other atoms/selectors)
default: [{ id: 0, text: "fasfasdf", done: false }] // default value (aka initial value)
});
Home
export default function Home({ accounts }) {
return (
<RecoilRoot>
<React.Suspense fallback={<span>Loading</span>}>
<Todo />
</React.Suspense>
</RecoilRoot>
);
}
Todo
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";
import React from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { todosDelayState, todosState } from "./todos";
function Todo() {
const [_, setTodos] = useRecoilState(todosState);
const todos = useRecoilValue(todosDelayState);
const onToggle = (id, done) => {
const _todos = todos.map((todo) => {
if (todo.id === id) {
return { ...todo, done: !done };
} else {
return todo;
}
});
setTodos(_todos);
};
const onRemove = (id) => {
const _todos = todos.filter((todo) => {
return todo.id === id ? false : true;
});
setTodos(_todos);
};
const onInsert = (value) => {
const id = todos.length === 0 ? 1 : todos[todos.length - 1].id + 1;
const todo = {
id,
text: value,
done: false
};
const _todos = todos.concat([todo]);
setTodos(_todos);
};
return (
<React.Fragment>
<TodoForm onInsert={onInsert} />
<TodoList todos={todos} onToggle={onToggle} onRemove={onRemove} />
</React.Fragment>
);
}
export default Todo;
1. Sandbox with next, react 18, recoil
In case it behaves exactly as I expected.
2. Sandbox with react 18, recoil
~~blinks~~ --> nice work
After changing the react dom generation code, it worked as expected.
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
// const rootElement = document.getElementById("root");
// ReactDOM.render(
// <StrictMode>
// <App />
// </StrictMode>,
// rootElement
// );
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(<App />);
3. Expo with react-native, react, recoil
~~blinks~~ -> work
Unlike react-dom, react-native could not find a code that directly affects it.
So, I made a custom hook, Suspense, and ErrorBoundary using the loadable of recoil.
export const useCustomRecoilValue = (state, initialValue) => {
const data = useRecoilValueLoadable(state)
const prevData = useRef(initialValue)
const _data = useMemo(() => {
if (data.state === 'hasValue') {
prevData.current = data.contents
return [data.contents, false, undefined]
} else if (data.state === 'hasError') {
return [prevData.current, false, data.contents]
} else {
return [prevData.current, true, undefined]
}
}, [data])
return _data
}
// const todos = useRecoilValue(todosDelayState)
const [todos, loading, error] = useCustomRecoilValue(todosDelayState, [])
// return (
// <VStack space={8} w="100%">
// <TodoForm onInsert={onInsert} />
// <TodoList todos={todos} onToggle={onToggle} onRemove={onRemove} />
// </VStack>
// )
return (
<VStack space={8} w="100%">
<TodoForm onInsert={onInsert} />
<ErrorBoundary error={error} fallback={<Box>Error</Box>}>
<Suspense
delay={250}
loading={loading}
fallback={<Box>Loading...</Box>}>
<TodoList todos={todos} onToggle={onToggle} onRemove={onRemove} />
</Suspense>
</ErrorBoundary>
</VStack>
)