Search⌘ K
AI Features

React Custom Hooks (Advanced)

Explore how to create advanced custom hooks in React that combine useState and useEffect to manage state and sync it with local storage. Understand naming conventions, reusability, and how to handle keys to avoid data overwrites. This lesson helps you abstract complex logic into reusable hooks to keep components lean and maintainable.

We'll cover the following...

Thus far we’ve covered the two most popular hooks in React: useState and useEffect. useState is used to make your application interactive; useEffect is used to opt into the lifecycle of your components.

We’ll eventually cover more hooks that come with React – though we won’t cover all of them here. Next, we’ll tackle React custom Hooks; that is, building a hook yourself.

We will use the two hooks we already possess to create a new custom hook called useSemiPersistentState, named as such because it manages state yet synchronizes with the local storage. It’s not fully persistent because clearing the local storage of the browser deletes relevant data for this application. Start by extracting all relevant implementation details from the App component into this new custom hook:

Node.js
const useSemiPersistentState = () => {
const [searchTerm, setSearchTerm] = React.useState(
localStorage.getItem('search') || ''
);
React.useEffect(() => {
localStorage.setItem('search', searchTerm);
}, [searchTerm]);
};
const App = () => {
...
};

So far, it’s just a function around our previously used in the App component used useState and useEffect hooks. Before we can use it, let’s return the values that are needed in our App component from this custom hook:

Node.js
const useSemiPersistentState = () => {
const [searchTerm, setSearchTerm] = React.useState(
localStorage.getItem('search') || ''
);
React.useEffect(() => {
localStorage.setItem('search', searchTerm);
}, [searchTerm]);
return [searchTerm, setSearchTerm];
};

We are following two conventions of React’s built-in hooks here. First, the naming convention which puts the “use” prefix in front of every hook name; second, the returned values are returned as an array.

While this example returns a state and a setter, a more complex react custom hook with multiple functions could return a whole suite of utilities. For instance, you could return a function to clear the storage, a function to validate the input, and the state itself. This allows you to keep the component lean while the hook handles the heavy lifting.

Now we can use the custom hook with its returned values in the App component with the usual array destructuring:

Node.js
const App = () => {
const stories = [ ... ];
const [searchTerm, setSearchTerm] = useSemiPersistentState();
const handleSearch = event => {
setSearchTerm(event.target.value);
};
const searchedStories = stories.filter(story =>
story.title.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
...
);
};

Another goal of a custom hook should be reusability. All of this custom hook’s internals are about the search domain, but the hook should be for a value that’s set in state and synchronized in local storage. Let’s adjust the naming therefore:

Node.js
const useSemiPersistentState = () => {
const [value, setValue] = React.useState(
localStorage.getItem('value') || ''
);
React.useEffect(() => {
localStorage.setItem('value', value);
}, [value]);
return [value, setValue];
};

We handle an abstracted “value” within the custom hook. Using it in the App component, we can name the returned current state and state updater function anything domain-related (e.g. searchTerm and setSearchTerm) with array destructuring.

There is still one problem with this custom hook. Using the custom hook more than once in a React application leads to an overwrite of the “value”-allocated item in the local storage. To fix this, pass in a key:

Node.js
const useSemiPersistentState = key => {
const [value, setValue] = React.useState(
localStorage.getItem(key) || ''
);
React.useEffect(() => {
localStorage.setItem(key, value);
}, [value, key]);
return [value, setValue];
};
const App = () => {
...
const [searchTerm, setSearchTerm] = useSemiPersistentState(
'search'
);
...
};

Since the key comes from outside, the custom hook assumes that it could change, so it needs to be included in the dependency array of the useEffect hook. Without it, the side-effect may run with an outdated key (also called stale) if the key changed between renders.

Another improvement is to give the custom hook the initial state we had from the outside:

Node.js
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) || initialState
);
...
};
const App = () => {
...
const [searchTerm, setSearchTerm] = useSemiPersistentState(
'search',
'React'
);
...
};

Here is the complete demonstration of the above concepts:

 ãF )   95@@ °n‰PNG


