React Best Practices

Avoid too many individual state hooks

The problem

Too many useState hooks can make your code spaghetti code. It'll be hard to maintain as it keeps growing. Here is an example of spaghetti state management:

1const Themes = () => {
2  const [themes, setThemes] = useState({ themes: [], customThemes: [] });
3  const [isTemplateLoading, setTemplateLoading] = useState(false);
4  const [isCloneDefaultTheme, setIsCloneDefaultTheme] = useState(false);
5  const [templateCodeChanged, setTemplateCodeChanged] = useState(false);
6  const [isAlertOpen, setIsAlertOpen] = useState(false);
7  const [themeName, setThemeName] = useState("");
8  const [originalThemeName, setOriginalThemeName] = useState("");
9  const [currentTab, setCurrentTab] = useState(TABS.themes);
10  const [configuration, setConfiguration] = useState({});
11  const [previewUrl, setPreviewUrl] = useState(null);
12  const [publishedTheme, setPublishedTheme] = useState(null);
13  const [organizationThemeId, setOrganizationThemeId] = useState(null);
14  const [savedConfig, setSavedConfig] = useState({});
15  const [templateContent, setTemplateContent] = useState("");
16  const [savedTemplateContent, setSavedTemplateContent] = useState("");
17  const [template, setTemplate] = useState({});
18  const [templateId, setTemplateId] = useState(null);
19  const [organisationThemeTemplates, setOrganisationThemeTemplates] = useState(
20    []
21  );
22  const [initialLoad, setInitialLoad] = useState(true);
23  const [cloneTemplate, setCloneTemplate] = useState(null);
24  const [groupedTemplates, setGroupedTemplates] = useState({});
25  const [typographs, setTypographs] = useState([]);
26  ...

This would mean that sending the states and state handlers to child components become messy too:

1  <ThemeSidebar
2    themes={themes}
3    template={template}
4    themeName={themeName}
5    currentTab={currentTab}
6    configuration={configuration}
7    groupedTemplates={groupedTemplates}
8    isTemplateLoading={isTemplateLoading}
9    organizationThemeId={organizationThemeId}
10    typographs={typographs}
11    setThemeName={setThemeName}
12    setCurrentTab={setCurrentTab}
13    setTemplateId={setTemplateId}
14    ...
15    setIsAlertOpen={setIsAlertOpen}
16    setOrganizationThemeId={setOrganizationThemeId}
17  />

This wasn't an issue in class components since it was all handled in a single state object and a setState() method.

The solution

There are many ways to fix this issue such as using useReducer hook. But the easiest and most familiar way to handle this is to have a state management solution similar to the one in class components. For that we can use the useSetState custom hook. The above code can be simplified to:

1import {useSetState} from 'react-use';
2const Themes = () => {
3    const [state, setState] = useSetState({
4        isTemplateLoading: false,
5        isCloneDefaultTheme: false
6        templateCodeChanged: false,
7        isAlertOpen: false,
8        themeName: "",
9        originalThemeName: "",
10        currentTab: TABS.themes,
11        configuration: {},
12        previewUrl: null,
13        publishedTheme: null,
14        organizationThemeId: null,
15        savedConfig: {},
16        templateContent: "",
17        savedTemplateContent: "",
18        template: {},
19        templateId: null,
20        organisationThemeTemplates: [],
21        initialLoad: true,
22        cloneTemplate: null,
23        groupedTemplates: {},
24        typographs: []
25    });
26    ...
27    setState({ themeName: "My theme" });

This would make passing states and their handlers to child components much easier:

1<ThemeSidebar themeStates={state} setThemeStates={setState} />
⌘K
    to navigateEnterto select Escto close
    Previous
    Next