React hooks component & file structures 📦
Tags: javascript, react, webdev
Published: 2019-10-13 17:09:11

Hello!

Not long ago I thought that my component and file structures were clean and conscious. That was before the arrival of hooks. My components got harder to read and I couldn't fight the feeling that my structure was not clean enough.

Let's start with my folder structure. It was like this before:
```
src/
  components/
    MyComponent/
      MyComponent.jsx
      MyComponent.test.js
      styled.js
      index.js
    AnotherComponent/
      ...
```
Nothing special here. Let's move on. 

I will consider only functional components as they are essential for hooks. My basic component might look like this:
```javascript
import React from 'react';
import PropTypes from 'prop-types';
import AnotherComponent from '../AnotherComponent';
import {Wrapper} from './styled';


const CONSTANT_VALUE = true;

function smallUsefulFunction(arg) {
  // do something
  return arg;
}

MyComponent.propTypes = {
  unambiguousProp: PropTypes.string,
};

MyComponent.defaultProps = {
  unambiguousProp: '',
};

export function MyComponent({unambiguousProp}) {
  const enhancedProp = smallUsefulFunction(unambiguousProp);

  if (CONSTANT_VALUE) {
    return <AnotherComponent />;
  }

  return (
    <Wrapper>
      {enhancedProp}
    </Wrapper>
  );
}
```
The idea is to keep everything as simple and close to each other
as possible without over-complicating.

