Core React Development: Project Setup, Hooks, State Management, and Routing
Bootstrapping a React Project
Install the latest project scaffolding tool globally or use npx for a temporary install:
npm install -g create-react-app
npx create-react-app my-app
For TypeScript support, append --template typescript.
Component Basics
Components are the building blocks. Use ES7 snippets in VS Code to generate class (rcc) or functional (rfc) skeletons.
Class component:
import React, { Component } from 'react';
export default class Welcome extends Component {
render() {
return <div>Welcome to React</div>;
}
}
Functional component:
import React from 'react';
export default function Greeting() {
return <div>Hello, Functional Component</div>;
}
Key rules: component names start with uppercase; render() is mandatory in class components; return a single root JSX element; wrap multiline returns in parentheses.
Composition and Nesting
Components can be nested freely:
class Sidebar extends Component {
render() {
return (
<div>
Sidebar
<Child />
</div>
);
}
}
function Banner() {
return <div>Banner</div>;
}
export default class App extends Component {
render() {
return (
<div>
<Sidebar />
<Banner />
</div>
);
}
}
Styling Approaches
Use inline styles or external CSS files. In JSX, class becomes className. Dynamic styles leverage JavaScript expressions inside {}:
const items = [
{ title: 'Carrot', type: 'vegetable', id: 1 },
{ title: 'Apple', type: 'fruit', id: 2 },
];
export default function ShoppingList() {
return (
<ul>
{items.map(item => (
<li
key={item.id}
style={{ color: item.type === 'fruit' ? 'orange' : 'green' }}
>
{item.title}
</li>
))}
</ul>
);
}
Event Handling
React uses synthetic events. Attach handlers via props like onClick, onMouseOver. In class components, prefer arrow function bindings to preserve this:
<button onClick={() => this.handleDelete(index)}>Delete</button>
Event delegation is used internally – a single listener at the root.
Importing and Exporting Modules
Default exports: one per file.
Named exports: multiple per file.
// ChildComponent.js
export function Avatar() { ... }
export default function Profile() { ... }
// Parent.js
import Profile from './ChildComponent';
import { Avatar } from './ChildComponent';
Working with Refs (Class Components)
Create a ref to access DOM nodes:
inputRef = React.createRef();
// Usage: <input ref={this.inputRef} />
// Read value: this.inputRef.current.value
State Management in Class Components
State is initialized as a class property:
state = { count: 0 };
Update with setState():
this.setState({ count: this.state.count + 1 });
setState is asynchronous in synchronous code and merges updates. Use a callback to access the latest state.
ToDo list example:
export default class TaskList extends Component {
textInput = React.createRef();
state = { tasks: [{ id: 1, text: 'Learn React' }] };
addTask = () => {
const newTask = { id: Date.now(), text: this.textInput.current.value };
this.setState(prev => ({ tasks: [...prev.tasks, newTask] }));
this.textInput.current.value = '';
};
removeTask = (id) => {
this.setState(prev => ({
tasks: prev.tasks.filter(task => task.id !== id),
}));
};
render() {
return (
<div>
<input ref={this.textInput} />
<button onClick={this.addTask}>Add</button>
<ul>
{this.state.tasks.map(task => (
<li key={task.id}>
{task.text}
<button onClick={() => this.removeTask(task.id)}>Del</button>
</li>
))}
</ul>
</div>
);
}
}
Props
Props are read‑only data passed from parent to child. In functional components:
function Greeting({ name, age }) {
return <p>{name}, {age} years old</p>;
}
In class components access via this.props.
PropTypes (install prop-types) validate types:
import PropTypes from 'prop-types';
Greeting.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
};
Default values can be provided via defaultProps or destructuring defaults.
Controlled vs. Uncontrolled Components
Controlled: value derived from state, updated via onChange:
const [email, setEmail] = useState('');
<input value={email} onChange={e => setEmail(e.target.value)} />
Uncontrolled: use refs to read values on demand; use defaultValue for initial value:
const fileRef = useRef();
<input type="file" ref={fileRef} />
Communication Between Components
- Parent → child: props
- Child → parent: callback functions passed via props
- Cross‑component: Context API or state management libraries
Context API
Create and provide a context value:
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
Consume in class component via ThemeContext.Consumer or in functional component via useContext.
Lifecycle Methods (Class Components)
Key methods:
componentDidMount– after first rendercomponentDidUpdate(prevProps, prevState)– after updatecomponentWillUnmount– cleanupshouldComponentUpdate(nextProps, nextState)– performance control
Modern alternatives: getDerivedStateFromProps, getSnapshotBeforeUpdate, and PureComponent.
Hooks
useState
const [items, setItems] = useState([]);
Remember: setter functions do not merge objects automatically; spread previous state when needed.
useEffect
Synchronise with external systems:
useEffect(() => {
const subscription = dataSource.subscribe();
return () => subscription.unsubscribe();
}, [dataSource]);
Dependency array controls re‑execution. Omit it to run after every render; pass [] to run once.
useLayoutEffect
Runs synchronously after DOM mutations but before paint; useful for measurements.
useCallback & useMemo
useCallback returns a memoized function; useMemo returns a memoized value. Both help avoid unnecessary re‑renders.
const expensiveValue = useMemo(() => compute(items), [items]);
const stableHandler = useCallback(() => doSomething(id), [id]);
useRef
Holds a mutable value that persists across renders without triggering re‑renders. Also used for DOM references.
useContext
Access context value directly:
const theme = useContext(ThemeContext);
useReducer
Alternative to useState for complex state logic:
const [state, dispatch] = useReducer(reducer, initialState);
Combine with useContext for global state.
Custom Hooks
Extract reusable logic; names must start with use.
React Router v6
Install: npm install react-router-dom@6
Basic setup:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
Nested routes use <Outlet />:
<Route path="dashboard" element={<Dashboard />}>
<Route index element={<Overview />} />
<Route path="stats" element={<Stats />} />
</Route>
Navigation:
- Declarative:
<NavLink to="/home">Home</NavLink> - Programmatic:
const navigate = useNavigate(); navigate('/home');
Route parameters: useParams(); query strings: useSearchParams(); state: pass via navigate(path, { state: {...} }) and read with useLocation().
Lazy loading:
const LazyComponent = React.lazy(() => import('./Component'));
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
Protected routes: wrap target component with an authentication check.
State Management with Redux
Core Principles
- Single source of truth (store)
- State is read‑only, changed only by dispatching actions
- Pure reducer functions compute new state
Basic Setup
import { createStore } from 'redux';
const initialState = { visible: true };
function visibilityReducer(state = initialState, action) {
switch (action.type) {
case 'SHOW':
return { ...state, visible: true };
case 'HIDE':
return { ...state, visible: false };
default:
return state;
}
}
const store = createStore(visibilityReducer);
export default store;
Connecting React Components
Use react-redux Provider, useSelector, and useDispatch:
// index.js
<Provider store={store}>
<App />
</Provider>
// Component
import { useSelector, useDispatch } from 'react-redux';
const visible = useSelector(state => state.visible);
const dispatch = useDispatch();
Combining Reducers
import { combineReducers } from 'redux';
const rootReducer = combineReducers({ visibility: visibilityReducer, user: userReducer });
Middleware for Async Actions
Redux Thunk: allows action creators to return functions:
const fetchData = () => async dispatch => {
const data = await api.get();
dispatch({ type: 'LOAD_SUCCESS', payload: data });
};
Redux Saga: uses generator functions for more complex async flows.
Redux Toolkit (RTK)
Simplifies Redux with createSlice and configureStore:
import { createSlice, configureStore } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
incremented: state => { state.value += 1; },
decremented: state => { state.value -= 1; },
},
});
export const { incremented, decremented } = counterSlice.actions;
const store = configureStore({ reducer: { counter: counterSlice.reducer } });
Enables mutative logic inside reducers via Immer.
Persistence can be added with redux‑persist.
MobX as an Alternative
Observable state and automatic tracking:
import { makeAutoObservable } from 'mobx';
class CartStore {
items = [];
constructor() {
makeAutoObservable(this);
}
addItem(item) {
this.items.push(item);
}
}
Use observer HOC (class) or useObserver (functional) to re‑render on changes.
Immutable.js
Provides persistent data structures. Often replaced by modern JavaScript spread syntax or Immer.
TypeScript with React
Create a project:
npx create-react-app my-app --template typescript
Typing props and state:
interface Props {
title: string;
count?: number;
}
const Header: React.FC<Props> = ({ title, count = 0 }) => (
<h1>{title} - {count}</h1>
);
For class components:
interface State {
loading: boolean;
}
class DataView extends React.Component<Props, State> { ... }
Typing Redux requires declaring store types and using typed hooks.
Supplementary Techniques
- Portals:
ReactDOM.createPortal(child, domNode)renders outside parent hierarchy. - forwardRef: pass ref through to a child component.
- Performance:
React.memofor functional components,PureComponentfor class components. - Event Bus: simple event emitter for decoupled communication (use sparinlgy).
Frameworks: Dva.js & Umi.js
Dva.js (deprecated) and Umi.js provide integrated routing, state management (Dva model), and mock capabilities out of the box. Umi uses convention‑based routing and a centralized configuration file (.umirc.ts).