State Management in Next.js II.
What is Prop Drilling, Know It to Avoid
Prop drilling refers to the unofficial practice of delivering data to a deeply nested component by passing it through a number of nested children components via props. This method has the drawback that few of the components through which the data is delivered actually require the data. They merely serve as conduits for sending this info to its final destination.
The phrase "drilling" refers to this process of forcing these components to take in unrelated data and pass it on to the following component, which in turn sends it on to the next component, and so on until it reaches its destination. The reusability of the component may be severely impacted by that act.
Possible Solutions
Now that it is clear what we want to accomplish and the issues itself we are facing, we must investigate the possible solutions. An incomplete list of alternatives to the prop drilling technique is written below.
- React Context API, useState, useReducer (built in solutions)
- Redux
- React Query
- Zustand
- Jotai
- Mobx
Even though we are huge fans of Redux, we advise using the built-in solutions when state management and interactivity are required if our application is rather small or simple.
However, if the application requires more complex state management solutions, we advise you to use Redux.
Our article is not intended to present Redux in detail, but we intorduce the integration of it with Next.js.
React Context API vs Redux
- Context API
- Built-in tool that React includes (does not increase package size)
- Requires minimal setup
- Designed specifically for static, not updated or updated often
- New contexts The provider must be created from scratch every time
- User interface logic and state management logic are in the same component
- Debugging can be difficult in the case of an embedded structure, even with the help of the Dev Tool
- Redux
- Additional packages need to be installed, which increases the size of the final package
- Integrating with React requires extra setup
- Works like a charm with both static and dynamic data
- Easily expandable by easily adding new operations after initial setup
- Better code organization with separate user interface logic and state management logic
- Incredibly powerful Redux Dev Tools for debugging
Built in solutions Context Api. useState, useReducer
It is important to note that using providers require attention.
- They can create the so-called “provider hell” which is strikingly similar to “callback hell”. Here is a great article about how to avoid provider hell A kind piece of advice from a fellow developer, always be aware of the potential pitfalls when organizing and nesting your providers.
- Lots of developers are confused about Contexts and Redux, Contexts is not a replacement of Redux. They have some
similarities and overlap, but there are major differences in their capabilities. They are different tools that do
different things, and you use them for different purposes. It is merely a means of transportation; it does not "
manage" anything. You and your own code handle all "state management," usually using useState/useReducer.
// ... imports const App = () => { // ... some code return ( <> <ReduxProvider value={store}> <ThemeProvider value={theme}> <OtherProvider value={otherValue}> <OtherOtherProvider value={otherOtherValue}> {/** ... other providers*/} <HellProvider value={hell}> <HelloWorld /> </HellProvider> {/** ... other providers*/} </OtherOtherProvider> </OtherProvider> </ThemeProvider> </ReduxProvider> </> ) }
- Example of a Provider - Although useReducer is not used in the following example, it is a possibility for states that are more complex.
import {createContext, ReactNode, useContext, useState,} from "react"
type ThemeProviderProps = {
children: ReactNode
}
type ThemeContext = {
isPrimary: boolean
}
type ThemeDispatchContext = {
changeTheme: () => void
}
const ThemeContext = createContext({} as ThemeContext)
const ThemeDispatchContext = createContext({} as ThemeDispatchContext)
export function useTheme() {
return useContext(ThemeContext)
}
export function useDispatchTheme() {
return useContext(ThemeDispatchContext)
}
const ThemeProvider = ({children}: ThemeProviderProps) => {
const [isPrimary, setIsPrimary] = useState<boolean>(true)
const changeTheme = () => {
setIsPrimary(prevState => !prevState)
}
return (
<ThemeDispatchContext.Provider value={{changeTheme}}>
<ThemeContext.Provider value={{isPrimary}}>
<div
className={
isPrimary
? "themeable-primary-theme"
: "themeable-secondary-theme"
}
>
{children}
</div>
</ThemeContext.Provider>
</ThemeDispatchContext.Provider>
)
}
export {ThemeProvider}
The article continues here.