Now, with hooks, my component might have the following additions:
```diff
import React from 'react';
import PropTypes from 'prop-types';
import AnotherComponent from '../AnotherComponent';
import {Wrapper} from './styled';


const CONSTANT_VALUE = true;

function smallUsefulFunction(arg) {
  // do something
  return arg;
}

MyComponent.propTypes = {
  unambiguousProp: PropTypes.string,
};

MyComponent.defaultProps = {
  unambiguousProp: '',
};

export function MyComponent({unambiguousProp}) {
+ const [stateValue, setStateValue] = React.useState(false);
  const enhancedProp = smallUsefulFunction(unambiguousProp);


+ React.useEffect(() => {
+   // do something
+ }, []);
+
+ const handleWrapperClick = () => {
+   setStateValue(prevValue => !prevValue);
+ };

  if (CONSTANT_VALUE) {
    return <AnotherComponent />;
  }

  return (
    <Wrapper onClick={handleWrapperClick}>
      {enhancedProp}
    </Wrapper>
  );
}
```
Now let's imagine that this component has an overcomplicated state. The best option here is to useReducer(). This implies the addition of a reducer function and (probably) an initial state variable:
```diff
import React from 'react';
import PropTypes from 'prop-types';
import AnotherComponent from '../AnotherComponent';
import {Wrapper} from './styled';


const CONSTANT_VALUE = true;

function smallUsefulFunction(arg) {
  // do something
  return arg;
}

+ const initialState = {
+   firstCase: false,
+   secondCase: false,
+ };
+
+ function reducer(state, action) {
+   switch(action.type) {
+     case 'FIRST':
+       return {...state, firstCase: true};
+     case 'SECOND':
+       return {...state, secondCase: true};
+     default:
+       return state; 
+   }
+ }

MyComponent.propTypes = {
  unambiguousProp: PropTypes.string,
};

MyComponent.defaultProps = {
  unambiguousProp: '',
};

export function MyComponent({unambiguousProp}) {
  const [stateValue, setStateValue] = React.useState(false);
+ const [store, dispatch] = React.useReducer(initialState);
  const enhancedProp = smallUsefulFunction(unambiguousProp);

  React.useEffect(() => {
    // do something here
  }, []);

  const handleWrapperClick = () => {
    setStateValue(prevValue => !prevValue);
  };

  if (CONSTANT_VALUE) {
    return <AnotherComponent />;
  }

  return (
    <Wrapper onClick={handleWrapperClick}>
      {enhancedProp}
    </Wrapper>
  );
}
```
Now let's assume that this component has its own custom hook that we have decided to extract from function body for simplicity:
```diff
import React from 'react';
import PropTypes from 'prop-types';
import AnotherComponent from '../AnotherComponent';
import {Wrapper} from './styled';


const CONSTANT_VALUE = true;

function smallUsefulFunction(arg) {
  // do something
  return arg;
}

const initialState = {
  firstCase: false,
  secondCase: false,
};

function reducer(state, action) {
  switch(action.type) {
    case 'FIRST':
      return {...state, firstCase: true};
    case 'SECOND':
      return {...state, secondCase: true};
    default:
      return state; 
  }
}

+ function useSomething(dependency) {
+   const [something, setSomething] = React.useState(null);
+ 
+   React.useEffect(() => {
+     setSomething('something');
+   }, [dependency]);
+   
+   return something;
+ }

MyComponent.propTypes = {
  unambiguousProp: PropTypes.string,
};

MyComponent.defaultProps = {
  unambiguousProp: '',
};

export function MyComponent({unambiguousProp}) {
  const [stateValue, setStateValue] = React.useState(false);
  const [store, dispatch] = React.useReducer(initialState);
+ const something = useSomething(stateValue);
  const enhancedProp = smallUsefulFunction(unambiguousProp);

  React.useEffect(() => {
    // do something here
  }, []);

  const handleWrapperClick = () => {
    setStateValue(prevValue => !prevValue);
  };

  if (CONSTANT_VALUE) {
    return <AnotherComponent />;
  }

  return (
    <Wrapper onClick={handleWrapperClick}>
      {enhancedProp}
    </Wrapper>
  );
}
```
At last, let's imagine that this component has a repeatable part of JSX that you want to extract but is too small to be extracted into a separate file:
```diff
import React from 'react';
import PropTypes from 'prop-types';
import AnotherComponent from '../AnotherComponent';
import {Wrapper} from './styled';


const CONSTANT_VALUE = true;


+ const RepeatableJSX = () => (
+   <div>
+     I repeat
+   </div>
+ );

function smallUsefulFunction(arg) {
  // do something
  return arg;
}

const initialState = {
  firstCase: false,
  secondCase: false,
};

function reducer(state, action) {
  switch(action.type) {
    case 'FIRST':
      return {...state, firstCase: true};
    case 'SECOND':
      return {...state, secondCase: true};
    default:
      return state; 
  }
}

function useSomething(dependency) {
  const [something, setSomething] = React.useState(null);
 
  React.useEffect(() => {
    setSomething('something');
  }, [dependency]);
   
  return something;
}

MyComponent.propTypes = {
  unambiguousProp: PropTypes.string,
};

MyComponent.defaultProps = {
  unambiguousProp: '',
};

export function MyComponent({unambiguousProp}) {
  const [stateValue, setStateValue] = React.useState(false);
  const [store, dispatch] = React.useReducer(initialState); 
  const something = useSomething(stateValue);
  const enhancedProp = smallUsefulFunction(unambiguousProp);

  React.useEffect(() => {
    // do something here
  }, []);

  const handleWrapperClick = () => {
    setStateValue(prevValue => !prevValue);
  };

  if (CONSTANT_VALUE) {
    return <AnotherComponent />;
  }

  return (
    <Wrapper onClick={handleWrapperClick}>
      {enhancedProp}
      <RepeatableJSX />
      <RepeatableJSX />
      <RepeatableJSX />
    </Wrapper>
  );
}
```
Here is the syntax highlighted end version:
```javascript
import React from 'react';
import PropTypes from 'prop-types';
import AnotherComponent from '../AnotherComponent';
import {Wrapper} from './styled';


const CONSTANT_VALUE = true;

const RepeatableJSX = () => (
  <div>
    I repeat   
  </div>
);

function smallUsefulFunction(arg) {
  // do something
  return arg;
}

const initialState = {
  firstCase: false,
  secondCase: false,
};

function reducer(state, action) {
  switch(action.type) {
    case 'FIRST':
      return {...state, firstCase: true};
    case 'SECOND':
      return {...state, secondCase: true};
    default:
      return state; 
  }
}

function useSomething(dependency) {
  const [something, setSomething] = React.useState(null);
 
  React.useEffect(() => {
    setSomething('something');
  }, [dependency]);
   
  return something;
}

MyComponent.propTypes = {
  unambiguousProp: PropTypes.string,
};

MyComponent.defaultProps = {
  unambiguousProp: '',
};

export function MyComponent({unambiguousProp}) {
  const [stateValue, setStateValue] = React.useState(false);
  const [store, dispatch] = React.useReducer(initialState); 
  const something = useSomething(stateValue);
  const enhancedProp = smallUsefulFunction(unambiguousProp);

  React.useEffect(() => {
    // do something here
  }, []);

  const handleWrapperClick = () => {
    setStateValue(prevValue => !prevValue);
  };

  if (CONSTANT_VALUE) {
    return <AnotherComponent />;
  }

  return (
    <Wrapper onClick={handleWrapperClick}>
      {enhancedProp}
      <RepeatableJSX />
      <RepeatableJSX />
      <RepeatableJSX />
    </Wrapper>
  );
}
```
It just became harder to read, didn't it? If it still not, then try to imagine how it might look at the real-world application with multiple implementations.

After a little struggle I came up with a solution to extract hooks and reducers into a separate files. It looks like this:
```
src/
  components/
    MyComponent/
      MyComponent.jsx
      MyComponent.reducers.js
      MyComponent.hooks.js
      MyComponent.test.js
      styled.js
      index.js
```

However, I may have missed something and there might be a better solution to it.

Please, share your ideas!