I'm Samuel FajreldinesI am a specialist in the entire JavaScript and TypeScript ecosystem (including Node.js, React, Angular and Vue.js) I am expert in AI and in creating AI integrated solutions I am expert in DevOps and Serverless Architecture (AWS, Google Cloud and Azure) I am expert in PHP and its frameworks (such as Codeigniter and Laravel). |
Samuel FajreldinesI am a specialist in the entire JavaScript and TypeScript ecosystem. I am expert in AI and in creating AI integrated solutions. I am expert in DevOps and Serverless Architecture I am expert in PHP and its frameworks.
|
Modern web applications have become increasingly complex, often requiring efficient state management solutions to handle their growing data needs. Redux has emerged as a popular library for managing state in JavaScript and TypeScript applications, particularly those built with React. This comprehensive guide will delve into Redux, exploring its core concepts and demonstrating how to build a complete example system using Redux in a React application.
Redux is a predictable state container for JavaScript applications. It helps you manage the state of your app in a single, centralized place called the store. By using Redux, you can make your application's state changes more predictable and easier to debug. Redux is often used with React, but it can be integrated with any other view library or framework.
Understanding Redux requires familiarity with its core concepts:
The store is the single source of truth in a Redux application. It holds the entire state tree of your app. Because there's only one store, the state management becomes more predictable and easier to maintain.
Actions are plain JavaScript objects that represent an intention to change the state. An action must have a type
property that indicates the type of action being performed. Additional data required to perform the action can be included in the action object.
const addAction = {
type: 'ADD_TODO',
payload: {
text: 'Learn Redux',
},
};
Reducers are pure functions that take the previous state and an action as arguments and return a new state. They specify how the application's state changes in response to actions sent to the store.
function todoReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload];
default:
return state;
}
}
The state tree is a JavaScript object that represents the entire state of the application. Since the state is immutable, any state changes return a new state object rather than modifying the existing one.
Dispatch is a method available on the store that allows you to send actions to the store. When an action is dispatched, the store's reducer processes it and updates the state accordingly.
store.dispatch(addAction);
Let's integrate Redux into a React application.
First, install Redux and React Redux (bindings for React):
npm install redux react-redux
For TypeScript users, also install the types:
npm install --save-dev @types/redux @types/react-redux
Create a store.js
file and configure the store:
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
export default store;
Wrap your root component with the Provider
from react-redux
to make the store available to all components:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Define action types:
export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
Create action creators:
export const addTodo = (text) => ({
type: ADD_TODO,
payload: { text },
});
export const removeTodo = (id) => ({
type: REMOVE_TODO,
payload: { id },
});
Create reducers to handle state changes:
import { ADD_TODO, REMOVE_TODO } from '../actions';
const initialState = {
todos: [],
};
function todoReducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
const newTodo = {
id: Date.now(),
text: action.payload.text,
};
return { ...state, todos: [...state.todos, newTodo] };
case REMOVE_TODO:
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.payload.id),
};
default:
return state;
}
}
export default todoReducer;
useSelector
and useDispatch
HooksThe useSelector
hook allows you to extract data from the Redux store state, and useDispatch
gives you access to the dispatch
function.
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, removeTodo } from './actions';
function TodoList() {
const todos = useSelector((state) => state.todos);
const dispatch = useDispatch();
const handleAddTodo = (text) => {
dispatch(addTodo(text));
};
const handleRemoveTodo = (id) => {
dispatch(removeTodo(id));
};
// Component rendering logic...
}
Redux middleware provides a third-party extension point between dispatching an action and the moment it reaches the reducer.
Redux Thunk is a middleware that allows you to write action creators that return a function instead of an action. This is useful for handling asynchronous actions.
Install Redux Thunk:
npm install redux-thunk
Configure the store with middleware:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Create an async action creator:
export const fetchTodos = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_TODOS_REQUEST' });
try {
const response = await fetch('/api/todos');
const data = await response.json();
dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_TODOS_FAILURE', payload: error });
}
};
};
Let's build a simple Todo application to illustrate Redux in action.
Initialize a new React app:
npx create-react-app redux-todo-app
cd redux-todo-app
npm install redux react-redux redux-thunk
In src/actions/index.js
:
export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
export const FETCH_TODOS_REQUEST = 'FETCH_TODOS_REQUEST';
export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS';
export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE';
export const addTodo = (text) => ({
type: ADD_TODO,
payload: { text },
});
export const removeTodo = (id) => ({
type: REMOVE_TODO,
payload: { id },
});
export const fetchTodos = () => {
return async (dispatch) => {
dispatch({ type: FETCH_TODOS_REQUEST });
try {
const response = await fetch('/api/todos');
const data = await response.json();
dispatch({ type: FETCH_TODOS_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: FETCH_TODOS_FAILURE, payload: error });
}
};
};
In src/reducers/todoReducer.js
:
import {
ADD_TODO,
REMOVE_TODO,
FETCH_TODOS_REQUEST,
FETCH_TODOS_SUCCESS,
FETCH_TODOS_FAILURE,
} from '../actions';
const initialState = {
todos: [],
loading: false,
error: null,
};
function todoReducer(state = initialState, action) {
switch (action.type) {
case FETCH_TODOS_REQUEST:
return { ...state, loading: true };
case FETCH_TODOS_SUCCESS:
return { ...state, loading: false, todos: action.payload };
case FETCH_TODOS_FAILURE:
return { ...state, loading: false, error: action.payload };
case ADD_TODO:
const newTodo = {
id: Date.now(),
text: action.payload.text,
};
return { ...state, todos: [...state.todos, newTodo] };
case REMOVE_TODO:
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.payload.id),
};
default:
return state;
}
}
export default todoReducer;
In src/reducers/index.js
:
import { combineReducers } from 'redux';
import todoReducer from './todoReducer';
const rootReducer = combineReducers({
todoState: todoReducer,
});
export default rootReducer;
In src/store.js
:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
In src/App.js
:
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, removeTodo, fetchTodos } from './actions';
function App() {
const [input, setInput] = useState('');
const { todos, loading, error } = useSelector((state) => state.todoState);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchTodos());
}, [dispatch]);
const handleAddTodo = () => {
if (input.trim() !== '') {
dispatch(addTodo(input));
setInput('');
}
};
const handleRemoveTodo = (id) => {
dispatch(removeTodo(id));
};
if (loading) return <p>Loading todos...</p>;
if (error) return <p>Error fetching todos: {error.message}</p>;
return (
<div>
<h1>Todo List</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo..."
/>
<button onClick={handleAddTodo}>Add</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}{' '}
<button onClick={() => handleRemoveTodo(todo.id)}>Remove</button>
</li>
))}
</ul>
</div>
);
}
export default App;
We've already added an async action fetchTodos
to simulate fetching todos from an API. In a real application, you'd replace the fetch URL with your actual API endpoint.
combineReducers
to manage separate parts of the state with different reducers.Redux is a powerful library that provides a predictable state container for JavaScript applications. By centralizing the state and enforcing strict unidirectional data flow, Redux makes applications easier to understand and debug. This comprehensive guide covered the core concepts of Redux, how to integrate it into a React application, and how to build a complete example system—a Todo application.
By applying the best practices discussed, you can harness the full potential of Redux in your projects, leading to scalable and maintainable applications. Whether you're working on a small project or a large enterprise application, Redux offers the tools necessary for effective state management.
About Me
Since I was a child, I've always wanted to be an inventor. As I grew up, I specialized in information systems, an area which I fell in love with and live around it. I am a full-stack developer and work a lot with devops, i.e., I'm a kind of "jack-of-all-trades" in IT. Wherever there is something cool or new, you'll find me exploring and learning... I am passionate about life, family, and sports. I believe that true happiness can only be achieved by balancing these pillars. I am always looking for new challenges and learning opportunities, and would love to connect with other technology professionals to explore possibilities for collaboration. If you are looking for a dedicated and committed full-stack developer with a passion for excellence, please feel free to contact me. It would be a pleasure to talk with you! |
SecurityScoreCard
Nov. 2023 - Present
New York, United States
Senior Software Engineer
I joined SecurityScorecard, a leading organization with over 400 employees, as a Senior Full Stack Software Engineer. My role spans across developing new systems, maintaining and refactoring legacy solutions, and ensuring they meet the company's high standards of performance, scalability, and reliability.
I work across the entire stack, contributing to both frontend and backend development while also collaborating directly on infrastructure-related tasks, leveraging cloud computing technologies to optimize and scale our systems. This broad scope of responsibilities allows me to ensure seamless integration between user-facing applications and underlying systems architecture.
Additionally, I collaborate closely with diverse teams across the organization, aligning technical implementation with strategic business objectives. Through my work, I aim to deliver innovative and robust solutions that enhance SecurityScorecard's offerings and support its mission to provide world-class cybersecurity insights.
Technologies Used:
Node.js Terraform React Typescript AWS Playwright and Cypress