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:
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:
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:
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:
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:
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:
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!
Top comments (0)