React Hooks With Context

Ayush Bansal
5 min readDec 27, 2021

Recently, I had to make functionality to show and hide Help button at different pages. The first thing the comes to our mind is we can make the button component in every page that needs to be shown. Right? But when we say it out loud then we notice that we have to mount the button at every page and also the button was being generated by third party API (Freshworks) so we have to load javascript again and again in every visible page.

Let’s go through the whole mind process in creating the functionality and try to make it a bit more better. Let’s import the component in the root component so that the button is visible at any page by default. And hide the function at required pages. Here comes the functionality of custom hook handy.

But what is a custom hook?

According to React’s official document: `A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks`

So a hook is just a javascript function basically in which we will define logic to show and hide the help button.

export const useHelp = () => {
const show = () => {};
const hide = () => {};
return { show, hide };
};

But I wanted to show the button by default and in the above logic once the hide function is called then it will be hidden even if the user changes the route. To solve This we have to add a listener which calls show method whenever route changes.

export const useHelp = () => {  useEffect(() => {
history.listen(() => {
show()
});
}, []);
const show = () => {
};
const hide = () => {
};
return { show, hide };
};

But I wanted to make a third part (Boot) which loads the help api button for the first time. Up until now I was just adding the script in public.html but to me it feels like hard coding. So I decided to add a custom helper function to add script dynamically into the Dom:

export function loadScript(src = '', innerHTML = '') {
return new Promise(resolve => {
const script = document.createElement('script');
script.src = src;
script.innerHTML = innerHTML;
script.onload = () => {
resolve(true);
};
script.onerror = () => {
resolve(false);
};
document.body.appendChild(script);
});
}

So Now my hooks look like this:

export const useHelp = () => {  useEffect(() => {
history.listen(() => {
show()
});
}, []);
const show = () => {
};
const hide = () => {
};
const boot = () => {
loadScript(
‘Script_address’
)
};
return { show, hide , boot};
};

But then here came another problem: Whenever I call the boot function then it loads the script asynchronously and Show or hide functions were already called by that time. So I needed to save the state of the page whether it should show the button or not in a variable.

export const useHelp = () => {
useEffect(() => {
history.listen(() => {
show();
});
}, []);
const [showHelp, setShowHelp] = useState(true); const hideShowHelp = show => {
if (show) {
// logic to show button
} else {
// logic to hide button
}
};
useEffect(() => {
hideShowHelp(showHelp);
}, [showHelp]);
const show = () => {
setShowHelp(true);
};
const hide = () => {
setShowHelp(false);
};
const boot = () => {
loadScript('Script_address').then(() => hideShowHelp(showHelp));
};
return { show, hide, boot };
};

Now When it seemed like it’s almost complete then came a bug. Now it shows error that we cannot change state on an unmounted compound. Whyyyyyy God. This is happening because when the route changes then our hook gets disconnected from the Dom and then the state cannot be updated. To solve this we need to use context so that our state would be accessible from anywhere in the tree.

But What is React Context?

According to ‘Geeks For Geeks’: `React Context is a method to pass props from parent to child component(s), by storing the props in a store(similar in Redux) and using these props from the store by child component(s) without actually passing them manually at each level of the component tree.`

Now that we know what react context is lets add this…

const HelpContext = createContext(null);export const HelpProvider = ({ children }) => {
const [show, setShow] = useState(true);
const history = useHistory();
const [isBooted, setIsBooted] = useState(false);
useEffect(() => {
history.listen(() => {
setShow(true); // By default set it to true whenever we changes the page.
});
}, []);
useEffect(() => {
// Code to show/hide the button through api
}, [show, isBooted]);
return (
<HelpContext.Provider value={{ setShow }}>{children}</HelpContext.Provider>
);
};

We wrap the HelpProvider around the root component then we can access the state from anywhere . So lets use this in our code now and here is our final code::

import React, { createContext, useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router';
import { loadScript } from '../utils/loadScript';
const HelpContext = createContext(null);export const HelpProvider = ({ children }) => {
const [show, setShow] = useState(true);
const history = useHistory();
const [isBooted, setIsBooted] = useState(false);
useEffect(() => {
history.listen(() => {
setShow(true);
});
}, []);
useEffect(() => {
if (show) {
//logic to show widget
} else {
// logic to hide widget
}
}, [show, isBooted]);
return (
<HelpContext.Provider value={{ setShow, setIsBooted }}>
{children}
</HelpContext.Provider>
);
};
HelpProvider.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
export const useHelp = () => {
const { setShow, setIsBooted } = useContext(HelpContext);
const show = () => {
setShow(true);
};
const hide = () => {
setShow(false);
};
const boot = () => {
loadScript('path/to/script').then(() => {
// Some initialisation
setIsBooted(true);
});
};
return { show, hide, boot };
};

Now We can wrap the HelpProvider around the root Component like:

<FreshWorksHelpProvider><App /></FreshWorksHelpProvider>

and call the boot function in the root component.

const { boot } = useFreshWorksHelp();useEffect(() => {boot();}, []);

Now on whatever page we want to hide the button we can do this:

const { hide } = useFreshWorksHelp();useEffect(() => {hide();}, []);

Please share your view on this.

That’s all for this post I hope this was helpful.

Keep on learning and keep on Reacting:)

--

--