This tutorial shows example code with useState, Immer and custom hooks.

JavaScript

TypeScript Copy import React from 'react' ; import { Provider } from '../store' ; import TodoList from './TodoList' ; const App = ( ) => ( < Provider > < TodoList / > < / Provider > ) ; export default App ;

This is the root component. It wraps TodoList with Provider.

JavaScript

TypeScript Copy import { useState , useCallback } from 'react' ; import { createContainer } from 'react-tracked' ; import produce from 'immer' ; const initialState = { todos : [ { id : 1 , title : 'Wash dishes' } , { id : 2 , title : 'Study JS' } , { id : 3 , title : 'Buy ticket' } , ] , query : '' , } ; const useValue = ( ) => useState ( initialState ) ; const { Provider , useTrackedState , useUpdate : useSetState } = createContainer ( useValue , ) ; const useSetDraft = ( ) => { const setState = useSetState ( ) ; return useCallback ( ( draftUpdater ) => { setState ( produce ( draftUpdater ) ) ; } , [ setState ] , ) ; } ; export { Provider , useTrackedState , useSetDraft } ;

The store is created by useState. useUpdate is renamed to useSetState, and based on it, useSetDraft with Immer is exported.

JavaScript

TypeScript Copy import { useTrackedState } from '../store' ; export const useTodoList = ( ) => { const state = useTrackedState ( ) ; return state . todos ; } ;

This is a custom hook to simply return todos .

JavaScript

TypeScript Copy import { useCallback } from 'react' ; import { useSetDraft } from '../store' ; let nextId = 100 ; export const useAddTodo = ( ) => { const setDraft = useSetDraft ( ) ; return useCallback ( ( title ) => { setDraft ( ( draft ) => { draft . todos . push ( { id : nextId ++ , title } ) ; } ) ; } , [ setDraft ] , ) ; } ;

This is a custom hook to return addTodo function.

JavaScript

TypeScript Copy import { useCallback } from 'react' ; import { useSetDraft } from '../store' ; export const useDeleteTodo = ( ) => { const setDraft = useSetDraft ( ) ; return useCallback ( ( id ) => { setDraft ( ( draft ) => { const index = draft . todos . findIndex ( ( todo ) => todo . id === id ) ; if ( index >= 0 ) draft . todos . splice ( index , 1 ) ; } ) ; } , [ setDraft ] , ) ; } ;

This is a custom hook to return deleteTodo function.

JavaScript

TypeScript Copy import { useCallback } from 'react' ; import { useSetDraft } from '../store' ; export const useToggleTodo = ( ) => { const setDraft = useSetDraft ( ) ; return useCallback ( ( id ) => { setDraft ( ( draft ) => { const todo = draft . todos . find ( ( todo ) => todo . id === id ) ; if ( todo ) todo . completed = ! todo . completed ; } ) ; } , [ setDraft ] , ) ; } ;

This is a custom hook to return toggleTodo function.

JavaScript

TypeScript Copy import { useCallback } from 'react' ; import { useTrackedState , useSetDraft } from '../store' ; export const useQuery = ( ) => { const state = useTrackedState ( ) ; const getQuery = ( ) => state . query ; const setDraft = useSetDraft ( ) ; const setQuery = useCallback ( ( query ) => { setDraft ( ( draft ) => { draft . query = query ; } ) ; } , [ setDraft ] , ) ; return { getQuery , setQuery } ; } ;

This is a custom hook to return getQuery and setQuery. It doesn't return state.query directly, because it will be used conditionally.

JavaScript

TypeScript Copy import React from 'react' ; import { useTodoList } from '../hooks/useTodoList' ; import { useQuery } from '../hooks/useQuery' ; import TodoItem from './TodoItem' ; import NewTodo from './NewTodo' ; const TodoList = ( ) => { const { getQuery , setQuery } = useQuery ( ) ; const todos = useTodoList ( ) ; return ( < div > < ul > { todos . map ( ( { id , title , completed } ) => ( < TodoItem key = { id } id = { id } title = { title } completed = { completed } / > ) ) } < NewTodo / > < / ul > < div > Highlight Query for incomplete items : < input value = { getQuery ( ) } onChange = { ( e ) => setQuery ( e . target . value ) } / > < / div > < / div > ) ; } ; export default TodoList ;

This component is to show the list of TodoItem s, NewTodo to create a new item, and Clear button to reset notes in all items.

JavaScript

TypeScript Copy import React from 'react' ; import { useQuery } from '../hooks/useQuery' ; import { useDeleteTodo } from '../hooks/useDeleteTodo' ; import { useToggleTodo } from '../hooks/useToggleTodo' ; import { useFlasher } from '../utils' ; const renderHighlight = ( title , query ) => { if ( ! query ) return title ; const index = title . indexOf ( query ) ; if ( index === - 1 ) return title ; return ( < > { title . slice ( 0 , index ) } < b > { query } < / b > { title . slice ( index + query . length ) } < / > ) ; } ; const TodoItem = ( { id , title , completed } ) => { const { getQuery } = useQuery ( ) ; const deleteTodo = useDeleteTodo ( ) ; const toggleTodo = useToggleTodo ( ) ; return ( < li ref = { useFlasher ( ) } > < input type = "checkbox" checked = { ! ! completed } onChange = { ( ) => toggleTodo ( id ) } / > < span style = { { textDecoration : completed ? 'line-through' : 'none' , } } > { completed ? title : renderHighlight ( title , getQuery ( ) ) } < / span > < button onClick = { ( ) => deleteTodo ( id ) } > Delete < / button > < / li > ) ; } ; export default React . memo ( TodoItem ) ;

This is the TodoItem component. We prefer primitive props for memoized components.

If you want to use object props for memoized components, you need to use a special memo instead of React.memo . See example/09 for the usage.

JavaScript

TypeScript Copy import React , { useState } from 'react' ; import { useAddTodo } from '../hooks/useAddTodo' ; import { useFlasher } from '../utils' ; const NewTodo = ( ) => { const addTodo = useAddTodo ( ) ; const [ text , setText ] = useState ( '' ) ; return ( < li ref = { useFlasher ( ) } > < input value = { text } placeholder = "Enter title..." onChange = { ( e ) => setText ( e . target . value ) } / > < button onClick = { ( ) => { addTodo ( text ) ; setText ( '' ) ; } } > Add < / button > < / li > ) ; } ; export default React . memo ( NewTodo ) ;

This is the NewTodo component to create a new item. It uses a local state for the text field.

JavaScript

TypeScript Copy import { useRef , useEffect } from 'react' ; export const useFlasher = ( ) => { const ref = useRef ( null ) ; useEffect ( ( ) => { if ( ! ref . current ) return ; ref . current . setAttribute ( 'style' , 'box-shadow: 0 0 2px 1px red; transition: box-shadow 100ms ease-out;' , ) ; setTimeout ( ( ) => { if ( ! ref . current ) return ; ref . current . setAttribute ( 'style' , '' ) ; } , 300 ) ; } ) ; return ref ; } ;

This is a util function to show which components render.

You can try working example.