Skip to content

Commit

Permalink
feat: integrate custom backend with createAsyncThunk and createSlice
Browse files Browse the repository at this point in the history
  • Loading branch information
Valik3201 committed Feb 19, 2024
1 parent 4111af9 commit b36ce41
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 164 deletions.
91 changes: 66 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.7",
"framer-motion": "^11.0.5",
"lucide-react": "^0.331.0",
"nanoid": "^5.0.4",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-redux": "^9.1.0",
"react-scripts": "5.0.1",
"redux-persist": "^6.0.0",
"web-vitals": "^2.1.3"
},
"scripts": {
Expand Down
9 changes: 5 additions & 4 deletions src/components/ContactForm.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addContact } from '../redux/reducers/contactsSlice';
import { nanoid } from 'nanoid';
import { addContact } from '../redux/operations/operations';
import { selectContacts } from '../redux/selectors/contactsSelectors';
import ModalAlert from './ModalAlert';

Expand All @@ -16,6 +15,7 @@ import { Plus } from 'lucide-react';
*/
const ContactForm = () => {
const dispatch = useDispatch();

const contacts = useSelector(selectContacts);
const [name, setName] = useState('');
const [phone, setPhone] = useState('');
Expand All @@ -31,12 +31,11 @@ const ContactForm = () => {
const handleSubmit = event => {
event.preventDefault();

const existingContact = contacts.find(
const existingContact = contacts.items.find(
contact => contact.name === name || contact.phone === phone
);

if (!existingContact) {
dispatch(addContact({ id: nanoid(), name, phone }));
setName('');
setPhone('');
} else {
Expand All @@ -52,6 +51,8 @@ const ContactForm = () => {
setIsModalOpen(true);
}
}

dispatch(addContact({ name, phone }));
};

return (
Expand Down
49 changes: 37 additions & 12 deletions src/components/ContactList.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { deleteContact } from '../redux/reducers/contactsSlice';
import { deleteContact, fetchContacts } from '../redux/operations/operations';
import {
selectContacts,
selectFilter,
Expand All @@ -12,6 +13,7 @@ import {
TableBody,
TableRow,
TableCell,
Spinner,
} from '@nextui-org/react';
import { Button } from '@nextui-org/react';
import { Trash2 } from 'lucide-react';
Expand All @@ -21,34 +23,33 @@ import { Trash2 } from 'lucide-react';
* @returns {JSX.Element} The JSX element representing the contact list.
*/
const ContactList = () => {
const contacts = useSelector(selectContacts);
const filter = useSelector(selectFilter);
const dispatch = useDispatch();

/**
* Filters contacts based on the filter input.
* @type {Array}
*/
const filteredContacts = contacts.filter(contact =>
contact.name.toLowerCase().includes(filter.toLowerCase())
const { items, isLoading, error } = useSelector(selectContacts);

useEffect(() => {
dispatch(fetchContacts());
}, [dispatch]);

const filteredContacts = items.filter(i =>
i.name.toLowerCase().includes(filter?.toLowerCase())
);

/**
* Sorts contacts alphabetically by name.
* @type {Array}
*/
const sortedContacts = filteredContacts
.slice()
?.slice()
.sort((a, b) => a.name.localeCompare(b.name));

/**
* Handles contact deletion.
* @param {string} id - The id of the contact to be deleted.
* @returns {void}
*/
const handleDelete = id => {
dispatch(deleteContact(id));
};
const handleDelete = id => dispatch(deleteContact(id));

return (
<Table
Expand All @@ -62,6 +63,30 @@ const ContactList = () => {
<TableColumn className="w-1/5 text-center">ACTIONS</TableColumn>
</TableHeader>
<TableBody emptyContent={'No contacts to display.'}>
{isLoading && (
<TableRow>
<TableCell aria-colspan={3} colSpan={3} className="text-center">
<Spinner />
</TableCell>
<TableCell className="hidden"></TableCell>
<TableCell className="hidden"></TableCell>
</TableRow>
)}

{error && (
<TableRow>
<TableCell
aria-colspan={3}
colSpan={3}
className="text-center text-danger"
>
{error}
</TableCell>
<TableCell className="hidden"></TableCell>
<TableCell className="hidden"></TableCell>
</TableRow>
)}

{sortedContacts.map(contact => (
<TableRow key={contact.id}>
<TableCell>{contact.name}</TableCell>
Expand Down
10 changes: 3 additions & 7 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './redux/store';
import { store } from './redux/store';
import App from './components/App';
import { NextUIProvider } from '@nextui-org/react';
import './index.css';
Expand All @@ -17,11 +16,8 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<NextUIProvider>
{/* Redux Provider for store */}
<Provider store={store}>
{/* PersistGate for persisting Redux store */}
<PersistGate loading={null} persistor={persistor}>
{/* Main application component */}
<App />
</PersistGate>
{/* Main application component */}
<App />
</Provider>
</NextUIProvider>
</React.StrictMode>
Expand Down
41 changes: 41 additions & 0 deletions src/redux/operations/operations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import axios from 'axios';
import { createAsyncThunk } from '@reduxjs/toolkit';

axios.defaults.baseURL = 'https://65d1fef9987977636bfbc75f.mockapi.io';

export const fetchContacts = createAsyncThunk(
'contacts/fetchAll',

async (_, thunkAPI) => {
try {
const response = await axios.get('/contacts');
return response.data;
} catch (e) {
return thunkAPI.rejectWithValue(e.message);
}
}
);

export const addContact = createAsyncThunk(
'contacts/addContact',
async ({ name, phone }, thunkAPI) => {
try {
const response = await axios.post('/contacts', { name, phone });
return response.data;
} catch (e) {
return thunkAPI.rejectWithValue(e.message);
}
}
);

export const deleteContact = createAsyncThunk(
'contacts/deleteContact',
async (contactId, thunkAPI) => {
try {
const response = await axios.delete(`/contacts/${contactId}`);
return response.data;
} catch (e) {
return thunkAPI.rejectWithValue(e.message);
}
}
);
Loading

0 comments on commit b36ce41

Please sign in to comment.