IHDR(-SäPLTE""""""""""""""""""2PX=r€)7;*:>H¤-BGEˆš8do5Xb6[eK™®Kš¯1MU9gs3S\I“§:gt'03@{‹V¹ÔT´ÏA}V»Ö@y‰6\fH’¦-CII”¨Eˆ›+;@7_i7_jFŠJ–«K›°H£-BHaÚû,@FCƒ”L³&.0W½ÙN£ºI“¨$)+B‘J•ªR¯È?v†>s>u„S±Ê=qP©ÁP¨ÀP§¿,?D4U^%+-M ¶K˜®%+,2OX+<ALœ²#&&D†˜%,.I•©vôTötRNSIæçJäeÀe¦©IDATxM޵ZEA„ÿÙ³	îî%R¡ïßáTThÇG…»,Á®Å=²Òîmífímnf’A–$â‡>!¦gºôHg½Eߏܵ}	Ý»ý‡º¼kdú§¯Jo—™Î3æL"J¹ ›ÌÕüQ‡$âçļffµ,é€5i9̟¯H¨/mB†‡wÍÜw;D
Ø+&‚W«ª¹¨Dôo@Ê´RI©ÐB¡om.Û³ÀIEND®B`‚‰PNG


IHDRשÍÊePLTE""""""""""""""""""""""""2RZN¢¹J–«3R[J—¬)59YÁÞ0KS4W`Q«ÄLœ²%+-0JR)6::gtC‚“"##?v†U·Ñ?w†<n{&-/YÂß=q:iuBA}A{ŒB‘/IPP§¿=q€K™®_ÔóL³$();lzR¯ÉaÚûI“¨ZÆã3U^1MU3T]ZÅâI“§X¿ÜF‹ž-BGP¨À6[e,@E5ZdO§¿-BHX¿Û+=AW¾Ú,@FW¼ØQªÃ?v…W¼×+<A@yˆ"#$\Ìê4Wa\ÌëS²Ì$(*.EL^ÑñVºÕ6]h#$%GŽ¡#&';jwV¹Ô-CIL›±ZÄá^Ðï>u„S°Ê/HNM ·_Õõ\ËéM ¶8doD…—D†˜>tƒ+=B[Èæ,>C>t‚<o}@y‰0LS.EKT´Î$'(%,.A~ŽW½ÙC’%+,\ÊèC!ätRNS‘í‰œG¾ÖOIDATxl‰ÃB¶Q…Ÿu´ß_ȳ<˦Ýveê²óa6AξŒûv¢{@ÎE'ÞdIÕ!çží ðC—ÔT‹þg1ÂE(ÏñSQsâi
ď…Zÿ·V¹Ð)ég!‰ªhÎùtéº-i}˜µµ<Õ?¶lBZaÄ´4{DÓ⌻_e8¥yǁ­À3ž)Ÿ¥?°f;8.ã¤tÌ=å;	:ã52fKZìlù¨ØšÍ9.ž#ƒÒAÁqÌúÛ®£Vÿ`=$¬Â?_¶¾®ÔqMç.ïJ$
?^q÷ñíۏï.},‚ìsæÝ_TttÔ¾1#‰/(ì—-[è`è`ÌÚïÅðZd5’Ž™›?ÎebZ¿Þˆi.Ûæ™ìq΄+1°}Œ5ùïçd¨G•ÏøIEND®B`‚‰PNG


