[{"data":1,"prerenderedAt":1879},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-zustand-guide":3},{"id":4,"title":5,"body":6,"description":1865,"difficulty":1866,"extension":1867,"framework":1868,"frameworkSlug":1869,"meta":1870,"navigation":95,"order":92,"path":1871,"qaPath":1872,"seo":1873,"stem":1874,"subtopic":32,"topic":1875,"topicSlug":1876,"updated":1877,"__hash__":1878},"blog\u002Fblog\u002Freact-zustand-guide.md","Zustand for React — Complete Interview Guide",{"type":7,"value":8,"toc":1849},"minimark",[9,14,27,34,42,46,61,339,360,363,459,470,474,489,562,579,689,695,699,713,915,918,1064,1075,1079,1085,1093,1237,1243,1250,1411,1417,1424,1623,1629,1633,1636,1758,1764,1770,1776,1779,1783,1793,1808,1814,1830,1839,1845],[10,11,13],"h2",{"id":12},"why-zustand-has-taken-over-react-state-management","Why Zustand has taken over React state management",[15,16,17,18,22,23,26],"p",{},"Redux was the default answer to global state for years, but its ceremony was\nalways a pain point. For every new feature you'd write an action type constant,\nan action creator, a case in a reducer, and then wire up ",[19,20,21],"code",{},"mapStateToProps"," or a\n",[19,24,25],{},"useSelector",". Redux Toolkit trimmed that surface, but the mental model still\ninvolves a separate store, slices, and the Redux DevTools ritual.",[15,28,29,33],{},[30,31,32],"strong",{},"Zustand"," (German for \"state\") arrived with a radically simpler bet: what if\nthe store were just a hook, and actions were just functions? The result is a\nlibrary that weighs about 1 kB, ships no Provider, requires no boilerplate, and\nstays completely out of your way until you need advanced features.",[15,35,36,37,41],{},"As of 2025, Zustand is the most-downloaded React state management library after\nRedux Toolkit, appearing in interview questions at every level from junior to\nprincipal. Knowing it well means understanding not just the API, but ",[38,39,40],"em",{},"why"," it\nmakes the trade-offs it does.",[10,43,45],{"id":44},"creating-your-first-store","Creating Your First Store",[15,47,48,49,52,53,56,57,60],{},"Everything starts with ",[19,50,51],{},"create()",". You pass it a callback that receives ",[19,54,55],{},"set","\n(and optionally ",[19,58,59],{},"get",") and returns an object containing both state fields and\nthe actions that mutate them — there's no separate reducers file.",[62,63,68],"pre",{"className":64,"code":65,"language":66,"meta":67,"style":67},"language-js shiki shiki-themes github-light github-dark","import { create } from 'zustand'\n\nconst useBearStore = create((set, get) => ({\n  \u002F\u002F ── State ──────────────────────────────────────────────────────────────\n  bears: 0,\n  honey: 100,\n\n  \u002F\u002F ── Actions ────────────────────────────────────────────────────────────\n  \u002F\u002F Updater function form — use when new value depends on current value\n  addBear: () => set((state) => ({ bears: state.bears + 1 })),\n\n  \u002F\u002F Partial object form — fine when value is independent\n  removeAllBears: () => set({ bears: 0 }),\n\n  \u002F\u002F get() reads current state without subscribing to it\n  eatHoney: (amount) => {\n    const { honey } = get()\n    if (honey >= amount) set({ honey: honey - amount })\n  },\n}))\n","js","",[19,69,70,90,97,134,141,153,164,169,175,181,216,221,227,247,252,258,277,301,327,333],{"__ignoreMap":67},[71,72,75,79,83,86],"span",{"class":73,"line":74},"line",1,[71,76,78],{"class":77},"szBVR","import",[71,80,82],{"class":81},"sVt8B"," { create } ",[71,84,85],{"class":77},"from",[71,87,89],{"class":88},"sZZnC"," 'zustand'\n",[71,91,93],{"class":73,"line":92},2,[71,94,96],{"emptyLinePlaceholder":95},true,"\n",[71,98,100,103,107,110,114,117,120,123,125,128,131],{"class":73,"line":99},3,[71,101,102],{"class":77},"const",[71,104,106],{"class":105},"sj4cs"," useBearStore",[71,108,109],{"class":77}," =",[71,111,113],{"class":112},"sScJk"," create",[71,115,116],{"class":81},"((",[71,118,55],{"class":119},"s4XuR",[71,121,122],{"class":81},", ",[71,124,59],{"class":119},[71,126,127],{"class":81},") ",[71,129,130],{"class":77},"=>",[71,132,133],{"class":81}," ({\n",[71,135,137],{"class":73,"line":136},4,[71,138,140],{"class":139},"sJ8bj","  \u002F\u002F ── State ──────────────────────────────────────────────────────────────\n",[71,142,144,147,150],{"class":73,"line":143},5,[71,145,146],{"class":81},"  bears: ",[71,148,149],{"class":105},"0",[71,151,152],{"class":81},",\n",[71,154,156,159,162],{"class":73,"line":155},6,[71,157,158],{"class":81},"  honey: ",[71,160,161],{"class":105},"100",[71,163,152],{"class":81},[71,165,167],{"class":73,"line":166},7,[71,168,96],{"emptyLinePlaceholder":95},[71,170,172],{"class":73,"line":171},8,[71,173,174],{"class":139},"  \u002F\u002F ── Actions ────────────────────────────────────────────────────────────\n",[71,176,178],{"class":73,"line":177},9,[71,179,180],{"class":139},"  \u002F\u002F Updater function form — use when new value depends on current value\n",[71,182,184,187,190,192,195,197,200,202,204,207,210,213],{"class":73,"line":183},10,[71,185,186],{"class":112},"  addBear",[71,188,189],{"class":81},": () ",[71,191,130],{"class":77},[71,193,194],{"class":112}," set",[71,196,116],{"class":81},[71,198,199],{"class":119},"state",[71,201,127],{"class":81},[71,203,130],{"class":77},[71,205,206],{"class":81}," ({ bears: state.bears ",[71,208,209],{"class":77},"+",[71,211,212],{"class":105}," 1",[71,214,215],{"class":81}," })),\n",[71,217,219],{"class":73,"line":218},11,[71,220,96],{"emptyLinePlaceholder":95},[71,222,224],{"class":73,"line":223},12,[71,225,226],{"class":139},"  \u002F\u002F Partial object form — fine when value is independent\n",[71,228,230,233,235,237,239,242,244],{"class":73,"line":229},13,[71,231,232],{"class":112},"  removeAllBears",[71,234,189],{"class":81},[71,236,130],{"class":77},[71,238,194],{"class":112},[71,240,241],{"class":81},"({ bears: ",[71,243,149],{"class":105},[71,245,246],{"class":81}," }),\n",[71,248,250],{"class":73,"line":249},14,[71,251,96],{"emptyLinePlaceholder":95},[71,253,255],{"class":73,"line":254},15,[71,256,257],{"class":139},"  \u002F\u002F get() reads current state without subscribing to it\n",[71,259,261,264,267,270,272,274],{"class":73,"line":260},16,[71,262,263],{"class":112},"  eatHoney",[71,265,266],{"class":81},": (",[71,268,269],{"class":119},"amount",[71,271,127],{"class":81},[71,273,130],{"class":77},[71,275,276],{"class":81}," {\n",[71,278,280,283,286,289,292,295,298],{"class":73,"line":279},17,[71,281,282],{"class":77},"    const",[71,284,285],{"class":81}," { ",[71,287,288],{"class":105},"honey",[71,290,291],{"class":81}," } ",[71,293,294],{"class":77},"=",[71,296,297],{"class":112}," get",[71,299,300],{"class":81},"()\n",[71,302,304,307,310,313,316,318,321,324],{"class":73,"line":303},18,[71,305,306],{"class":77},"    if",[71,308,309],{"class":81}," (honey ",[71,311,312],{"class":77},">=",[71,314,315],{"class":81}," amount) ",[71,317,55],{"class":112},[71,319,320],{"class":81},"({ honey: honey ",[71,322,323],{"class":77},"-",[71,325,326],{"class":81}," amount })\n",[71,328,330],{"class":73,"line":329},19,[71,331,332],{"class":81},"  },\n",[71,334,336],{"class":73,"line":335},20,[71,337,338],{"class":81},"}))\n",[15,340,341,343,344,347,348,351,352,355,356,359],{},[19,342,55],{}," does a ",[30,345,346],{},"shallow merge"," — you only include the keys you want to change,\nsimilar to ",[19,349,350],{},"setState"," in class components. Pass ",[19,353,354],{},"true"," as the second argument\n(",[19,357,358],{},"set(fn, true)",") when you want a full replacement instead.",[15,361,362],{},"Consuming the store is a single hook call:",[62,364,368],{"className":365,"code":366,"language":367,"meta":67,"style":67},"language-jsx shiki shiki-themes github-light github-dark","function Bears() {\n  const bears    = useBearStore((s) => s.bears)\n  const addBear  = useBearStore((s) => s.addBear)\n  return \u003Cbutton onClick={addBear}>{bears} bears\u003C\u002Fbutton>\n}\n","jsx",[19,369,370,381,406,429,454],{"__ignoreMap":67},[71,371,372,375,378],{"class":73,"line":74},[71,373,374],{"class":77},"function",[71,376,377],{"class":112}," Bears",[71,379,380],{"class":81},"() {\n",[71,382,383,386,389,392,394,396,399,401,403],{"class":73,"line":92},[71,384,385],{"class":77},"  const",[71,387,388],{"class":105}," bears",[71,390,391],{"class":77},"    =",[71,393,106],{"class":112},[71,395,116],{"class":81},[71,397,398],{"class":119},"s",[71,400,127],{"class":81},[71,402,130],{"class":77},[71,404,405],{"class":81}," s.bears)\n",[71,407,408,410,413,416,418,420,422,424,426],{"class":73,"line":99},[71,409,385],{"class":77},[71,411,412],{"class":105}," addBear",[71,414,415],{"class":77},"  =",[71,417,106],{"class":112},[71,419,116],{"class":81},[71,421,398],{"class":119},[71,423,127],{"class":81},[71,425,130],{"class":77},[71,427,428],{"class":81}," s.addBear)\n",[71,430,431,434,437,441,444,446,449,451],{"class":73,"line":136},[71,432,433],{"class":77},"  return",[71,435,436],{"class":81}," \u003C",[71,438,440],{"class":439},"s9eBZ","button",[71,442,443],{"class":112}," onClick",[71,445,294],{"class":77},[71,447,448],{"class":81},"{addBear}>{bears} bears\u003C\u002F",[71,450,440],{"class":439},[71,452,453],{"class":81},">\n",[71,455,456],{"class":73,"line":143},[71,457,458],{"class":81},"}\n",[15,460,461,462,465,466,469],{},"No ",[19,463,464],{},"Provider"," wraps ",[19,467,468],{},"Bears",". Zustand stores state outside the React tree, so\nany component anywhere in the app can subscribe.",[10,471,473],{"id":472},"selectors-and-re-render-optimization","Selectors and Re-render Optimization",[15,475,476,477,480,481,484,485,488],{},"The function you pass to ",[19,478,479],{},"useBearStore()"," is a ",[30,482,483],{},"selector",". It determines what\nthe component subscribes to. Zustand compares the selector's return value\nbetween renders using ",[19,486,487],{},"Object.is"," — if it's unchanged, the component doesn't\nre-render.",[62,490,492],{"className":365,"code":491,"language":367,"meta":67,"style":67},"\u002F\u002F ❌ No selector — subscribes to the entire store object.\n\u002F\u002F    Re-renders whenever bears OR honey changes.\nconst state = useBearStore()\nconst bears = state.bears\n\n\u002F\u002F ✅ Selector — subscribes only to bears.\n\u002F\u002F    Re-renders only when bears changes.\nconst bears = useBearStore((s) => s.bears)\n",[19,493,494,499,504,517,528,532,537,542],{"__ignoreMap":67},[71,495,496],{"class":73,"line":74},[71,497,498],{"class":139},"\u002F\u002F ❌ No selector — subscribes to the entire store object.\n",[71,500,501],{"class":73,"line":92},[71,502,503],{"class":139},"\u002F\u002F    Re-renders whenever bears OR honey changes.\n",[71,505,506,508,511,513,515],{"class":73,"line":99},[71,507,102],{"class":77},[71,509,510],{"class":105}," state",[71,512,109],{"class":77},[71,514,106],{"class":112},[71,516,300],{"class":81},[71,518,519,521,523,525],{"class":73,"line":136},[71,520,102],{"class":77},[71,522,388],{"class":105},[71,524,109],{"class":77},[71,526,527],{"class":81}," state.bears\n",[71,529,530],{"class":73,"line":143},[71,531,96],{"emptyLinePlaceholder":95},[71,533,534],{"class":73,"line":155},[71,535,536],{"class":139},"\u002F\u002F ✅ Selector — subscribes only to bears.\n",[71,538,539],{"class":73,"line":166},[71,540,541],{"class":139},"\u002F\u002F    Re-renders only when bears changes.\n",[71,543,544,546,548,550,552,554,556,558,560],{"class":73,"line":171},[71,545,102],{"class":77},[71,547,388],{"class":105},[71,549,109],{"class":77},[71,551,106],{"class":112},[71,553,116],{"class":81},[71,555,398],{"class":119},[71,557,127],{"class":81},[71,559,130],{"class":77},[71,561,405],{"class":81},[15,563,564,566,567,570,571,574,575,578],{},[19,565,487],{}," works correctly for primitives and stable object references but\nfails when your selector constructs a ",[30,568,569],{},"new object or array"," on every call.\nThat's where ",[19,572,573],{},"shallow"," from ",[19,576,577],{},"zustand\u002Fshallow"," comes in:",[62,580,582],{"className":64,"code":581,"language":66,"meta":67,"style":67},"import { shallow } from 'zustand\u002Fshallow'\n\n\u002F\u002F Without shallow: new object literal every call → always re-renders\nconst { bears, honey } = useBearStore((s) => ({ bears: s.bears, honey: s.honey }))\n\n\u002F\u002F With shallow: compares each key individually → only re-renders on real changes\nconst { bears, honey } = useBearStore(\n  (s) => ({ bears: s.bears, honey: s.honey }),\n  shallow,\n)\n",[19,583,584,596,600,605,635,639,644,665,679,684],{"__ignoreMap":67},[71,585,586,588,591,593],{"class":73,"line":74},[71,587,78],{"class":77},[71,589,590],{"class":81}," { shallow } ",[71,592,85],{"class":77},[71,594,595],{"class":88}," 'zustand\u002Fshallow'\n",[71,597,598],{"class":73,"line":92},[71,599,96],{"emptyLinePlaceholder":95},[71,601,602],{"class":73,"line":99},[71,603,604],{"class":139},"\u002F\u002F Without shallow: new object literal every call → always re-renders\n",[71,606,607,609,611,614,616,618,620,622,624,626,628,630,632],{"class":73,"line":136},[71,608,102],{"class":77},[71,610,285],{"class":81},[71,612,613],{"class":105},"bears",[71,615,122],{"class":81},[71,617,288],{"class":105},[71,619,291],{"class":81},[71,621,294],{"class":77},[71,623,106],{"class":112},[71,625,116],{"class":81},[71,627,398],{"class":119},[71,629,127],{"class":81},[71,631,130],{"class":77},[71,633,634],{"class":81}," ({ bears: s.bears, honey: s.honey }))\n",[71,636,637],{"class":73,"line":143},[71,638,96],{"emptyLinePlaceholder":95},[71,640,641],{"class":73,"line":155},[71,642,643],{"class":139},"\u002F\u002F With shallow: compares each key individually → only re-renders on real changes\n",[71,645,646,648,650,652,654,656,658,660,662],{"class":73,"line":166},[71,647,102],{"class":77},[71,649,285],{"class":81},[71,651,613],{"class":105},[71,653,122],{"class":81},[71,655,288],{"class":105},[71,657,291],{"class":81},[71,659,294],{"class":77},[71,661,106],{"class":112},[71,663,664],{"class":81},"(\n",[71,666,667,670,672,674,676],{"class":73,"line":171},[71,668,669],{"class":81},"  (",[71,671,398],{"class":119},[71,673,127],{"class":81},[71,675,130],{"class":77},[71,677,678],{"class":81}," ({ bears: s.bears, honey: s.honey }),\n",[71,680,681],{"class":73,"line":177},[71,682,683],{"class":81},"  shallow,\n",[71,685,686],{"class":73,"line":183},[71,687,688],{"class":81},")\n",[15,690,691,692,694],{},"The rule is simple: use ",[19,693,573],{}," whenever your selector returns an object or\narray; skip it when it returns a single value.",[10,696,698],{"id":697},"async-actions","Async Actions",[15,700,701,702,705,706,709,710,712],{},"This is one of the most-asked interview topics because Zustand's answer is\nsurprisingly elegant: ",[30,703,704],{},"you don't need middleware",". Actions are plain\nfunctions, so they can be ",[19,707,708],{},"async",". Call ",[19,711,55],{}," as many times as you like inside\nthe function — once for loading, once for success, once for error.",[62,714,716],{"className":64,"code":715,"language":66,"meta":67,"style":67},"const usePostsStore = create((set) => ({\n  posts:   [],\n  loading: false,\n  error:   null,\n\n  fetchPosts: async () => {\n    set({ loading: true, error: null })        \u002F\u002F 1. show spinner\n\n    try {\n      const res  = await fetch('\u002Fapi\u002Fposts')\n      const data = await res.json()\n      set({ posts: data, loading: false })     \u002F\u002F 2. success\n    } catch (err) {\n      set({ error: err.message, loading: false }) \u002F\u002F 3. failure\n    }\n  },\n}))\n",[19,717,718,739,744,754,764,768,785,806,810,817,841,860,876,887,902,907,911],{"__ignoreMap":67},[71,719,720,722,725,727,729,731,733,735,737],{"class":73,"line":74},[71,721,102],{"class":77},[71,723,724],{"class":105}," usePostsStore",[71,726,109],{"class":77},[71,728,113],{"class":112},[71,730,116],{"class":81},[71,732,55],{"class":119},[71,734,127],{"class":81},[71,736,130],{"class":77},[71,738,133],{"class":81},[71,740,741],{"class":73,"line":92},[71,742,743],{"class":81},"  posts:   [],\n",[71,745,746,749,752],{"class":73,"line":99},[71,747,748],{"class":81},"  loading: ",[71,750,751],{"class":105},"false",[71,753,152],{"class":81},[71,755,756,759,762],{"class":73,"line":136},[71,757,758],{"class":81},"  error:   ",[71,760,761],{"class":105},"null",[71,763,152],{"class":81},[71,765,766],{"class":73,"line":143},[71,767,96],{"emptyLinePlaceholder":95},[71,769,770,773,776,778,781,783],{"class":73,"line":155},[71,771,772],{"class":112},"  fetchPosts",[71,774,775],{"class":81},": ",[71,777,708],{"class":77},[71,779,780],{"class":81}," () ",[71,782,130],{"class":77},[71,784,276],{"class":81},[71,786,787,790,793,795,798,800,803],{"class":73,"line":166},[71,788,789],{"class":112},"    set",[71,791,792],{"class":81},"({ loading: ",[71,794,354],{"class":105},[71,796,797],{"class":81},", error: ",[71,799,761],{"class":105},[71,801,802],{"class":81}," })        ",[71,804,805],{"class":139},"\u002F\u002F 1. show spinner\n",[71,807,808],{"class":73,"line":171},[71,809,96],{"emptyLinePlaceholder":95},[71,811,812,815],{"class":73,"line":177},[71,813,814],{"class":77},"    try",[71,816,276],{"class":81},[71,818,819,822,825,827,830,833,836,839],{"class":73,"line":183},[71,820,821],{"class":77},"      const",[71,823,824],{"class":105}," res",[71,826,415],{"class":77},[71,828,829],{"class":77}," await",[71,831,832],{"class":112}," fetch",[71,834,835],{"class":81},"(",[71,837,838],{"class":88},"'\u002Fapi\u002Fposts'",[71,840,688],{"class":81},[71,842,843,845,848,850,852,855,858],{"class":73,"line":218},[71,844,821],{"class":77},[71,846,847],{"class":105}," data",[71,849,109],{"class":77},[71,851,829],{"class":77},[71,853,854],{"class":81}," res.",[71,856,857],{"class":112},"json",[71,859,300],{"class":81},[71,861,862,865,868,870,873],{"class":73,"line":223},[71,863,864],{"class":112},"      set",[71,866,867],{"class":81},"({ posts: data, loading: ",[71,869,751],{"class":105},[71,871,872],{"class":81}," })     ",[71,874,875],{"class":139},"\u002F\u002F 2. success\n",[71,877,878,881,884],{"class":73,"line":229},[71,879,880],{"class":81},"    } ",[71,882,883],{"class":77},"catch",[71,885,886],{"class":81}," (err) {\n",[71,888,889,891,894,896,899],{"class":73,"line":249},[71,890,864],{"class":112},[71,892,893],{"class":81},"({ error: err.message, loading: ",[71,895,751],{"class":105},[71,897,898],{"class":81}," }) ",[71,900,901],{"class":139},"\u002F\u002F 3. failure\n",[71,903,904],{"class":73,"line":254},[71,905,906],{"class":81},"    }\n",[71,908,909],{"class":73,"line":260},[71,910,332],{"class":81},[71,912,913],{"class":73,"line":279},[71,914,338],{"class":81},[15,916,917],{},"Consuming it is as natural as any other action:",[62,919,921],{"className":365,"code":920,"language":367,"meta":67,"style":67},"function PostList() {\n  const { posts, loading, fetchPosts } = usePostsStore()\n\n  useEffect(() => {\n    fetchPosts()\n  }, [fetchPosts]) \u002F\u002F fetchPosts has a stable identity — safe in deps\n\n  if (loading) return \u003Cp>Loading…\u003C\u002Fp>\n  return \u003Cul>{posts.map(p => \u003Cli key={p.id}>{p.title}\u003C\u002Fli>)}\u003C\u002Ful>\n}\n",[19,922,923,932,959,963,975,982,990,994,1016,1060],{"__ignoreMap":67},[71,924,925,927,930],{"class":73,"line":74},[71,926,374],{"class":77},[71,928,929],{"class":112}," PostList",[71,931,380],{"class":81},[71,933,934,936,938,941,943,946,948,951,953,955,957],{"class":73,"line":92},[71,935,385],{"class":77},[71,937,285],{"class":81},[71,939,940],{"class":105},"posts",[71,942,122],{"class":81},[71,944,945],{"class":105},"loading",[71,947,122],{"class":81},[71,949,950],{"class":105},"fetchPosts",[71,952,291],{"class":81},[71,954,294],{"class":77},[71,956,724],{"class":112},[71,958,300],{"class":81},[71,960,961],{"class":73,"line":99},[71,962,96],{"emptyLinePlaceholder":95},[71,964,965,968,971,973],{"class":73,"line":136},[71,966,967],{"class":112},"  useEffect",[71,969,970],{"class":81},"(() ",[71,972,130],{"class":77},[71,974,276],{"class":81},[71,976,977,980],{"class":73,"line":143},[71,978,979],{"class":112},"    fetchPosts",[71,981,300],{"class":81},[71,983,984,987],{"class":73,"line":155},[71,985,986],{"class":81},"  }, [fetchPosts]) ",[71,988,989],{"class":139},"\u002F\u002F fetchPosts has a stable identity — safe in deps\n",[71,991,992],{"class":73,"line":166},[71,993,96],{"emptyLinePlaceholder":95},[71,995,996,999,1002,1005,1007,1009,1012,1014],{"class":73,"line":171},[71,997,998],{"class":77},"  if",[71,1000,1001],{"class":81}," (loading) ",[71,1003,1004],{"class":77},"return",[71,1006,436],{"class":81},[71,1008,15],{"class":439},[71,1010,1011],{"class":81},">Loading…\u003C\u002F",[71,1013,15],{"class":439},[71,1015,453],{"class":81},[71,1017,1018,1020,1022,1025,1028,1031,1033,1035,1038,1040,1043,1046,1048,1051,1053,1056,1058],{"class":73,"line":177},[71,1019,433],{"class":77},[71,1021,436],{"class":81},[71,1023,1024],{"class":439},"ul",[71,1026,1027],{"class":81},">{posts.",[71,1029,1030],{"class":112},"map",[71,1032,835],{"class":81},[71,1034,15],{"class":119},[71,1036,1037],{"class":77}," =>",[71,1039,436],{"class":81},[71,1041,1042],{"class":439},"li",[71,1044,1045],{"class":112}," key",[71,1047,294],{"class":77},[71,1049,1050],{"class":81},"{p.id}>{p.title}\u003C\u002F",[71,1052,1042],{"class":439},[71,1054,1055],{"class":81},">)}\u003C\u002F",[71,1057,1024],{"class":439},[71,1059,453],{"class":81},[71,1061,1062],{"class":73,"line":183},[71,1063,458],{"class":81},[15,1065,1066,1067,1070,1071,1074],{},"Compare this to Redux, where async work requires installing ",[19,1068,1069],{},"redux-thunk"," (or\n",[19,1072,1073],{},"redux-saga",") and following a specific action-dispatching protocol. Zustand's\nasync story requires zero extra packages.",[10,1076,1078],{"id":1077},"useful-middleware","Useful Middleware",[15,1080,1081,1082,1084],{},"Zustand ships three middleware utilities that cover the most common production\nneeds. Each is a wrapper — you compose them around your ",[19,1083,51],{}," callback.",[1086,1087,1089,1092],"h3",{"id":1088},"devtools-redux-devtools-integration",[19,1090,1091],{},"devtools"," — Redux DevTools integration",[62,1094,1096],{"className":64,"code":1095,"language":66,"meta":67,"style":67},"import { devtools } from 'zustand\u002Fmiddleware'\n\nconst useStore = create(\n  devtools(\n    (set) => ({\n      count: 0,\n      \u002F\u002F Third argument to set names the action in the DevTools log\n      increment: () => set((s) => ({ count: s.count + 1 }), false, 'counter\u002Fincrement'),\n    }),\n    { name: 'AppStore', enabled: process.env.NODE_ENV !== 'production' },\n  ),\n)\n",[19,1097,1098,1110,1114,1127,1134,1147,1156,1161,1200,1205,1228,1233],{"__ignoreMap":67},[71,1099,1100,1102,1105,1107],{"class":73,"line":74},[71,1101,78],{"class":77},[71,1103,1104],{"class":81}," { devtools } ",[71,1106,85],{"class":77},[71,1108,1109],{"class":88}," 'zustand\u002Fmiddleware'\n",[71,1111,1112],{"class":73,"line":92},[71,1113,96],{"emptyLinePlaceholder":95},[71,1115,1116,1118,1121,1123,1125],{"class":73,"line":99},[71,1117,102],{"class":77},[71,1119,1120],{"class":105}," useStore",[71,1122,109],{"class":77},[71,1124,113],{"class":112},[71,1126,664],{"class":81},[71,1128,1129,1132],{"class":73,"line":136},[71,1130,1131],{"class":112},"  devtools",[71,1133,664],{"class":81},[71,1135,1136,1139,1141,1143,1145],{"class":73,"line":143},[71,1137,1138],{"class":81},"    (",[71,1140,55],{"class":119},[71,1142,127],{"class":81},[71,1144,130],{"class":77},[71,1146,133],{"class":81},[71,1148,1149,1152,1154],{"class":73,"line":155},[71,1150,1151],{"class":81},"      count: ",[71,1153,149],{"class":105},[71,1155,152],{"class":81},[71,1157,1158],{"class":73,"line":166},[71,1159,1160],{"class":139},"      \u002F\u002F Third argument to set names the action in the DevTools log\n",[71,1162,1163,1166,1168,1170,1172,1174,1176,1178,1180,1183,1185,1187,1190,1192,1194,1197],{"class":73,"line":171},[71,1164,1165],{"class":112},"      increment",[71,1167,189],{"class":81},[71,1169,130],{"class":77},[71,1171,194],{"class":112},[71,1173,116],{"class":81},[71,1175,398],{"class":119},[71,1177,127],{"class":81},[71,1179,130],{"class":77},[71,1181,1182],{"class":81}," ({ count: s.count ",[71,1184,209],{"class":77},[71,1186,212],{"class":105},[71,1188,1189],{"class":81}," }), ",[71,1191,751],{"class":105},[71,1193,122],{"class":81},[71,1195,1196],{"class":88},"'counter\u002Fincrement'",[71,1198,1199],{"class":81},"),\n",[71,1201,1202],{"class":73,"line":177},[71,1203,1204],{"class":81},"    }),\n",[71,1206,1207,1210,1213,1216,1219,1222,1225],{"class":73,"line":183},[71,1208,1209],{"class":81},"    { name: ",[71,1211,1212],{"class":88},"'AppStore'",[71,1214,1215],{"class":81},", enabled: process.env.",[71,1217,1218],{"class":105},"NODE_ENV",[71,1220,1221],{"class":77}," !==",[71,1223,1224],{"class":88}," 'production'",[71,1226,1227],{"class":81}," },\n",[71,1229,1230],{"class":73,"line":218},[71,1231,1232],{"class":81},"  ),\n",[71,1234,1235],{"class":73,"line":223},[71,1236,688],{"class":81},[15,1238,1239,1240,1242],{},"With ",[19,1241,1091],{}," you get time-travel debugging, action logs, and state diffing\nin the Redux DevTools browser extension — the same tools Redux users rely on,\nat no extra cost.",[1086,1244,1246,1249],{"id":1245},"persist-localstorage-sessionstorage-hydration",[19,1247,1248],{},"persist"," — localStorage \u002F sessionStorage hydration",[62,1251,1253],{"className":64,"code":1252,"language":66,"meta":67,"style":67},"import { persist, createJSONStorage } from 'zustand\u002Fmiddleware'\n\nconst useSettingsStore = create(\n  persist(\n    (set) => ({\n      theme:    'dark',\n      fontSize: 16,\n      setTheme: (t) => set({ theme: t }),\n    }),\n    {\n      name:       'user-settings',                    \u002F\u002F localStorage key\n      storage:    createJSONStorage(() => localStorage),\n      partialize: (state) => ({ theme: state.theme }), \u002F\u002F only persist theme\n    },\n  ),\n)\n",[19,1254,1255,1266,1270,1283,1290,1302,1312,1322,1341,1345,1350,1364,1379,1398,1403,1407],{"__ignoreMap":67},[71,1256,1257,1259,1262,1264],{"class":73,"line":74},[71,1258,78],{"class":77},[71,1260,1261],{"class":81}," { persist, createJSONStorage } ",[71,1263,85],{"class":77},[71,1265,1109],{"class":88},[71,1267,1268],{"class":73,"line":92},[71,1269,96],{"emptyLinePlaceholder":95},[71,1271,1272,1274,1277,1279,1281],{"class":73,"line":99},[71,1273,102],{"class":77},[71,1275,1276],{"class":105}," useSettingsStore",[71,1278,109],{"class":77},[71,1280,113],{"class":112},[71,1282,664],{"class":81},[71,1284,1285,1288],{"class":73,"line":136},[71,1286,1287],{"class":112},"  persist",[71,1289,664],{"class":81},[71,1291,1292,1294,1296,1298,1300],{"class":73,"line":143},[71,1293,1138],{"class":81},[71,1295,55],{"class":119},[71,1297,127],{"class":81},[71,1299,130],{"class":77},[71,1301,133],{"class":81},[71,1303,1304,1307,1310],{"class":73,"line":155},[71,1305,1306],{"class":81},"      theme:    ",[71,1308,1309],{"class":88},"'dark'",[71,1311,152],{"class":81},[71,1313,1314,1317,1320],{"class":73,"line":166},[71,1315,1316],{"class":81},"      fontSize: ",[71,1318,1319],{"class":105},"16",[71,1321,152],{"class":81},[71,1323,1324,1327,1329,1332,1334,1336,1338],{"class":73,"line":171},[71,1325,1326],{"class":112},"      setTheme",[71,1328,266],{"class":81},[71,1330,1331],{"class":119},"t",[71,1333,127],{"class":81},[71,1335,130],{"class":77},[71,1337,194],{"class":112},[71,1339,1340],{"class":81},"({ theme: t }),\n",[71,1342,1343],{"class":73,"line":177},[71,1344,1204],{"class":81},[71,1346,1347],{"class":73,"line":183},[71,1348,1349],{"class":81},"    {\n",[71,1351,1352,1355,1358,1361],{"class":73,"line":218},[71,1353,1354],{"class":81},"      name:       ",[71,1356,1357],{"class":88},"'user-settings'",[71,1359,1360],{"class":81},",                    ",[71,1362,1363],{"class":139},"\u002F\u002F localStorage key\n",[71,1365,1366,1369,1372,1374,1376],{"class":73,"line":223},[71,1367,1368],{"class":81},"      storage:    ",[71,1370,1371],{"class":112},"createJSONStorage",[71,1373,970],{"class":81},[71,1375,130],{"class":77},[71,1377,1378],{"class":81}," localStorage),\n",[71,1380,1381,1384,1386,1388,1390,1392,1395],{"class":73,"line":229},[71,1382,1383],{"class":112},"      partialize",[71,1385,266],{"class":81},[71,1387,199],{"class":119},[71,1389,127],{"class":81},[71,1391,130],{"class":77},[71,1393,1394],{"class":81}," ({ theme: state.theme }), ",[71,1396,1397],{"class":139},"\u002F\u002F only persist theme\n",[71,1399,1400],{"class":73,"line":249},[71,1401,1402],{"class":81},"    },\n",[71,1404,1405],{"class":73,"line":254},[71,1406,1232],{"class":81},[71,1408,1409],{"class":73,"line":260},[71,1410,688],{"class":81},[15,1412,1413,1416],{},[19,1414,1415],{},"partialize"," is important: never persist loading flags, error messages, or\nother transient state. On the next page load they'd render stale UI before the\nfirst fetch completes.",[1086,1418,1420,1423],{"id":1419},"immer-mutable-style-updates",[19,1421,1422],{},"immer"," — mutable-style updates",[62,1425,1427],{"className":64,"code":1426,"language":66,"meta":67,"style":67},"import { immer } from 'zustand\u002Fmiddleware\u002Fimmer'\n\nconst useCartStore = create(\n  immer((set) => ({\n    items: [],\n\n    \u002F\u002F Direct mutation — Immer converts it to a structural share\n    addItem: (item) => set((state) => {\n      state.items.push(item)\n    }),\n\n    updateQty: (id, qty) => set((state) => {\n      const item = state.items.find(i => i.id === id)\n      if (item) item.qty = qty   \u002F\u002F nested mutation — no spreads needed\n    }),\n  })),\n)\n",[19,1428,1429,1441,1445,1458,1473,1478,1482,1487,1513,1524,1528,1532,1563,1594,1610,1614,1619],{"__ignoreMap":67},[71,1430,1431,1433,1436,1438],{"class":73,"line":74},[71,1432,78],{"class":77},[71,1434,1435],{"class":81}," { immer } ",[71,1437,85],{"class":77},[71,1439,1440],{"class":88}," 'zustand\u002Fmiddleware\u002Fimmer'\n",[71,1442,1443],{"class":73,"line":92},[71,1444,96],{"emptyLinePlaceholder":95},[71,1446,1447,1449,1452,1454,1456],{"class":73,"line":99},[71,1448,102],{"class":77},[71,1450,1451],{"class":105}," useCartStore",[71,1453,109],{"class":77},[71,1455,113],{"class":112},[71,1457,664],{"class":81},[71,1459,1460,1463,1465,1467,1469,1471],{"class":73,"line":136},[71,1461,1462],{"class":112},"  immer",[71,1464,116],{"class":81},[71,1466,55],{"class":119},[71,1468,127],{"class":81},[71,1470,130],{"class":77},[71,1472,133],{"class":81},[71,1474,1475],{"class":73,"line":143},[71,1476,1477],{"class":81},"    items: [],\n",[71,1479,1480],{"class":73,"line":155},[71,1481,96],{"emptyLinePlaceholder":95},[71,1483,1484],{"class":73,"line":166},[71,1485,1486],{"class":139},"    \u002F\u002F Direct mutation — Immer converts it to a structural share\n",[71,1488,1489,1492,1494,1497,1499,1501,1503,1505,1507,1509,1511],{"class":73,"line":171},[71,1490,1491],{"class":112},"    addItem",[71,1493,266],{"class":81},[71,1495,1496],{"class":119},"item",[71,1498,127],{"class":81},[71,1500,130],{"class":77},[71,1502,194],{"class":112},[71,1504,116],{"class":81},[71,1506,199],{"class":119},[71,1508,127],{"class":81},[71,1510,130],{"class":77},[71,1512,276],{"class":81},[71,1514,1515,1518,1521],{"class":73,"line":177},[71,1516,1517],{"class":81},"      state.items.",[71,1519,1520],{"class":112},"push",[71,1522,1523],{"class":81},"(item)\n",[71,1525,1526],{"class":73,"line":183},[71,1527,1204],{"class":81},[71,1529,1530],{"class":73,"line":218},[71,1531,96],{"emptyLinePlaceholder":95},[71,1533,1534,1537,1539,1542,1544,1547,1549,1551,1553,1555,1557,1559,1561],{"class":73,"line":223},[71,1535,1536],{"class":112},"    updateQty",[71,1538,266],{"class":81},[71,1540,1541],{"class":119},"id",[71,1543,122],{"class":81},[71,1545,1546],{"class":119},"qty",[71,1548,127],{"class":81},[71,1550,130],{"class":77},[71,1552,194],{"class":112},[71,1554,116],{"class":81},[71,1556,199],{"class":119},[71,1558,127],{"class":81},[71,1560,130],{"class":77},[71,1562,276],{"class":81},[71,1564,1565,1567,1570,1572,1575,1578,1580,1583,1585,1588,1591],{"class":73,"line":229},[71,1566,821],{"class":77},[71,1568,1569],{"class":105}," item",[71,1571,109],{"class":77},[71,1573,1574],{"class":81}," state.items.",[71,1576,1577],{"class":112},"find",[71,1579,835],{"class":81},[71,1581,1582],{"class":119},"i",[71,1584,1037],{"class":77},[71,1586,1587],{"class":81}," i.id ",[71,1589,1590],{"class":77},"===",[71,1592,1593],{"class":81}," id)\n",[71,1595,1596,1599,1602,1604,1607],{"class":73,"line":249},[71,1597,1598],{"class":77},"      if",[71,1600,1601],{"class":81}," (item) item.qty ",[71,1603,294],{"class":77},[71,1605,1606],{"class":81}," qty   ",[71,1608,1609],{"class":139},"\u002F\u002F nested mutation — no spreads needed\n",[71,1611,1612],{"class":73,"line":254},[71,1613,1204],{"class":81},[71,1615,1616],{"class":73,"line":260},[71,1617,1618],{"class":81},"  })),\n",[71,1620,1621],{"class":73,"line":279},[71,1622,688],{"class":81},[15,1624,1625,1626,1628],{},"Use ",[19,1627,1422],{}," for deeply nested state where manual spreading becomes brittle.\nSkip it for flat state — the overhead isn't worth it.",[10,1630,1632],{"id":1631},"zustand-vs-redux-toolkit-vs-context","Zustand vs Redux Toolkit vs Context",[15,1634,1635],{},"Interviewers often ask you to compare the three. Here's the honest picture:",[1637,1638,1639,1656],"table",{},[1640,1641,1642],"thead",{},[1643,1644,1645,1648,1651,1653],"tr",{},[1646,1647],"th",{},[1646,1649,1650],{},"Context",[1646,1652,32],{},[1646,1654,1655],{},"Redux Toolkit",[1657,1658,1659,1674,1687,1700,1714,1731,1744],"tbody",{},[1643,1660,1661,1665,1668,1671],{},[1662,1663,1664],"td",{},"Bundle size",[1662,1666,1667],{},"0 kB (built-in)",[1662,1669,1670],{},"~1 kB",[1662,1672,1673],{},"~12 kB",[1643,1675,1676,1679,1682,1685],{},[1662,1677,1678],{},"Provider required",[1662,1680,1681],{},"Yes",[1662,1683,1684],{},"No",[1662,1686,1681],{},[1643,1688,1689,1692,1695,1698],{},[1662,1690,1691],{},"Re-render granularity",[1662,1693,1694],{},"Per-context",[1662,1696,1697],{},"Per-selector",[1662,1699,1697],{},[1643,1701,1702,1705,1708,1711],{},[1662,1703,1704],{},"Devtools",[1662,1706,1707],{},"None",[1662,1709,1710],{},"Via middleware",[1662,1712,1713],{},"First-class",[1643,1715,1716,1719,1722,1725],{},[1662,1717,1718],{},"Async",[1662,1720,1721],{},"Manual",[1662,1723,1724],{},"Plain async\u002Fawait",[1662,1726,1727,1730],{},[19,1728,1729],{},"createAsyncThunk"," \u002F RTK Query",[1643,1732,1733,1736,1738,1741],{},[1662,1734,1735],{},"Data fetching + caching",[1662,1737,1707],{},[1662,1739,1740],{},"DIY or pair with TanStack",[1662,1742,1743],{},"RTK Query",[1643,1745,1746,1749,1752,1755],{},[1662,1747,1748],{},"Learning curve",[1662,1750,1751],{},"Low",[1662,1753,1754],{},"Very low",[1662,1756,1757],{},"Medium",[15,1759,1760,1763],{},[30,1761,1762],{},"When to use Context:"," Infrequently-changing cross-cutting concerns — theme,\nlocale, current user identity. It's built into React, requires no dependencies,\nand the performance hit only matters when the value changes constantly.",[15,1765,1766,1769],{},[30,1767,1768],{},"When to use Zustand:"," Multiple unrelated components share frequently-updated\nstate. You want global state with minimal boilerplate, a small bundle, and\naccess outside the component tree. Medium-to-large apps where Context's\nall-consumers-re-render model would be a problem.",[15,1771,1772,1775],{},[30,1773,1774],{},"When to use Redux Toolkit:"," Very large apps with large teams that benefit\nfrom strict conventions and code organization. RTK Query is a compelling data\nfetching + caching layer — if you'd otherwise reach for TanStack Query plus\nZustand, RTK Query's integration can unify the two concerns.",[15,1777,1778],{},"There's no \"wrong\" answer — a well-reasoned trade-off explanation is exactly\nwhat interviewers want to hear.",[10,1780,1782],{"id":1781},"interview-tips","Interview Tips",[15,1784,1785,1788,1789,1792],{},[30,1786,1787],{},"Know the \"no Provider\" detail."," Interviewers often ask how Zustand avoids\nthe Provider requirement. The answer is that it stores state in a closure\noutside the React tree, using React's ",[19,1790,1791],{},"useSyncExternalStore"," (or a custom\nsubscription internally) to connect components to that external store.",[15,1794,1795,1801,1802,1804,1805,1807],{},[30,1796,1797,1798,1800],{},"Explain selectors and ",[19,1799,573],{}," confidently."," This is the most common\nfollow-up question at mid to senior level. Be able to explain why ",[19,1803,487],{},"\nfails for object selectors and when ",[19,1806,573],{}," fixes it.",[15,1809,1810,1813],{},[30,1811,1812],{},"Don't dismiss Redux."," Saying \"Zustand is better\" signals inexperience.\nInstead, articulate the trade-offs: Zustand for smaller bundles and less\nceremony, Redux Toolkit for large teams, strict conventions, and RTK Query.",[15,1815,1816,1819,1820,122,1823,1826,1827,1829],{},[30,1817,1818],{},"Know the slices pattern."," If asked how you'd scale a Zustand store, describe\nsplitting state into slice files (",[19,1821,1822],{},"createAuthSlice",[19,1824,1825],{},"createCartSlice",") and\nmerging them in a single ",[19,1828,51],{},". This shows you've thought past toy-app\nusage.",[15,1831,1832,1835,1836,1838],{},[30,1833,1834],{},"Be able to write a store from memory."," A ",[19,1837,51],{}," call with state, an\naction that uses the updater function form, and a selector in a component —\nthat's the core pattern. Practice writing it without looking it up.",[15,1840,1841,1842,1844],{},"Zustand is a small library with a handful of concepts, but demonstrating you\nunderstand ",[38,1843,40],{}," it makes each design choice — no Provider, selector-based\nsubscriptions, middleware composition — is what separates a candidate who\nGoogled the API from one who has actually used it in production.",[1846,1847,1848],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":67,"searchDepth":92,"depth":92,"links":1850},[1851,1852,1853,1854,1855,1863,1864],{"id":12,"depth":92,"text":13},{"id":44,"depth":92,"text":45},{"id":472,"depth":92,"text":473},{"id":697,"depth":92,"text":698},{"id":1077,"depth":92,"text":1078,"children":1856},[1857,1859,1861],{"id":1088,"depth":99,"text":1858},"devtools — Redux DevTools integration",{"id":1245,"depth":99,"text":1860},"persist — localStorage \u002F sessionStorage hydration",{"id":1419,"depth":99,"text":1862},"immer — mutable-style updates",{"id":1631,"depth":92,"text":1632},{"id":1781,"depth":92,"text":1782},"Master Zustand for React interviews — store creation, selectors, middleware, async actions, and when to choose Zustand over Redux or Context.","medium","md","React","react",{},"\u002Fblog\u002Freact-zustand-guide","\u002Freact\u002Fstate-management\u002Fzustand",{"title":5,"description":1865},"blog\u002Freact-zustand-guide","State Management","state-management","2026-06-24","ieawseawZC_HKyfmi92DUE-xNq9Z--E_uQfQgZQ78Bc",1782244083220]