(Micro) State - leotm/react-native-template-new-architecture GitHub Wiki
aka client/UI state
React Concurrent Mode and Suspense (Nov19) Experimental
- Pub-sub
- store (shared/global)
- components (immutable React/JSX.element attribs/children)
- Reactive / unidirectional (top-down, bottom-up) / bidirectional
- Persistence (offline)
- Mutability
- Progressive Hydration (more)
- Memoising / React DOM re-rendering/updating
- Comparison/Reconciliation
- (Skipped) React lifecycle
- Code Structure
- Project specs/size
And so it begins...
- Read-Update's
- classy ๐ฉ old skool
- top-down
- global store obj
- predictable (restrictive) state mutations
- immutable state (emit actions of pure fn reducers)
- low boilerplate (RTK 18)
- redux-observable for RxJS
How well does Redux โscaleโ in terms of performance and architecture?โ
- while no single answer, most of the time shouldn't be a concern
- heavily optimised to cut down unnecessary re-renders
- not as efficient OOTB compared to other libs
- for max rendering perf in a React, minimising overall rendering needed
- store state normalised
- many individual components should be connected to the store instead of just a few
- connected list components should pass item IDs to their connected child list items (allowing the list items to look up their own data by ID)
- memoised selector fn's also an important perf consideration
for scale, we have ~500 action types, ~400 reducer cases, ~150 components, 5 middlewares, ~200 actions, ~2300 tests
Do I have to deep-clone my state in a reducer? Isn't copying my state going to be slow?
How can I reduce the number of store update events?
batch
public API is available to help minimize the number of React re-renders when dispatching actions outside of React event handlers. It wraps React's unstable_batchedUpdate() API, allows any React updates in an event loop tick to be batched together into a single render pass. React already uses this internally for its own event handler callbacks. This API is actually part of the renderer packages like ReactDOM and React Native, not the React core itself
Since React-Redux needs to work in both ReactDOM and React Native environments, we've taken care of importing this API from the correct renderer at build time for our own use
import { batch } from 'react-redux'
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}
Lodash for events
- reactive
- easier async/cb-based code
- observe (data streams), sub (cb), unsub (cb)
- not a state manager
- RN sensors example
- reactive
- mutable state
- observe (data values)
- data model close to view
- Why MobX-State-Tree?
https://github.com/RisingStack/react-easy-state
- deep Read-Update's
- some data changes rarely (e.g. user/auth, theme, locale) needs sharing to many deeply/shallow nested components
- subscribe to re-render changes (ignoring shouldComponentUpdate)
- consider 1st
{children}
and inversion of control (JSX props) - all consumers re-render ๐ฅ (comparing {} ref id w Object.is), split into small contexts of compound components (e.g. List > Item), or React.memo / useMemo / react-tracked / lifting value to parent state
ez business logic
- reactive
- immutable state
- reactive
- OOP
const ContextA = createContext({ something: 'initValue' })
export const Root = () =>
<ContextA.Provider value={{ something: 'newValue' }}>
<App />
</ContextA.Provider>
const DeepChild = () => {
const { something } = useContext(ContextA) // subscribe to consume (read)
return (
<Text>
{ something }
</Text>
)
}
- Read-Update's
- component UI state, render initial state/props and changes
- use Server State lib instead for fetching/loading/error state or GraphQL client
const Counter = ({ initialCount }) => {
const [count, setCount] = useState(initialCount)
return (
<>
Count: {count}
<Button onPress={() => setCount(initialCount)}>Reset</button>
<Button onPress={() => setCount(prevCount => prevCount - 1)}>-</button>
<Button onPress={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
)
}
- complex logic, multiple sub-values, nextState depends on prevState
const initialState = { count: 0 }
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
throw new Error()
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
Count: { state.count }
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
)
}
- large component trees
const TodosDispatch = createContext(null)
const TodosApp = () => {
// `dispatch` unchanged between re-renders
const [todos, dispatch] = useReducer(todosReducer)
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
)
}
const DeepChild = (props) => {
const dispatch = useContext(TodosDispatch)
return (
<button onClick={dispatch({ type: 'add', text: 'hello' })}>Add todo</button>
)
}
- top-down
- store/update outside React
- immutable state
- Redux devtools
- https://jotai.org/docs/basics/comparison#how-is-jotai-different-from-zustand
- https://github.com/pmndrs/zustand/wiki/Difference-between-zustand-and-valtio
- React 18 โ๏ธ
import React from 'react'
import create from 'zustand'
import CodePreview from './components/CodePreview'
import Backdrop from './components/Backdrop'
import Details from './components/Details'
import code from './resources/code'
const useStore = create((set) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
}))
function Counter() {
const { count, inc } = useStore()
return (
<div className="counter">
<span>{count}</span>
<button onClick={inc}>one up</button>
</div>
)
}
export default function App() {
return (
<>
<Backdrop />
<div className="main">
<div className="code">
<div className="code-container">
<CodePreview code={code} />
<Counter />
</div>
</div>
<Details />
</div>
</>
)
}
- bottom-up (atomic)
- within React, useState+useContext alternative
- if like Zustand
- if wanna try to create new lib
- code splitting
- React 18 โ๏ธ
https://jotai.org/docs/basics/comparison
- bottom-up (atomic)
- experimental
- full featured for big apps
- for apps heavily requiring state serialization (storing state in storage, server, or URL)
- https://jotai.org/docs/basics/comparison#how-is-jotai-different-from-recoil
- bidirectional
- mutable state
- https://github.com/pmndrs/zustand/wiki/Difference-between-zustand-and-valtio
- https://github.com/pmndrs/eslint-plugin-valtio
https://github.com/dai-shi/use-context-selector
- reactive
- 3kb, fast, ez
- global/local (1 store or atoms)
- persist
- React 18 โ๏ธ
- XState in React Native - How I finally managed to keep my views lean and my logic clean - March 2023
https://github.com/statelyai/xstate/tree/main/packages/xstate-fsm