Skip to main content

Toolchain

React

  • useSelector.
  • useDispatch: dispatch function reference will be stable as long as same store instance is being passed to the <Provider>.

Types

import type { TypedUseSelectorHook } from 'react-redux'
import type store from './store'
import { useDispatch, useSelector } from 'react-redux'

type AppDispatch = typeof store.dispatch
type RootState = ReturnType<typeof store.getState>

export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

Hooks

import { shallowEqual, useSelector } from 'react-redux'

export default function useShallowEqualSelector(selector) {
return useSelector(selector, shallowEqual)
}
import { useMemo } from 'react'
import { useDispatch } from 'react-redux'
import { bindActionCreators } from 'redux'

export default function useActions(actions) {
const dispatch = useDispatch()

return useMemo(() => {
if (Array.isArray(actions))
return actions.map(a => bindActionCreators(a, dispatch))

return bindActionCreators(actions, dispatch)
}, [actions, dispatch])
}

APIs

batch:

import { batch } from 'react-redux'

function myThunk() {
return (dispatch, getState) => {
// Only result in one combined re-render, not two.
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}

SSR

  • Client side: a new Redux store will be created with state provided from server.
  • Server side: provide the initial state of app.

client.jsx:

import { hydrateRoot } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './containers/App'
import counterApp from './reducers'

const preloadedState = window.__PRELOADED_STATE__

delete window.__PRELOADED_STATE__

const store = createStore(counterApp, preloadedState)

hydrateRoot(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

server.js:

import path from 'node:path'
import Express from 'express'
import qs from 'qs'
import { renderToString } from 'react-dom/server'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './containers/App'
import counterApp from './reducers'

const app = Express()
const port = 3000

app.use('/static', Express.static('static'))

app.use(handleRender)

function handleRender(req, res) {
// `parseInt` to prevent XSS attack
const params = qs.parse(req.query)
const counter = Number.parseInt(params.counter, 10) || 0

const preloadedState = { counter }
const store = createStore(counterApp, preloadedState)

const html = renderToString(
<Provider store={store}>
<App />
</Provider>
)

const finalState = store.getState()
res.send(renderFullPage(html, finalState))
}

function renderFullPage(html, preloadedState) {
// https://redux.js.org/usage/server-rendering#security-considerations
// `replace(/</g, '\\u003c')` to prevent XSS attack
return `
<!doctype html>
<html>
<head>
<title>Redux Universal Example</title>
</head>
<body>
<div id="root">${html}</div>
<script>
// WARNING: security issues around embedding JSON in HTML:
// https://redux.js.org/usage/server-rendering#security-considerations
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(
/</g,
'\\u003c'
)}
</script>
<script src="/static/bundle.js"></script>
</body>
</html>
`
}

app.listen(port)

Library

Immutable

  • Immer: Create next immutable state by mutating current one.
  • Immutable.js: Immutable persistent data collections.

Middleware

State

  • Reselect: Memorize state transformation.

Debugging

  • Devtools: Hot reloading and action replay.