Appearance
Component Organization
Keep components small and reusable
Single Responsibility Principle
- Definition: Each component should have one specific responsibility or purpose.
- Implementation: Break down functionality so that each component performs a single, distinct task. For example:
- A button component should handle only the rendering of the button, not complex logic.
- Separate a form into multiple components: one for the form container, one for input fields, and another for the submit button.
Reusability
- Definition: Create components that can be used in multiple parts of your application without modification.
- Implementation:
Accept props for customization:
jsxconst Button = ({ label, onClick, style }) => ( <button onClick={onClick} style={style}> {label} </button> );
This button can now be reused with different
label
,onClick
, andstyle
values.Avoid hardcoding values: Don’t include data that is specific to a particular page or context inside reusable components. Pass data as props or through context.
Component Composition
Definition: Build complex components by combining smaller ones.
Implementation:
- Use smaller, focused components to create large components:
jsxconst Input = ({ type, placeholder, value, onChange }) => ( <input type={type} placeholder={placeholder} value={value} onChange={onChange} /> ); const SubmitButton = ({ onClick }) => <Button label="Submit" onClick={onClick} />; const Form = () => ( <form> <Input type="text" placeholder="Name" onChange={handleInputChange} /> <SubmitButton onClick={handleSubmit} /> </form> );
Container vs Presentational Components
- Definition: Separate components into "container" components (manage state, logic) and "presentational" components (display data).
- Implementations:
Container components:
- Manage state and logic.
- Pass data and functions as props to presentational components.
- Should not contain any JSX for rendering.
jsxconst UserListContainer = () => { const [users, setUsers] = useState([]); useEffect(() => { fetch('/api/users').then((res) => res.json()).then(setUsers); }, []); return <UserList users={users} />; };
Presentational components:
- Receive data and functions as props.
- Focus on rendering UI based on props.
- Should not manage state or logic.
jsxconst UserList = ({ users }) => ( <ul> {users.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
Folder Structure
Definition: Organize components by feature or domain to encourage reusability and readability.
Implementation:
Group by feature:
- Create a folder for each feature or domain.
- Place all components, styles, and tests related to that feature in the same folder.
src/ ├── components/ │ ├── Button/ │ │ ├── Button.js │ │ ├── Button.css │ │ └── Button.test.js │ ├── Form/ │ │ ├── Form.js │ │ ├── Form.css │ │ └── Form.test.js │ └── ...
Shared components:
- Create a
components
folder for shared components that are used across multiple features. - Place components that are not specific to a feature in this folder.
src/ ├── components/ │ ├── Button/ │ │ ├── Button.js │ │ ├── Button.css │ │ └── Button.test.js │ ├── Form/ │ │ ├── Form.js │ │ ├── Form.css │ │ └── Form.test.js │ └── ...
- Create a
Avoid Over-Engineering
- Definition: Ensure components are not too abstract or generic.
- Implementation:
- YAGNI (You Aren’t Gonna Need It):
- Only add complexity when it’s necessary.
- Avoid creating abstractions or patterns that are not currently needed.
- Avoid premature optimization:
- Optimize components for readability and maintainability first.
- Only optimize for performance when necessary.
- YAGNI (You Aren’t Gonna Need It):
Write Tests for Reusability
- Definition: Ensure your components work in different scenarios.
- Implementation: Use a testing library like React Testing Library:
jsx
test('renders Button with correct label', () => {
render(<Button label="Click Me" />);
expect(screen.getByText('Click Me')).toBeInTheDocument();
});
Use functional components and hooks whenever possible
Why Functional Components and Hooks?
Functional components with hooks have become the standard in React development for several reasons:
- Simpler syntax:
- Functional components are more concise and easier to read compared to class components.
- Hooks for State and Side Effects:
- Hooks like useState and useEffect provide a clean way to handle state and lifecycle methods without needing a class.
- Performance Benefits:
- Functional components with React.memo and hooks like useCallback and useMemo can optimize performance.
- Future-Proof
- React is moving towards functional components and hooks as the preferred standard.
Functional Components Over Class Components
Definition:
- Functional components are JavaScript functions that take props as input and return JSX.
- Replace complex class-based components with simpler functional alternatives.
Implementation:
Class Component:
jsxclass Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
Functional Component:
jsxconst Welcome = ({ name }) => { return <h1>Hello, {name}</h1>; };
Use Hooks for State Management
Definition: Hooks like useState and useReducer allow you to add state to functional components without the need for classes.
Implementation:
jsxconst Counter = () => { const [count, setCount] = React.useState(0); const increment = () => setCount((prevCount) => prevCount + 1); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); };
This replaces the this.state and this.setState syntax from class components with a simpler and more intuitive approach.