IHDR  D¤ŠÆAPLTE"""""""""""""""""""""""""""2RZVºÖ_ÔôU·Ñ=r€$()'25]ÎíCƒ•0LS<o}XÀÜX¿Û0JQ=p~D„–<n{VºÕE‡™8do_ÔóEˆšF‹žH‘¥9dp_ÕõH¤I“¨FŒŸ6[e`Ö÷`×øL³/GM_ÓòU¸Ó'02P©Á/IPPªÂX¿Ü&/1;ly3R[`ØøGŽ¡T³Í\ÌêaÚû1OW"##Q«ÄaÙúR®Ç=q€`Öö.EL+=ATµÐ-CIK˜®#&'C‚“^ÐïI”¨&.04U^^Ñð@yˆZÇä$(*[Éç^Ññ,?DR¯É"#$1NV1MTD…—>u„;kxG R¯È/HN&-/@y‰>s>t‚@zŠ]ÍìP¨À$'(D†—]Ïî<n|0JRU·Ò×\¼	tRNS%­ñ'ïó(ò~ÑÝèžIDATx“šC1F_Ý¿MmÛ¶4¶m{ÿ˜¤n†çáÓ	®A$–à$b‘ Heø™TãWÄÂh•šh´:PtZ
Q«0@.`€Þ`4™-V`³Zì&‡A#ÁébkÝÄãñúØ>.''ø`C$FØÏ	‘(±x"6Xác”TÚéL§@Iù;dd-¹|¾P,Ȕ9¡R­ÕÍf3¢¿F½VmMíX§ÚíÍç@Y˜7ÎõºÕN¬=—ŽåÈʪu
}Ö¬«+‘e‡aiq ¤Ö76­íÝ=h
ûZìíîl›ë‡}á¨ʱ¥[F«I9A¹k9¥ÖëäŒ3¢Ã9ΡóžqB~Øáb¸ÃåU_¸^Ü[·ôw†ý{z‡v‡z‡Ù(Š£(Š£(þ›†Šfòq”ÉG–Éïkñ”ÏçŠY¾ÿªfäòÇ~à:*4ÓQ\O>Ÿ‘ ‡¼<דúW£éÍZ|ދÅ7“ñ•ïjTÔäãn”½»¢®`$Hð+ò¿GOñûð*èx‹•ø¥X*|”^ÿdIEND®B`‚‰PNG


IHDR@@·ì:PLTE"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""%+-@y‰W¼×`×ø^ÑñS²ÌC‚“,>C*8<XÁÝaÚûaÙùMŸµ+<AaÙúXÀÜ#%%TµÐLž´=q>u„K›°`ÖöA}L›±8do=r€%+,@yˆ^ÐïS²Ë)59=q€P©ÁU·Ò"#$PªÂ\Êè0JQQªÃ"##U·Ñ#&&_Ôô>t‚>s`Øø_Ôó5Yc1OW5Zd1NV+=B1MU+;@/GM\Ìê*;?3S\)8<2RZ_Õõ+=A]Ïî,@F,@E&-/0KS7alO¦¾9dp8amB~EˆšP¨ÀN¢¹'023T]]Îí?x‡3U^Cƒ•6\gU¸Ó&.0D„–7_iR­ÆH‘¥I”¨M ¶$(*?v…ZÆãX¿Ü-AG#$%[Éç8co[ÈæW½ÙC’'25?v†8bn%*+Lœ²N£º2PX)7;=p~(58^ÒòP§¿4WaQ«ÄT´Ï0JRQ¬ÅT´ÎI“¨6]hR¯ÉT³Í0LSF‹9eqEˆ›E‰œ9gsFŠCƒ”#&'\Ëé`Ö÷&/16\fB‘A{ŒR®Ç]Íì(47%,.*:>*9=9fr:gt7^iU¶Ð?w†ZÇäX¿Û^ÑðQ­ÅH£)6:V¹Ô'034U^E‡™.EL.FMK™®@zŠS×tRNS*Ž×øÖ”ý˜	»½•+üùóԐ,ØúôÀ=VIDATx¤ËµC!Ð‡C|ÿãÚ^yR]ÕMÛáO]ßÔÕÝ0NÈ2ÍËí¿"ªª¢(0Vã(ÀY%PDT-~(m¬ó!âKÞY£~´•üIÒf{³ÛÞáa¼§§ô3—ÕOp&”Ф‰¡xŽ÷#Ÿj­ôÚ¶mméòc)]m¤’‡É)Ƨgfçhk²ñÎÒ ægg¦Ç™šìÐ+X€ÅêuiyVת·k«°²\[ü:,Ø6ØÜjIJ
;»"»;°×XùþÛfÁáÇÍûý“SÎÎÏÏ8=Þo¾;æèÐ(ƒ‹öÓ¥BkÔeûÍ\7p+mîîáþNÚ<ÀQðOÒæùô³´y‚g»ttÐëo•ý½£ŸìVð»Òäsýü¬™ø”&_ðaüïV~à·Ö?­*8àQ ;8¥Á,¸¤‚f¥“1Üx¤†×§ñŸ*œøÑA¯Ôð°a#±³¶¦#ŠnP‘i+¼¶CÈ,ˆÆèäÍ_áNbÑá‚øç•HŽB*ÚÒ¦ L(^<ñÁ‚L6pJ¾P”ɥީ¢%"“R,ä9Èe3eRËa1(
¢ßqÇ8َ´ŠmK˱mƶmÛü·yi!èΪYÏuë ÀÏ_Àï?i÷ˆý+òŠÄA|ù{‘˜´?¿_En).JËD¤<€
©¬¢Z\Ts©R*(	¯©JŠ…uX/
4J9š¡5·DEµ4kÇ4‡&i¥V4Ú¡®Ð¯†vsf:àg,¢èBC»î$¶ºÍùá–@ôŠI_?<‘!^ŠÈàÓ½ÇöäõB‘%Làw±FD1ŠÁ¨(F€±øH˜%0Ʊ¿Å؄(¢0ˆÅÄ'Åæ—N.0u„@íY‡PWìIüaNâKš™Ä?ðÓµŒ=Žeœv/c—±ŒÓ0c0÷2Êë:ˆ06R-uÒÄ­\Q̶ää´¼µ6R#
ÆFš³6Òñ·rՁ­ìu˜æmâðž‡Iñi~ –Åü ÃsPþ"±óŸ¼
eiyå£ËPšàãÊò’§¡œÝÒ,S]U¦ºV…ªÖ”©®Z¦êoëë·xzã™âÆSnm¬{ÚºwaلÏ…Å»´Ýõ(mg/®þå½À¿¼ûŒ[§b³µq¶Å&Õ¯¹$ñzȊ‹H>aÌKT1/æø1O‚‰0¾.h͇YþAÓö£
-ê>ۋº«¢XÕ¢î}ߨëÛÑ;ÃöN´ØvÅýÎ¸ÿ1ë×ÄO@&v/Äþ_—ö\ôÇ\í.™½+0”;!fÊ¦´Ó% JY·O”ÂŽ'/Å]_Š;ßÀ'"&Nªn	aQ^”cx¦AáÒIEND®B`‚

You’ve just created your first custom hook. If you’re not comfortable with custom hooks, you can revert the changes and use the useState and useEffect hook as before, in the App component.

However, knowing how to design a react custom hook with multiple functions gives you many new options. A custom hook can encapsulate non-trivial implementation details that should be kept away from a component. It can be shared across multiple components or even open-sourced as an external library.

Using your favorite search engine, you’ll notice there are hundreds of React hooks that could be used in your application without worry over implementation details.

Exercises:

  • Confirm the changes from the last section.
  • Read more about React Hooks to get a good understanding of them. They are the bread and butter in React function components, so it’s important to really understand them (0, 1).