I have a custom component where I want to prevent the useEffect to fire every time the component is rendering.
The main idea is to get the font-family name from the API and then pass it to the style value, so I want to get the font family just once - not every time the component renders in other screens.
Here's what I tried, but it doesn't work as expected, it's not updating the state after getting the value from API (getValue() not called).
import React, {useCallback, useEffect, useRef} from 'react';
import {useState} from 'react';
import {Text, StyleSheet, Platform} from 'react-native';
import {COLORS} from '../../common';
const AppText = ({children, style, ...rest}) => {
const isMounted = useRef(false);
const [fontFamily, setFontFamily] = useState('Helvetica-Bold');
const getValue = useCallback(() => {
// mock API
setTimeout(() => {
console.log('AppText: get font family name from API!!');
setFontFamily('HelveticaNeue');
}, 200);
}, []);
useEffect(() => {
if (isMounted.current) {
getValue();
} else {
isMounted.current = true;
return;
}
}, [getValue]);
return (
<Text
style={[
styles.text,
style,
{
fontFamily: fontFamily,
},
]}
{...rest}>
{children}
</Text>
);
};
export {AppText};
using:
Home/About/etc // other screens
const Home = () => {
return(
<View>
<AppText> Hey from home </AppText>
</View>
);
}
You can use a context to propergate the value down into multiple components (or use props) and just fetch it once higher up in the tree.
//App.js
import {useEffect, createContext, useState} from "react";
export const FontFamilyContext = createContext("DefaultFont");
const App = () =>{
const [font,setFont] = useState();
useEffect( () =>{
setFont(loadFont());
},[]);
return (
<FontFamilyContext.Provider value={font} >
<Screen />
<ScreenViaProp fontFamily={font} />
</FontFamilyContext.Provider>
);
}
export default App;
//Screen.jsx
//Advantage The font family can be used in nested components deep down
import { useContext } from "react";
import {FontContext} from "./App";
const Screen = () =>{
const fontFamily = useContext(FontFamilyContext);
return (
<div style={{fontFamily: fontFamily}}>
</div>
)
}
// ScreenViaProp .jsx Easier and no context is required
const ScreenViaProp = ({fontFamily}) =>{
return (
<div style={{fontFamily: fontFamily}}>
</div>
)
}
const DEFAULT_FONT_FAMILY = 'Helvetica-Bold';
const AppText = ({children, style, ...rest}) => {
const [fontFamily, setFontFamily] = useState(DEFAULT_FONT_FAMILY);
useEffect(() => {
// mock API
setTimeout(() => {
console.log('AppText: get font family name from API!!');
const FETCHED_FONT_FAMILY = 'HelveticaNeue';
if (FETCHED_FONT_FAMILY !== fontFamily) setFontFamily(FETCHED_FONT_FAMILY);
}, 200);
}, []);
return (
<Text
style={[
styles.text,
style,
{
fontFamily: fontFamily,
},
]}
{...rest}>
{children}
</Text>
);
};
You can also use a custom hook to load the font value only once in you app :
customHooks.js
let fontFromApi = null;
const fontP = new Promise(resolve => {
setTimeout(() => {
console.log('get font family name from API!!');
fontFromApi = 'HelveticaNeue';
resolve(fontFromApi);
}, 200);
});
export function useFont() {
const [font, setFont] = useState(fontFromApi || "Helvetica-Bold");
if (fontFromApi === null) {
fontP.then(v => setFont(v));
}
return font;
}
I declared a custom hook called useFont returning the font from the API. If the font is not loaded yet, it will return the fallback value Helvetica-Bold.
AppText.jsx
const { useFont } from './customHooks'
const AppText = ({children, style, ...rest}) => {
const fontFamily = useFont();
return (
<Text
style={[
styles.text,
style,
{ fontFamily }
]}
{...rest}>
{children}
</Text>
);
};