Pass props vs useContext() In a project we may have multiple nested components Data should be passed between components, for example... Pass 'data in props from Parent' string through all components tree in props, which creates prop drilling issue If we have many variables it may produce a mess in code We can pass data in a smarter way Create Context variable outside components with Context = createContext() Wrap components in <Context.Provider value={value}> Passed value will be available in all children components value can be retrieved from the context with hook useContext(Context) Multiple contexts are allowed import { createContext, useContext, useState, useReducer } from "react" const style = { border: "1px solid grey", padding: "10px", margin: "10px" } const ContextParent = createContext() const ContextA = createContext() const ContextB = createContext() function Parent1() { return ( <ContextParent.Provider value={{ data: 'data in context from context Parent' }}> <A data='data in props from Parent' /> <Age /> </ContextParent.Provider> ) } function A(props) { return ( <ContextA.Provider value={'context A'}> <div style={style}> A <B data={props.data}/> </div> </ContextA.Provider> ) } function B(props) { return ( <ContextB.Provider value={'context B'}> <div style={style}> B <C data={props.data}/> </div> </ContextB.Provider> ) } function C(props) { return ( <div style={style}> C <D data={props.data}/> </div> ) } function D(props) { const contextParent = useContext(ContextParent) const contextA = useContext(ContextA) const contextB = useContext(ContextB) return ( <div style={style}> D <div>{props.data}</div> <div>{contextParent.data} & {contextA} & {contextB}</div> </div> ) } function Age() { const contextParent = useContext(ContextParent) return ( <div style={style}> I am data from {contextParent.data} </div> ) } Pass state in useContext() Same idea as above, but pass a state variable into <Context.Provider value={state}> import { createContext, useContext, useState, useReducer } from "react" import shortid from 'shortid' const Context2 = createContext(''); function Parent2() { const [string, setString] = useState(shortid()) const contextValue={ string, setString, style: { border: '2px solid grey', padding: '10px', margin: '10px' } } return ( <Context2.Provider value={contextValue}> <div style={contextValue.style}> <div>Parent</div> <div>String from context: <b>{contextValue.string}</b></div> <button onClick={() => contextValue.setString(shortid()) }>Change string</button> <Child2 name='Child 1'/> <Child2 name='Child 2'/> </div> </Context2.Provider> ) } function Child2(props) { const contextValue = useContext(Context2); return ( <div style={contextValue.style}> <div>{props.data}</div> <div>String from context: <b>{contextValue.string}</b></div> <button onClick={() => contextValue.setString(shortid()) }>Change string</button> </div> ) } useContext() with useReducer() Same as above, but instead of useState() hook useReducer() is used. import { createContext, useContext, useState, useReducer } from "react" import shortid from 'shortid' const Context3 = createContext(''); function reducer(state, action) { if (action === 'randomize string') return shortid() return state } function Parent3() { const [string, dispatch] = useReducer(reducer, shortid()) const contextValue={ string, dispatch, style: { border: '2px solid grey', padding: '10px', margin: '10px' } } return ( <Context3.Provider value={contextValue}> <div style={contextValue.style}> <div>Parent</div> <div>String from context: <b>{contextValue.string}</b></div> <button onClick={() => contextValue.dispatch('randomize string') }>Change string</button> <Child3 name='Child 1'/> <Child3 name='Child 2'/> </div> </Context3.Provider> ) } function Child3(props) { const contextValue = useContext(Context3); return ( <div style={contextValue.style}> <div>{props.data}</div> <div>String from context: <b>{contextValue.string}</b></div> <button onClick={() => contextValue.dispatch('randomize string') }>Change string</button> </div> ) } context in typescript // RowProvider.tsx import { createContext, useContext, type ReactNode } from 'react' type Context = { rowIndex: number rowId: string } type Props = Context & { children: ReactNode } const RowContext = createContext<Context | null>(null) export const RowProvider = ({ children, rowIndex, rowId, }: Props): JSX.Element => { return ( <RowContext.Provider value={{ rowIndex, rowId, }} > {children} </RowContext.Provider> ) } export const useRow = (): Context => { const context = useContext(RowContext) if (!context) { throw new Error('useRow must be used within a RowProvider') } return context } // ParentComponent.tsx import { BoqRow, boqRows } from './row/BoqRow' import { useItem } from 'client/widgets/items/ItemProvider' import { RowProvider } from './RowProvider' export const ParentComponent = (): JSX.Element => { return ( {boqRows.map((boqRow, rowIndex) => { if (boqRow.type === 'boq row') { return ( <RowProvider rowIndex={rowIndex} rowId={boqRow.id} key={boqRow.id} > <BoqRow /> </RowProvider> ) } })} ) } // ChildComponent.tsx import type { ReactNode } from 'react' import { useRow } from '../RowProvider' type Props = { children: ReactNode } export const ChildComponent = ({ children }: Props): JSX.Element => { const { rowId } = useRow() return ( <div id={rowId} > {children} </div> ) }