Skip to content

Commit

Permalink
add RTK Query
Browse files Browse the repository at this point in the history
  • Loading branch information
dzhafarovm committed Oct 15, 2021
1 parent 2926487 commit 28bbacc
Show file tree
Hide file tree
Showing 24 changed files with 149 additions and 264 deletions.
8 changes: 8 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hot-toast": "^2.1.1",
"react-loader-spinner": "^4.0.0",
"react-redux": "^7.2.5",
"react-scripts": "4.0.3",
"redux": "^4.1.1",
Expand Down
4 changes: 1 addition & 3 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React from 'react';
import { Phonebook } from './Components/Phonebook/Phonebook';
// import { Counter } from './Components/Counter/Counter.jsx';
import { Phonebook } from 'Components/Phonebook/Phonebook';

export const App = () => {
return (
<>
{/* <Counter /> */}
<Phonebook />
</>
);
Expand Down
14 changes: 0 additions & 14 deletions src/Components/Counter/Controls.jsx

This file was deleted.

37 changes: 0 additions & 37 deletions src/Components/Counter/Counter.jsx

This file was deleted.

3 changes: 0 additions & 3 deletions src/Components/Counter/Value.jsx

This file was deleted.

67 changes: 31 additions & 36 deletions src/Components/Phonebook/ContactForm.jsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import { useState, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import shortid from 'shortid';
import toast from 'react-hot-toast';
import { addContact } from '../../redux/Phonebook/phonebook-actions';
import { getContacts } from '../../redux/Phonebook/phonebook-selectors';
import shortid from 'shortid';

import { LoaderSpinnerDots } from 'Components/Spinner/spinner';
import { useCreateContactMutation } from 'redux/Phonebook/ContactSlice.jsx';
import css from './phonebook-css/ContactForm.module.css';

export default function ContactForm() {
const contacts = useSelector(getContacts);
const dispatch = useDispatch();
export default function ContactForm({ contacts }) {
const [createContact, { isLoading }] = useCreateContactMutation();

const [name, setName] = useState('');
const [number, setNumber] = useState('');
const [phone, setPhone] = useState('');

const nameId = useRef(shortid.generate());
const numberId = useRef(shortid.generate());
const phoneId = useRef(shortid.generate());

const handleContactChange = e => {
switch (e.target.name) {
case 'name':
setName(e.target.value);
break;

case 'number':
setNumber(e.target.value);
case 'phone':
setPhone(e.target.value);
break;

default:
Expand All @@ -34,34 +33,30 @@ export default function ContactForm() {
const handleSubmit = e => {
e.preventDefault();

const toastStyle = {
style: {
borderRadius: '10px',
background: '#e8f2f2',
color: '#000',
},
};

if (contacts.find(con => con.name.toLowerCase() === name.toLowerCase())) {
toast(`Name '${name}' is alresdy in contacts`, {
icon: '📞',
style: {
borderRadius: '10px',
background: '#666',
color: '#fff',
},
});
toast(`Name '${name}' is alresdy in contacts`, toastStyle);

return;
}

if (contacts.find(con => con.number === number)) {
toast(`Number '${number}' is alresdy in contacts`, {
icon: '📞',
style: {
borderRadius: '10px',
background: '#666',
color: '#fff',
},
});
if (contacts.find(con => con.phone === phone)) {
toast(`Number '${phone}' is alresdy in contacts`, toastStyle);
return;
}

dispatch(addContact({ name, number }));
createContact({ name, phone });
toast.success(`Contact '${name}' is added`);

setName('');
setNumber('');
setPhone('');
};

return (
Expand All @@ -85,15 +80,15 @@ export default function ContactForm() {

<br />

<label htmlFor={numberId.current} className={css.label}>
<label htmlFor={phoneId.current} className={css.label}>
Number:
<input
className={css.number}
type="tel"
name="number"
name="phone"
placeholder="Enter number"
value={number}
id={numberId.current}
value={phone}
id={phoneId.current}
onChange={handleContactChange}
pattern="\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}"
title="Номер телефона должен состоять цифр и может содержать пробелы, тире, круглые скобки и может начинаться с +"
Expand All @@ -103,8 +98,8 @@ export default function ContactForm() {

<br />

<button type="submit" className={css.btn}>
add contact
<button type="submit" className={css.btn} disabled={isLoading}>
{isLoading ? <LoaderSpinnerDots /> : 'add contact'}
</button>
</form>
</div>
Expand Down
28 changes: 28 additions & 0 deletions src/Components/Phonebook/ContactItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import toast from 'react-hot-toast';

import { useDeleteContactMutation } from 'redux/Phonebook/ContactSlice.jsx';
import { LoaderSpinnerDots } from 'Components/Spinner/spinner';
import css from './phonebook-css/ContactList.module.css';

export default function ContactListItem({ id, name, phone }) {
const [deleteContact, { isLoading }] = useDeleteContactMutation();

return (
<li key={id} className={css.item}>
<div>
<span>{name}: </span>
<span>{phone}</span>
</div>
<button
className={css.btn}
onClick={() => {
deleteContact(id);
toast.success(`Contact '${name}' deleted`);
}}
disabled={isLoading}
>
{isLoading ? <LoaderSpinnerDots /> : 'delete'}
</button>
</li>
);
}
23 changes: 4 additions & 19 deletions src/Components/Phonebook/ContactList.jsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
import { useSelector, useDispatch } from 'react-redux';
import { deleteContact } from '../../redux/Phonebook/phonebook-actions';
import { onFilteredContacts } from '../../redux/Phonebook/phonebook-selectors';
import ContactListItem from './ContactItem';
import css from './phonebook-css/ContactList.module.css';

export default function ContactList() {
const contacts = useSelector(onFilteredContacts);
const dispatch = useDispatch();

const onDeleteContact = id => dispatch(deleteContact(id));

export default function ContactList({ contacts }) {
return (
<ul className={css.list}>
{contacts.map(({ id, name, number }) => (
<li key={id} className={css.item}>
<div>
<span>{name}: </span>
<span>{number}</span>
</div>
<button className={css.btn} onClick={() => onDeleteContact(id)}>
delete
</button>
</li>
{contacts.map(({ id, name, phone }) => (
<ContactListItem key={id} id={id} name={name} phone={phone} />
))}
</ul>
);
Expand Down
12 changes: 2 additions & 10 deletions src/Components/Phonebook/Filter.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
import { useSelector, useDispatch } from 'react-redux';
import { changeFilter } from '../../redux/Phonebook/phonebook-actions';
import { getFilter } from '../../redux/Phonebook/phonebook-selectors';
import css from './phonebook-css/Filter.module.css';

export default function Filter() {
const value = useSelector(getFilter);
const dispatch = useDispatch();

const onChange = e => dispatch(changeFilter(e.target.value));

export default function Filter({ value, onChange }) {
return (
<label>
Find contacts by name
<input
className={css.inp}
type="text"
placeholder="Enter name"
placeholder="Enter contact's name"
value={value}
onChange={onChange}
/>
Expand Down
26 changes: 19 additions & 7 deletions src/Components/Phonebook/Phonebook.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
import { useSelector } from 'react-redux';
import { useState } from 'react';
import { Toaster } from 'react-hot-toast';

import ContactForm from './ContactForm.jsx';
import ContactList from './ContactList.jsx';
import Filter from './Filter';
import { getContacts } from '../../redux/Phonebook/phonebook-selectors';
import { LoaderSpinner } from 'Components/Spinner/spinner';
import { useFetchContactsQuery } from 'redux/Phonebook/ContactSlice.jsx';
import css from './phonebook-css/Phonebook.module.css';

export const Phonebook = () => {
const contacts = useSelector(getContacts);
const { data, isFetching } = useFetchContactsQuery();
const [filter, setFilter] = useState('');

const onChange = e => setFilter(e.target.value);

const onFilteredContacts = () => {
return data.filter(contact =>
contact.name.toLowerCase().includes(filter.toLowerCase()),
);
};

return (
<div className={css.phonebookBox}>
<h1>Phonebook</h1>
<ContactForm />
<ContactForm contacts={data} />

<h2>Contacts</h2>
{contacts.length !== 0 ? (
<Filter />
{data && data.length !== 0 ? (
<Filter value={filter} onChange={onChange} />
) : (
<h3>Your contacts list is empty</h3>
)}

<ContactList />
{isFetching && <LoaderSpinner />}
{data && <ContactList contacts={onFilteredContacts()} />}

<Toaster position="top-right" />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

.btn {
display: block;
width: 90px;
margin-top: 10px;
margin-left: auto;
margin-right: auto;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
}

.btn {
/* display: inline-block; */
width: 60px;
font-size: 12px;
margin-left: auto;
padding: 2px 10px 3px 10px;
Expand Down
9 changes: 9 additions & 0 deletions src/Components/Spinner/spinner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Loader from 'react-loader-spinner';

export const LoaderSpinner = () => {
return <Loader type="Oval" color="#0ab38e" height={20} width={20} />;
};

export const LoaderSpinnerDots = () => {
return <Loader type="ThreeDots" color="#0ab38e" height={10} width={20} />;
};
8 changes: 2 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store, persistore } from './redux/store.js';
import { PersistGate } from 'redux-persist/integration/react';

import { store } from 'redux/store';
import { App } from './App';
import 'normalize.css';
import './index.css';

ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistore}>
<App />
</PersistGate>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root'),
Expand Down
Loading

0 comments on commit 28bbacc

Please sign in to comment.