Git Product home page Git Product logo

contact-keeper's Introduction

Contact Keeper

Full stack MERN contact manager with React hooks, context & JWT authentication. Part of my React course on Udemy.

The app has been refactored since the course to take a more hook orientated approach and to use React Router 6.

At the time of the course hooks were very new and much of their implementation tried to substitute lifecycle methods with the closest hooks approximation which is understandable, many tutorials and courses early to adopt hooks took this approach. Hooks were very new at the time of recording, as more clearly defined patterns of use have emerged it's clear that hooks require a completely different approach and thought process to lifecycle methods. We need to think in terms of hooks and functions and not lifecycle.

If you are looking for the code you will see in the Udemy course then please check out the originalcoursecode branch of this repository.

If you're looking to fix your course code or wondering why in the course we had to use // eslint-disable-next-line or thought this doesn't feel right ignoring the linting rules, then I urge you to have a read of this post on overreacted by Dan Abramov. It covers a lot more than just useEffect There is also this great article from Kent C. Dodds. And this excellent full explanation of useEffect on the LogRocket blog.

To summarize the issues we faced in the course though, and why we had to use // eslint-disable-next-line at all is that all our data fetching methods are in our context states (AuthState.js and ContactState.js) and passed down to all our components via the context Provider. The problem with this is that every time we update our context state we create a new function. If we include these functions in our useEffect dependency array (as the linter suggests) then each time we fetch data and our reducer runs it updates the context which triggers a re-render (creating a whole set of new functions). The useEffect dependency sees it as a new function and triggers another render which again updates the state when we call the function in our useEffect, which triggers another re-render and so on.... infinite loop of re-rendering. Even though these functions are called the same and do the same thing, in memory they are different functions.

The solution is not to add an empty array and tell the linter to ignore it (trying to make a componentDidMount out of useEffect), but to think in terms of hooks and functions. We should keep our functions pure where possible and take all our data fetching methods out of context state. Take a look at ContactState.js as an example. You will see all of our action creators have been taken out of the context, this gurantees that the functions never change with a update to state and don't need to be added to the dependency array in useEffect. Each function now takes a dispatch argument as the first parameter which is passed in at the time of invocation. We can safely provide the dispatch via our context as react gurantees that a dispatch returned from useReducer is static and won't change. So the dispatch in our context state will not be a problem. Here is our new getContacts function:

export const getContacts = async (dispatch) => {
  try {
    const res = await axios.get('/api/contacts');

    dispatch({
      type: GET_CONTACTS,
      payload: res.data
    });
  } catch (err) {
    dispatch({
      type: CONTACT_ERROR,
      payload: err.response.msg
    });
  }
};

As you can see it takes disptach as the first argument and it is exported as a named export so we can use it.

We also make a new custom hook to easily consume our auth state and dispatch in our components..

export const useContacts = () => {
  const { state, dispatch } = useContext(ContactContext);
  return [state, dispatch];
};

We can then use our new hook like so (example taken from Contacts.js)

Import it along with our methods

import { useContacts, getContacts } from '../../context/contact/ContactState';

Use the hook to get our state and dispatch

const [contactState, contactDispatch] = useContacts();

const { contacts, filtered } = contactState;

And in our useEffect to fetch our users contacts

useEffect(() => {
  getContacts(contactDispatch);
}, [contactDispatch]);

You'll note that we did not need to include getContacts in our dependency array and we get no warnings now. We don't need to include getContacts as it doesn't change, it's the same function used every time.

Ultimately a good rule of thumb when using react context along with hooks would be to not provide functions in your context state, especially if those functions have side effects. The only exception being the dispatch returned from useReducer.

Postman Routes

Test your routes in PostMan with the following...

Users & Authentication Routes

  1. Register a new user - POST http://localhost:5000/api/users

    Headers
    key value
    Content-Type application/json

Body

{
"name": "Sam Smith",
"email": "[email protected]",
"password": "123456"
}
  1. Login a user - POST http://localhost:5000/api/auth
Headers
key value
Content-Type application/json

Body

{
"email": "[email protected]",
"password": "123456"
}
  1. Get logged in user - GET http://localhost:5000/api/auth
Headers
key value
Content-Type application/json
x-auth-token <VALID_TOKEN>

Contacts Routes

  1. Get a users contacts - GET
Headers
key value
Content-Type application/json
x-auth-token <VALID_TOKEN>
  1. Add a new contact - POST http://localhost:5000/api/contacts
Headers
key value
Content-Type application/json
x-auth-token <VALID_TOKEN>

Body

