I have a custom hook that fetches a local JSON file that many components make use of.
hooks.js
export function useContent(lang) {
const [content, setContent] = useState(null);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
fetch(`/locale/${lang}.json`, { signal: signal })
.then((res) => {
return res.json();
})
.then((json) => {
setContent(json);
})
.catch((error) => {
console.log(error);
});
return () => {
abortController.abort();
};
}, [lang]);
return { content };
}
/components/MyComponent/MyComponent.js
import { useContent } from '../../hooks.js';
function MyComponent(props) {
const { content } = useContent('en');
}
/components/MyOtherComponent/MyOtherComponent.js
import { useContent } from '../../hooks.js';
function MyOtherComponent(props) {
const { content } = useContent('en');
}
My components behave the same, as I send the same en
string to my useContent()
hook in both. The useEffect()
should only run when the lang
parameter changes, so seeing as both components use the same en
string, the useEffect()
should only run once, but it doesn't - it runs multiple times. Why is that? How can I update my hook so it only fetches when the lang
parameter changes?
Hooks are run independently in different components (and in different instances of the same component type). So each time you call useContent
in a new component, the effect (fetching data) is run once. (Repeated renders of the same component will, as promised by React, not re-fetch the data.) Related: React Custom Hooks fetch data globally and share across components?
A general React way to share state across many components is using a Context hook (useContext
). More on contexts here. You'd want something like:
const ContentContext = React.createContext(null)
function App(props) {
const { content } = useContent(props.lang /* 'en' */);
return (
<ContentContext.Provider value={content}>
<MyComponent>
<MyOtherComponent>
</ContentContext>
);
}
function MyComponent(props) {
const content = useContext(ContentContext);
}
function MyOtherComponent(props) {
const content = useContext(ContentContext);
}
This way if you want to update the content / language / whatever, you would do that at the app level (or whatever higher level you decide makes sense).