100+ React Tips & Tricks For Beginners To Experts ✨
React, a popular JavaScript library for building user interfaces, is widely used due to its efficiency, flexibility, and powerful features. Whether you’re a beginner or an expert, there’s always something new to learn. In this guide, we’ll explore 101 tips and tricks to help you master React, covering everything from the basics to advanced techniques.
Table of Contents
-
Understanding React Basics
- 1.1. JSX: The Basics
- 1.2. Component Lifecycle
- 1.3. Functional vs. Class Components
- 1.4. Props and State
-
Working with Components
- 2.1. Reusable Components
- 2.2. PropTypes for Type Checking
- 2.3. Controlled vs. Uncontrolled Components
- 2.4. Conditional Rendering
-
State Management
- 3.1. Using
useState
anduseReducer
- 3.2. Global State with Context API
- 3.3. Redux for Complex State Management
- 3.4. Zustand: A Lightweight Alternative to Redux
- 3.1. Using
-
Handling Side Effects
- 4.1. Using
useEffect
Hook - 4.2. Cleanup Functions in
useEffect
- 4.3. Fetching Data with
useEffect
- 4.4. Optimizing Performance with Memoization
- 4.1. Using
-
Advanced Component Patterns
- 5.1. Higher-Order Components (HOCs)
- 5.2. Render Props
- 5.3. Compound Components
- 5.4. Custom Hooks
-
Optimizing Performance
- 6.1. Lazy Loading Components
- 6.2. Code Splitting with React.lazy and Suspense
- 6.3. Memoizing with
React.memo
- 6.4. Using
React.PureComponent
for Performance Gains
-
Routing in React
- 7.1. Basic Routing with React Router
- 7.2. Nested Routes
- 7.3. Dynamic Routing
- 7.4. Programmatic Navigation
-
Form Handling
- 8.1. Handling Form Inputs
- 8.2. Validation with Formik and Yup
- 8.3. Using Custom Hooks for Form Management
- 8.4. File Uploads in React
-
Testing React Applications
- 9.1. Unit Testing with Jest
- 9.2. Component Testing with React Testing Library
- 9.3. Mocking API Calls in Tests
- 9.4. End-to-End Testing with Cypress
-
Building and Deploying React Apps
- 10.1. Setting Up with Create React App
- 10.2. Custom Webpack Configurations
- 10.3. Optimizing for Production
- 10.4. Deploying to Vercel, Netlify, and Other Platforms
-
Styling React Components
- 11.1. CSS Modules in React
- 11.2. Styled Components for Modern UI
- 11.3. Tailwind CSS with React
- 11.4. Theming with Styled Components
-
React with TypeScript
- 12.1. Setting Up TypeScript in React
- 12.2. TypeScript Generics in React
- 12.3. Advanced Types in React
- 12.4. Migrating a JavaScript Project to TypeScript
-
Accessibility in React
- 13.1. Keyboard Navigation and Focus Management
- 13.2. Using ARIA Attributes in React
- 13.3. Accessible Forms and Buttons
- 13.4. Testing Accessibility with Lighthouse
-
React and SEO
- 14.1. Server-Side Rendering (SSR) with Next.js
- 14.2. Static Site Generation (SSG) with Next.js
- 14.3. Managing Metadata with React Helmet
- 14.4. Optimizing Images for SEO
-
React Best Practices
- 15.1. Structuring Your React Project
- 15.2. Using ESLint and Prettier
- 15.3. Avoiding Common Pitfalls
- 15.4. Writing Clean, Maintainable Code
1. Understanding React Basics
1.1. JSX: The Basics
JSX (JavaScript XML) allows you to write HTML within JavaScript. It makes your code more readable and expressive. Here's an example:
function HelloWorld() {
return <h1>Hello, World!</h1>;
}
1.2. Component Lifecycle
Understanding the lifecycle of a React component is crucial. In class components, lifecycle methods like componentDidMount
, componentDidUpdate
, and componentWillUnmount
are used to manage side effects.
1.3. Functional vs. Class Components
React introduced Hooks in version 16.8, making functional components more powerful. While class components are still used, functional components with hooks are now the preferred way to build React components.
// Class Component
class MyComponent extends React.Component {
render() {
return <div>Class Component</div>;
}
}
// Functional Component
function MyComponent() {
return <div>Functional Component</div>;
}
1.4. Props and State
Props are used to pass data from parent to child components, while state is used to manage data within a component. Here’s a simple example:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return <Welcome name="Alice" />;
}
2. Working with Components
2.1. Reusable Components
Creating reusable components is key to maintaining a clean and scalable codebase. For example, a button component that can be used throughout your app:
function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
function App() {
return <Button label="Click Me" onClick={() => alert('Button Clicked!')} />;
}
2.2. PropTypes for Type Checking
PropTypes allow you to enforce type checking in your components, helping to catch errors early:
import PropTypes from 'prop-types';
function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
Greeting.propTypes = {
name: PropTypes.string.isRequired,
};
2.3. Controlled vs. Uncontrolled Components
Controlled components are those where form data is handled by the state in React, while uncontrolled components maintain their own state.
// Controlled Component
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
// Uncontrolled Component
function UncontrolledInput() {
const inputRef = useRef();
return <input ref={inputRef} />;
}
2.4. Conditional Rendering
Conditionally render components based on certain criteria:
function App({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in.</h1>}
</div>
);
}
3. State Management
3.1. Using useState
and useReducer
useState
is the simplest way to manage state in functional components. For more complex state logic, useReducer
is recommended.
// useState Example
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// useReducer Example
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
3.2. Global State with Context API
Context API is useful for passing data through the component tree without having to pass props manually at every level.
const ThemeContext = createContext();
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return
(
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Themed Button</button>;
}
3.3. Redux for Complex State Management
Redux is a powerful library for managing complex state in large applications.
import { createStore } from 'redux';
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
const store = createStore(counterReducer);
store.dispatch({ type: 'increment' });
console.log(store.getState()); // { count: 1 }
3.4. Zustand: A Lightweight Alternative to Redux
Zustand is a small, fast state-management library for React.
import create from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
function Counter() {
const { count, increment } = useStore();
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
4. Handling Side Effects
4.1. Using useEffect
Hook
useEffect
is used for managing side effects such as data fetching, subscriptions, and manual DOM changes.
function App() {
useEffect(() => {
document.title = "React App";
}, []);
return <div>Check the document title!</div>;
}
4.2. Cleanup Functions in useEffect
Always use cleanup functions to avoid memory leaks, especially when dealing with subscriptions or timers.
function Timer() {
useEffect(() => {
const timer = setInterval(() => console.log("Tick"), 1000);
return () => clearInterval(timer);
}, []);
return <div>Check the console for ticks!</div>;
}
4.3. Fetching Data with useEffect
You can fetch data within useEffect
, and manage the state accordingly.
function DataFetchingComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
}, []);
return (
<div>
{data ? <pre>{JSON.stringify(data, null, 2)}</pre> : "Loading..."}
</div>
);
}
4.4. Optimizing Performance with Memoization
Use memoization to optimize the performance of your React app by preventing unnecessary re-renders.
const MemoizedComponent = React.memo(function MyComponent({ value }) {
console.log('Rendered');
return <div>{value}</div>;
});
5. Advanced Component Patterns
5.1. Higher-Order Components (HOCs)
HOCs are functions that take a component and return a new component with added functionality.
function withLogging(WrappedComponent) {
return function(props) {
console.log("Rendering", WrappedComponent.name);
return <WrappedComponent {...props} />;
};
}
const MyComponentWithLogging = withLogging(MyComponent);
5.2. Render Props
Render props allow you to share code between components using a prop whose value is a function.
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: '100vh' }} onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
}
function App() {
return (
<MouseTracker render={({ x, y }) => (
<h1>Mouse position: ({x}, {y})</h1>
)} />
);
}
5.3. Compound Components
Compound components are a pattern where components work together to manage their state and behavior.
function Tabs({ children }) {
const [activeTab, setActiveTab] = useState(0);
return (
<div>
<div>
{React.Children.map(children, (child, index) => (
<button onClick={() => setActiveTab(index)}>
{child.props.title}
</button>
))}
</div>
{React.Children.map(children, (child, index) =>
index === activeTab ? child : null
)}
</div>
);
}
function Tab({ children }) {
return <div>{children}</div>;
}
function App() {
return (
<Tabs>
<Tab title="Tab 1">Content 1</Tab>
<Tab title="Tab 2">Content 2</Tab>
<Tab title="Tab 3">Content 3</Tab>
</Tabs>
);
}
5.4. Custom Hooks
Custom hooks allow you to encapsulate logic and reuse it across different components.
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return { count, increment, decrement };
}
function Counter() {
const { count, increment, decrement } = useCounter();
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
6. Optimizing Performance
6.1. Lazy Loading Components
Lazy loading helps improve performance by splitting your code into smaller chunks that are loaded as needed.
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}
6.2. Code Splitting with React.lazy and Suspense
Code splitting can be achieved using React.lazy
and Suspense
, allowing parts of your app to load only when needed.
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
);
}
6.3. Memoizing with React.memo
React.memo
can be used to prevent unnecessary re-renders of components that do not change.
const MemoizedComponent = React.memo(function MyComponent({ value }) {
console.log('Rendered');
return <div>{value}</div>;
});
6.4. Using React.PureComponent
for Performance Gains
React.PureComponent
is similar to React.Component
, but it performs a shallow comparison of props and state to prevent unnecessary re-renders.
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}
7. Routing in React
7.1. Basic Routing with React Router
React Router is the most popular library for routing in React applications.
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
function Home() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Router>
);
}
7.2. Nested Routes
You can define nested routes within your application.
function Topics() {
return (
<div>
<h2>Topics</h2>
<Switch>
<Route path="/topics/topic1">
<Topic1 />
</Route>
<Route path="/topics/topic2">
<Topic2 />
</Route>
</Switch>
</div>
);
}
7.3. Dynamic Routing
Dynamic routing allows you to create routes that accept parameters.
function Topic({ match }) {
return <h3>Requested Param: {match.params.topicId}</h3>;
}
function Topics() {
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to="/topics/topic1">Topic 1</Link>
</li>
<li>
<Link to="/topics/topic2">Topic 2</Link>
</li>
</ul>
<Switch>
<Route path="/topics/:topicId" component={Topic} />
</Switch>
</div>
);
}
7.4. Protected Routes
Protected routes are routes that require authentication to access.
function PrivateRoute({ component: Component, ...rest }) {
const isAuthenticated = false; // Replace with your auth logic
return (
<Route
{...rest}
render={(props) =>
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
}
8. Testing in React
8.1. Writing Unit Tests with Jest
Jest is a popular testing framework for React applications.
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
test('renders learn react link', () => {
render(<MyComponent />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
8.2. Testing Components with React Testing Library
React Testing Library is a tool for testing React components in a way that resembles how users interact with them.
import { render, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter', () => {
const { getByText } = render(<Counter />);
const button = getByText(/increment/i);
fireEvent.click(button);
expect(getByText(/count: 1/i)).toBeInTheDocument();
});
8.3. Mocking API Calls
You can mock API calls in your tests to simulate different scenarios.
import { render, screen, waitFor } from '@testing-library/react';
import App from './App';
import fetchMock from 'jest-fetch-mock';
fetchMock.enableMocks();
beforeEach(() => {
fetch.resetMocks();
});
test('loads and displays data', async () => {
fetch.mockResponseOnce(JSON.stringify({ data: '12345' }));
render(<App />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
await waitFor(() => screen.getByText('12345'));
expect(screen.getByText('12345')).toBeInTheDocument();
});
8.4. Snapshot Testing
Snapshot testing ensures that your UI does not change unexpectedly.
import renderer from 'react-test-renderer';
import MyComponent from './MyComponent';
test('renders correctly', () => {
const tree = renderer.create(<MyComponent />).toJSON();
expect(tree).toMatchSnapshot();
});
9. Managing Forms in React
9.1. Controlled vs. Uncontrolled Components
Controlled components are those whose form data is handled by the React component's state.
function MyForm() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
return <input type="text" value={value} onChange={handleChange} />;
}
Uncontrolled components are those where the form data is handled by the DOM itself.
function MyForm() {
const inputRef = useRef();
const handleSubmit = (event) => {
event.preventDefault();
alert(inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
}
9.2. Using Formik for Form Management
Formik is a popular library for managing forms in React.
import { Formik, Form, Field } from 'formik';
function MyForm() {
return (
<Formik
initialValues={{ name: '' }}
onSubmit={(values) => console.log(values)}
>
<Form>
<Field name="name" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
}
9.3. Validation with Yup
Yup is a JavaScript schema builder for validation and parsing.
import * as Yup from 'yup';
const validationSchema = Yup.object({
name: Yup.string().required('Name is required'),
});
<Formik
initialValues={{ name: '' }}
validationSchema={validationSchema}
onSubmit={(values) => console.log(values)}
>
<Form>
<Field name="name" />
<ErrorMessage name="name" />
<button type="submit">Submit</button>
</Form>
</Formik>;
9.4. Handling Multiple Inputs
You can manage multiple inputs by using dynamic keys in your state.
function MyForm() {
const [formData, setFormData] = useState({});
const handleChange = (event) => {
setFormData({
...formData,
[event.target.name]: event.target.value,
});
};
return (
<form>
<input name="firstName" onChange={handleChange} />
<input name="lastName" onChange={handleChange} />
</form>
);
}
10. React and TypeScript
10.1. Adding TypeScript to Your React Project
To add TypeScript to an existing React project, install TypeScript and rename your .js
files to .tsx
.
npm install --save typescript @types/node @types/react @types/react-dom
10.2. Type Checking with TypeScript
TypeScript allows you to add static types to your React components.
type GreetingProps = {
name: string;
};
function Greeting({ name }: GreetingProps) {
return <h1>Hello, {name}</h1>;
}
10.3. Using Interfaces for Props
You can use interfaces instead of types to define the props for your components.
interface GreetingProps {
name: string;
}
function Greeting({ name }: GreetingProps) {
return <h1>Hello, {name}</h1>;
}
10.4. Typing State and Hooks
You can type your state and hooks in TypeScript for better type safety.
function Counter() {
const [count, setCount] = useState<number>(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
11. React and GraphQL
11.1. Fetching Data with Apollo Client
Apollo Client is a popular library for working with GraphQL in React.
import { ApolloClient, InMemoryCache, ApolloProvider, useQuery, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://example.com/graphql',
cache: new InMemoryCache(),
});
const GET_DATA = gql`
query GetData {
data {
id
value
}
}
`;
function DataComponent() {
const { loading, error, data } = useQuery(GET_DATA);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return (
<ul>
{data.data.map(({ id, value }) => (
<li key={id}>{value}</li>
))}
</ul>
);
}
function App() {
return (
<ApolloProvider client={client}>
<DataComponent />
</ApolloProvider>
);
}
11.2. Writing GraphQL Mutations
Mutations in GraphQL are used to modify server-side data.
const ADD_ITEM = gql`
mutation AddItem($value: String!) {
addItem(value: $value) {
id
value
}
}
`;
function AddItem() {
let input;
const [addItem, { data }] = useMutation(ADD_ITEM);
return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
addItem({ variables: { value: input.value } });
input.value = '';
}}
>
<input
ref={(node) => {
input = node;
}}
/>
<button type="submit">Add Item</button>
</form>
</div>
);
}
11.3. Using GraphQL Fragments
Fragments allow you to share common fields between multiple queries or mutations.
const ITEM_DETAILS = gql`
fragment ItemDetails on Item {
id
value
}
`;
const GET_ITEMS = gql`
query GetItems {
items {
...ItemDetails
}
}
${ITEM_DETAILS}
`;
const ADD_ITEM = gql`
mutation AddItem($value: String!) {
addItem(value: $value) {
...ItemDetails
}
}
${ITEM_DETAILS}
`;
11.4. Handling Errors in GraphQL
Apollo Client provides tools to handle errors in your GraphQL queries and mutations.
function DataComponent() {
const { loading, error, data } = useQuery(GET_DATA);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return <div>Data loaded successfully!</div>;
}
12. Miscellaneous Tips and Tricks
12.1. Using React.Fragment
to Avoid Extra Nodes
Use React.Fragment
to group multiple elements without adding extra nodes to the
DOM.
function MyComponent() {
return (
<React.Fragment>
<h1>Hello</h1>
<p>World</p>
</React.Fragment>
);
}
12.2. Using dangerouslySetInnerHTML
Safely
dangerouslySetInnerHTML
is a React feature that allows you to set HTML directly from a string. Use it cautiously to avoid XSS attacks.
function MyComponent() {
return <div dangerouslySetInnerHTML={{ __html: 'Hello <strong>World</strong>' }} />;
}
12.3. Conditionally Adding Attributes
You can conditionally add attributes to your JSX elements.
function MyComponent({ disabled }) {
return <button disabled={disabled ? true : undefined}>Click Me</button>;
}
12.4. Optimizing Performance with React.memo
React.memo
is a higher-order component that memoizes your component and only re-renders it when its props change.
const MyComponent = React.memo(function MyComponent({ name }) {
return <div>Hello, {name}</div>;
});
12.5. Using React.lazy
for Code Splitting
React.lazy
is a function that lets you render a dynamic import as a regular component. It's useful for code-splitting.
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</React.Suspense>
);
}
12.6. Using the Context API for State Management
The Context API is a React feature that lets you pass data through the component tree without having to pass props down manually at every level.
const MyContext = React.createContext();
function MyProvider({ children }) {
const [state, setState] = useState('Hello World');
return (
<MyContext.Provider value={{ state, setState }}>
{children}
</MyContext.Provider>
);
}
function MyComponent() {
const { state } = useContext(MyContext);
return <div>{state}</div>;
}
12.7. Using Higher-Order Components (HOCs)
Higher-order components are functions that take a component and return a new component with additional props or functionality.
function withExtraProps(Component) {
return function (props) {
return <Component {...props} extra="extra value" />;
};
}
const MyComponent = withExtraProps(function ({ extra }) {
return <div>{extra}</div>;
});
12.8. Managing Side Effects with useEffect
The useEffect
hook is used to handle side effects in function components, such as fetching data or setting up event listeners.
function MyComponent() {
useEffect(() => {
document.title = 'Hello World';
}, []);
return <div>Hello World</div>;
}
12.9. Cleaning Up in useEffect
You can return a cleanup function from useEffect
to clean up side effects when a component unmounts.
function MyComponent() {
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return <div>Check the console</div>;
}
12.10. Handling Errors Gracefully
You can handle errors gracefully in React by using error boundaries.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function MyComponent() {
if (Math.random() > 0.7) {
throw new Error('Oops!');
}
return <div>Hello World</div>;
}
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
13. Conclusion
React is a powerful library for building modern web applications, offering a flexible component-based architecture. Whether you're building simple UI components or complex applications, understanding these core concepts and techniques will help you create scalable and maintainable projects. Happy coding!