{
"name": "William Williams",
"email": "[email protected]",
"phone": "77575894"
}
  1. Update a contact - PUT http://localhost:5000/api/contacts/<CONTACT_ID>
Headers
key value
Content-Type application/json
x-auth-token <VALID_TOKEN>

Body

{
"phone": "555555"
}
  1. Delete a contact - DELETE http://localhost:5000/api/contacts/<CONTACT_ID>
Headers
key value
Content-Type application/json
x-auth-token <VALID_TOKEN>

Usage

Install dependencies

npm install
cd client
npm install

Mongo connection setup

Edit your /config/default.json file to include the correct MongoDB URI

Run Server

npm run dev     # Express & React :3000 & :5000
npm run server  # Express API Only :5000
npm run client  # React Client Only :3000

contact-keeper's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

contact-keeper's Issues

Causes Error

console.error(er.message); to console.error(err.message);

Installing dependencies via 'npm client-install' not working.

I just cloned the repository and tried to install the dependencies and found out that the command 'npm client-install' as given in the README.md executed in the parent file does not work or exist. However running 'npm install' in the client folder does.

Windows 10

Should match id with Id of mongoDB _id

const deleteContact = async _id => {
try {
await axios.delete(/api/contacts/${_id});

  dispatch({
    type: DELETE_CONTACT,
    payload: _id
  });
} catch (err) {
  dispatch({
    type: CONTACT_ERROR,
    payload: err.response.msg
  });
}

};

//ContactState.js

clean way to combine multiple hooks?

Hi Bard,

https://github.com/bradtraversy/contact-keeper/blob/master/client/src/App.js#L23

In this app, there are three hooks, so putting everything in the App.js is okay.
but let's say there are 100 hooks, then i have to put 100 hooks in App.js and it is not acceptable.
Is there a way to import all the hooks into ./context/hooks.js

and from App.js
import Hooks from "./context/hooks.js"

and wrap the Router with just one

......

and now i am accessible to all 100 hooks
any suggestion?

Delete contact is not working.

I have tried everything.

   const deleteContact = async id => {
     
        try {
          await axios.delete(`/api/contacts/${id}`);
    
          dispatch({
            type: DELETE_CONTACT,
            payload: id
          });
        } catch (err) {
          dispatch({
            type: CONTACT_ERROR,
            payload: err.response.msg
          });
        }
      };

I have checked, when pressing delete button it calls ondelete with the correct id, that passes it to the the contactState, and to axios, however after axios runs i check the database and the contacts is still there it's not being deleted from it. Anyone had similar issue

cannot ready property map of undefined

Contacts
D:/Projects/webprojects/contacts/client/src/components/contacts/Contacts.js:15

 }
 return (
<Fragment>
  {filtered !== null ? filtered.map(contact => (
<ContactItem key={contact.id} contact={contact} />

the code

const contactContext = useContext(ContactContext);

const { contacts, filtered } = contactContext;

if (contacts.length === 0) {
return <h4>please add a contact</h4>;
}

return (
<Fragment>
{filtered !== null
? filtered.map(contact => (
<ContactItem key={contact.id} contact={contact} />
))
: contacts.map(contact => (
<ContactItem key={contact.id} contact={contact} />
))}
</Fragment>
);
};

export default Contacts;

what am i missing?

Proxy error

in your course,, in lesson no 59, when you register user and get data in your state user and isAuthenticated,, i get isAuthenticated true but user does not get filled it at first register,, but when i call loadUser in Home component and refresh then the data is loaded to the state in user and isAuthenticated.

App crashes if there is '\' in filter.

The app completely crashes if we type a backslash in filter contacts input. I guess this is happening because it is trying to use backslash as an escape character.

This is the exact error:

SyntaxError: Invalid regular expression: //: \ at end of pattern
(anonymous function)
src/context/contact/contactReducer.js:47
44 | return {
45 | ...state,
46 | filtered: state.contacts.filter((contact) => {

47 | const regex = new RegExp(${action.payload}, 'gi');
| ^ 48 | return contact.name.match(regex) || contact.email.match(regex);
49 | }),
50 | };

I've gotten around this by replacing ${action.payload} with ${action.payload.replace('\\', '\\\\')}.

Missing loading-prop in ContactState.js

Hi Brad,

first of all, thank you for your hard work and for just being a good human.

I am doing this course right now and noticed that in this block of ContactState.js the loading: true property is missing. We set loading: false in contactReducer.js and query this prop in Contacts.js but it will always be undefined.

I cloned the repo, created a new branch, added the necessary lines, and tried to push it back here, but it seems I need permissions for it. So I thought, I should just create an issue for it then.

Thank you and have a good day,

Nik

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.