Git Product home page Git Product logo

whatsapp-clone-tutorial's Introduction

Whatsapp Clone Tutorial

whatsapp-clone

!(https://www.youtube.com/watch?v=omsHNP4Vjhc)

Whatsapp Clone is a free and open-source tutorial that will guide you step-by-step on how to create a full-stack, mobile, hybrid web application from scratch.

The software world is evolving quickly, and oftentimes people find themselves left behind, even the most experienced ones. The purpose of this tutorial is not only to demonstrate how to create a full application with the latest technologies, but also to keep up to date with the ever-changing development ecosystem.

This tutorial is for anyone who has ever asked themselves one of the following questions:

  • How do people build an app today?
  • What are the currently leading technologies in the ecosystem?
  • What are the best practices for using technology XXX?
  • What is the purpose of technology XXX?
  • How does technology XXX work?
  • How do I use technology XXX?
  • How do I migrate to the new version of technology XXX?
  • Why should I use technology XXX over technology YYY?

All of the above and more can be answered in the tutorial. Whether you’re a beginner, intermediate or a professional, we will have the answers you’re looking for.

What technologies does Whatsapp Clone uses?

The version of the Whatsapp Clone you are looking at, uses:

The point of this tutorial is not to be bound to a certain technology, but rather keep itself aligned with the ecosystem. When a new technology comes out, and it’s better and more popular, Whatsapp Clone will upgrade to use it (together with full migration instructions).

P2P tutorial for the community by the community

Keeping tutorials up to date is not an easy task. That's why we've created the Tortilla Tutorial Framework that makes it easy to write and update tutorials. Also, the WhatsApp clone is completely open source in order for the community to give its feedback, help and fork ideas. Here are the repositories the tutorial is made of:

We’ve also made sure to publish some important documents so you can get more involved. You can track our progress and comment your suggestions, since everything is based on Google Docs and is updated live:

Cloning the finished app

If you want to simply clone and run the app locally in it's finished, first clone the Server repository and follow the instructions on the README there.

Then clone the React Client repository and follow the instructions there carefully.

Migration instructions included

There are many great tutorials out there, but almost none of them shows you what changes you should make in your app in order to be aligned with a new version of a certain technology. As technologies are being updated by the minute, some changes are minor and insignificant, but often times a breaking change will be made in which case we need to know how we can adapt to that change. Thanks to the Tortilla platform, we can provide you with a git-diff that will show you what changes were made between each and every released version of the Whatsapp Clone tutorial since the beginning of history. This way you can easily notice the changes in APIs and migrate your app in no time.

tutorial-versions-diff

Prerequisites for WhatsApp Clone

Even if you don't have experience with the technologies below you might still be able to start the tutorial and pick things along the way. If you struggle with anything during the tutorial, contact us on our forum or on Github with your questions. It will help us make sure that the tutorial is good for beginners as well

OS operations such as navigating to a folder, or creating a folder, are all gonna be written in Bash, but the instructions are OS agnostic and can be applied on any machine that is web-compatible.

Make sure you have the latest global dependencies on your computer before starting the tutorial:

Node

Install nvm by running the following command in your command line:

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash

Then install the latest version of Node by running the following:

$ nvm install node

Yarn

Follow the instructions here.

What’s on the tutorial?

Whatsapp Clone is built chronologically, from the most basic, to more higher level features, so we recommend you to follow the tutorial in the right order. Each step is focused on a different subject, so by the end of it you’ll have a new feature and a new set of knowledge that you can start implementing in your everyday scenario immediately.

If you feel like you want to skip or focus on a specific subject, on each step you can download the full app code till that point in time.

That is also useful in case you get stuck.

Currently, Whatsapp Clone includes the following chapters:

Whatsapp Clone is updated on a regular basis, so you should expect more steps and extensions with time. You can keep track of our road map to see what’s upcoming.

External contributors

Begin Tutorial >

whatsapp-clone-tutorial's People

Contributors

ardatan avatar dab0mb avatar darkbasic avatar kamilkisiela avatar

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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

whatsapp-clone-tutorial's Issues

[Client Step 5.3] Vscode issues and date time

For the client-side test, I got "16:00" for the content in the date property. This might be due to timezone differences between me and the tutorial writer. Might be a good idea to clarify this.

Also, in vscode, all the setup's in setupTest.ts were ignored by vscode in the test file itself. This was resolved after installing the jest plugin. Could be added to the guide for troubled users.

[Client Step 6.4]

Hi,

 window.location = {
      href: '/',
    };

produces below error;

Type '{ href: string; }' is missing the following properties from type 'Location': ancestorOrigins, hash, host, hostname, and 8 more.ts(2740)

(SOLVED) [Server Step 2.3] -- Server requests do nothing, 'hanging'

Whenever I initiate any sort of request to the server, be it using the provided curl command, or by visiting http://localhost:4000/graphql in the browser, the request always stalls and provides nothing, not even an error.

I've gone through the project with a fine-tooth comb, and cannot find any deviations in my code from the tutorial.

Here's /index.ts:


import { ApolloServer, gql } from 'apollo-server-express';
import cors from 'cors';
import express from 'express';
import { chats } from './db';
import schema from './schema';

const app = express();

app.use(cors());
app.use(express.json);

app.get('/_ping', (req, res) => {
    res.send('pong');
    res.json(chats);
});

const server = new ApolloServer({
    schema,
    //playground: true,
    //introspection: true,
});

server.applyMiddleware({ app, cors: true, path: '/graphql' });


const port = process.env.PORT || 4000;
//process.env.PLAYGROUND_URL = "http://localhost:4000";

app.listen(port, () => {
    console.log(`Server is listening on port ${port}`);
    //console.log(process.env.NODE_ENV);
})

Here is also /schema/resolvers.ts


import { DateTimeResolver, URLResolver } from 'graphql-scalars';
import { chats, messages } from '../db'

const resolvers = {
    Date: DateTimeResolver,
    URL: URLResolver,

    Chat: {
        lastMessage(chat: any) {
            return messages.find(m => m.id === chat.lastMessage);
        },
    },

    Query: {
        chats() {
            return chats;
        },
    },
};

export default resolvers;

I won't bother copying over /schema/index.ts, nor /schema/typeDefs.graphql, nor /db.ts, since they are identical to the tutorial.

[Client Step 6.9] change src/components/ChatRoomScreen/MessageInput.test.tsx

those waits don't have any effect , the inputs are already selected with get that would throw if the element was not found

import { createMemoryHistory } from 'history';
import React from 'react';
import { cleanup, render, waitFor, fireEvent } from '@testing-library/react';
import MessageInput from './MessageInput';

describe('MessageInput;', () => {
  afterEach(cleanup);

  it('triggers callback on send button click', async () => {
    const onSendMessage = jest.fn(() => {});

    {
      const { container, getByTestId } = render(
        <MessageInput onSendMessage={onSendMessage} />
      );
      const messageInput = getByTestId('message-input');
      const sendButton = getByTestId('send-button');

      fireEvent.change(messageInput, { target: { value: 'foo' } });

      fireEvent.click(sendButton);

      await waitFor(() => expect(onSendMessage.mock.calls.length).toBe(1));
    }
  });

  it('triggers callback on Enter press', async () => {
    const onSendMessage = jest.fn(() => {});

    {
      const { container, getByTestId } = render(
        <MessageInput onSendMessage={onSendMessage} />
      );
      const messageInput = getByTestId('message-input');

      fireEvent.change(messageInput, { target: { value: 'foo' } });

      fireEvent.keyPress(messageInput, {
        key: 'Enter',
        code: 13,
        charCode: 13,
      });

      await waitFor(() => expect(onSendMessage.mock.calls.length).toBe(1));
    }
  });
});

Step 15 Use environmental variables for the Unsplash API key

I think it should be a nice thing to explain to the user what are Environmental Variables and how to create and use a .env file in the application and change the current way the Unsplash API key it's used

headers: {
  Authorization:
    'Client-ID 4d048cfb4383b407eff92e4a2a5ec36c0a866be85e64caafa588c110efad350d',
},

EDIT
This course is so extensive and full of teachings that I forgot that the .env file is actually explained in Step 3 > Client 3.1
So maybe just create one for the server and add the Unsplash API key to it?

Client Step 7.3

There is an error here.

I followed every step but when i type yarn start in both client and server folder it gives me error how I can fix this issue.

TypeError: _useQuery.data is undefined
ChatRoomScreen
src/components/ChatRoomScreen/index.tsx:56

  53 | const ChatRoomScreen: React.FC<ChatRoomScreenParams> = ({
  54 |     history,
  55 |     chatId,
> 56 |   }) => {
  57 |     const client = useApolloClient();
  58 |     const {
  59 |       data: { chat },

[Client Step 6.5]

a small bug (i think)

app.tsx should have history = createBrowserHistory and pass that object onto chatScreen no?

[Client Step 12.3] duplicated import on src/services/cache.service.ts

The import of the useMessageAddedSubscription is duplicated on Client Step 12.3: Write chat on chatAdded

image

It should be imported just once or it will throw an error
image

Just remove one of them

import { DataProxy } from 'apollo-cache';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import * as fragments from '../graphql/fragments';
import * as queries from '../graphql/queries';
import {
  MessageFragment,
  ChatsQuery,
  ChatFragment,
  useMessageAddedSubscription,
  useChatAddedSubscription,
} from '../graphql/types';

[Server Step 1.3]

curl localhost:4000/_ping
curl: (7) Failed to connect to localhost port 4000: Connection refused

Tutorial step 8's step 5.1 is buggy

modify schema/resolvers.ts

Lines 36-37 create a pretty annoying bug.

const lastMessageId = chat.messages[chat.messages.length - 1];
const messageId = String(Number(lastMessageId) + 1);

So what happens here is that when using the tutorial app depending on scenario you're re-using IDs on "database". I can see this has already been fixed on https://github.com/Urigo/WhatsApp-Clone-Server , but the tutorial still has this bug

...or maybe this bug always was only on tutorial, couldn't find the exact commit which would show this bug getting fixed.

[Client Step 7.3]

There seems to be a problem with running this code. It happens in the index.tsx script located in the ChatRoomScreen folder on line 59 where you try to extract the chat object from the data object returned by the useQuery hook. The error that appears is "cannot read property chat of undefined". Seems to me that at the moment of extracting the chat object apollo is still loading the results, therefore data is still empty. Could you please take a look into it or tell me if I'm doing or if I understood something wrong.

Thanks

Step 16 Server step 13.28 not found

The server step referenced here is giving a NOT FOUND! message.

image

I did not submit a PR to remove it because I thought that maybe the problem is that the reference to the files is missing or a similar issue.

Step 14 Problem displaying Bash code in Markdown

The Markdown Bash code block in this section is not displaying properly
image

I think it is a problem related to the way the Markdown is rendered by Tortilla because GitHub renders the same code properly.

Welcome to psql 7.4.16, the PostgreSQL interactive terminal.

Type:  \\copyright for distribution terms
       \\h for help with SQL commands
       \\? for help on internal slash commands
       \\g or terminate with semicolon to execute query
       \\q to quit

template1

[Server Step 8.1] change db.ts

export type User = {
  id: string;
  name: string;
  picture: string;
};

export type Message = {
  id: string;
  content: string;
  createdAt: Date;
  sender: string;
  recipient: string;
};

export type Chat = {
  id: string;
  messages: string[];
  participants: string[];
};

export const users: User[] = [];
export const messages: Message[] = [];
export const chats: Chat[] = [];

export const resetDb = () => {
  users.splice(
    0,
    Infinity,
    ...[
      {
        id: '1',
        name: 'Ray Edwards',
        picture: 'https://randomuser.me/api/portraits/thumb/lego/1.jpg',
      },
      {
        id: '2',
        name: 'Ethan Gonzalez',
        picture: 'https://randomuser.me/api/portraits/thumb/men/1.jpg',
      },
      {
        id: '3',
        name: 'Bryan Wallace',
        picture: 'https://randomuser.me/api/portraits/thumb/men/2.jpg',
      },
      {
        id: '4',
        name: 'Avery Stewart',
        picture: 'https://randomuser.me/api/portraits/thumb/women/1.jpg',
      },
      {
        id: '5',
        name: 'Katie Peterson',
        picture: 'https://randomuser.me/api/portraits/thumb/women/2.jpg',
      },
    ]
  );

  messages.splice(
    0,
    Infinity,
    ...[
      {
        id: '1',
        content: 'You on your way?',
        createdAt: new Date(new Date('1-1-2019').getTime() - 60 * 1000 * 1000),
        sender: '2',
        recipient: '1',
      },
      {
        id: '2',
        content: "Hey, it's me",
        createdAt: new Date(
          new Date('1-1-2019').getTime() - 2 * 60 * 1000 * 1000
        ),
        sender: '3',
        recipient: '1',
      },
      {
        id: '3',
        content: 'I should buy a boat',
        createdAt: new Date(
          new Date('1-1-2019').getTime() - 24 * 60 * 1000 * 1000
        ),
        sender: '4',
        recipient: '1',
      },
      {
        id: '4',
        content: 'This is wicked good ice cream.',
        createdAt: new Date(
          new Date('1-1-2019').getTime() - 14 * 24 * 60 * 1000 * 1000
        ),
        sender: '5',
        recipient: '1',
      },
    ]
  );

  chats.splice(
    0,
    Infinity,
    ...[
      {
        id: '1',
        participants: ['1', '2'],
        messages: ['1'],
      },
      {
        id: '2',
        participants: ['1', '3'],
        messages: ['2'],
      },
      {
        id: '3',
        participants: ['1', '4'],
        messages: ['3'],
      },
      {
        id: '4',
        participants: ['1', '5'],
        messages: ['4'],
      },
    ]
  );
};

resetDb();

[Client Step 5.3]

I'm getting an error when using fetch in the test:

Property 'mockResponseOnce' does not exist on type '{ (input: RequestInfo, init?: RequestInit | undefined): Promise<Response>; (input: RequestInfo, init?: RequestInit | undefined): Promise<Response>; }'.ts(2339)

This is resolved by using fetchMock instead - should this be updated in the tutorial?

[Server Step 5.1] - ordering of the chats in the top page is done server-side?

Hi,

The server-side code is OK but the reference to the client-side ChatsList component feels a bit weird.

// The chat will appear at the top of the ChatsList component

It implies that the service is making assumptions on the client-side usage and implementation, whereas it should really be the client relying on documented service API.

While it might make sense for the service to present its data in a (documented) particular order, it should not have to do so in order to achieve a specific result client-side, expecting it to display the data in the same order.

I'd rather expect server-side documentation that the chats are provided in a specific order, and client-side comments explaining that no reordering is necessary because the service API is designed that way, or explicit reordering client-side.

[Client Step 3.2] Key has temporary !

In this section, the key for each StyledListItem changes from key={chat.id} to key={chat!.id}. This exclamation mark vanishes in future steps. Why would we want an exclamation mark? Is this an error?

[Client Step 1.3] change package.json

Running yarn format on Windows produces the error "No matching files." due to the single quotes. It works when removing the quotes in the command.

{
  "name": "whatsapp-clone-client",
  "version": "0.1.0",
  "private": true,
  "repository": {
    "type": "git",
    "url": "https://github.com/Urigo/WhatsApp-Clone-Client-React.git"
  },
  "dependencies": {
    "@types/jest": "24.0.15",
    "@types/node": "12.0.10",
    "@types/react": "16.8.22",
    "@types/react-dom": "16.8.4",
    "prettier": "1.18.2",
    "react": "16.8.6",
    "react-dom": "16.8.6",
    "react-scripts": "3.0.1",
    "typescript": "3.5.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "TZ=\"Asia/Jerusalem\" react-scripts test",
    "eject": "react-scripts eject",
    "format": "prettier **/*.{ts,tsx,css,graphql} --write"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

[Client Step 6.4] change src/components/ChatsListScreen/ChatsList.test.tsx

We should create a browser history and pass it to ChatsList component...

import React from 'react';
import ReactDOM from 'react-dom';
import {
  cleanup,
  render,
  fireEvent,
  wait,
  waitForDomChange,
} from '@testing-library/react';
import ChatsList from './ChatsList';
import { createBrowserHistory } from 'history';

describe('ChatsList', () => {
  afterEach(() => {
    cleanup();
    delete window.location;

    window.location = {
        href: '/',
    };
  });

  it('renders fetched chats data', async () => {
    fetchMock.mockResponseOnce(
      JSON.stringify({
        data: {
          chats: [
            {
              id: 1,
              name: 'Foo Bar',
              picture: 'https://localhost:4000/picture.jpg',
              lastMessage: {
                id: 1,
                content: 'Hello',
                createdAt: new Date('1 Jan 2019 GMT'),
              },
            },
          ],
        },
      })
    );
	const history = createBrowserHistory();
    {
      const { container, getByTestId } = render(<ChatsList history={history} />);

      await waitForDomChange({ container });

      expect(getByTestId('name')).toHaveTextContent('Foo Bar');
      expect(getByTestId('picture')).toHaveAttribute(
        'src',
        'https://localhost:4000/picture.jpg'
      );
      expect(getByTestId('content')).toHaveTextContent('Hello');
      expect(getByTestId('date')).toHaveTextContent('02:00');
    }
  });

  it('should navigate to the target chat room on chat item click', async () => {
    fetchMock.mockResponseOnce(
      JSON.stringify({
        data: {
          chats: [
            {
              id: 1,
              name: 'Foo Bar',
              picture: 'https://localhost:4000/picture.jpg',
              lastMessage: {
                id: 1,
                content: 'Hello',
                createdAt: new Date('1 Jan 2019 GMT'),
              },
            },
          ],
        },
      })
    );

    const history = createBrowserHistory();

    {
      const { container, getByTestId } = render(
        <ChatsList history={history} />
      );

      await waitForDomChange({ container });

      fireEvent.click(getByTestId('chat'));

      await wait(() => expect(history.location.pathname).toEqual('/chats/1'));
    }
  });
});

[Step 1] Whatsapp Clone Client Added src/App.tsx section

Hello there.
I'm trying to follow the tutorial. Although I'm not a beginner, I've changed career some years ago so I'm not coding as much as I used to, at least not professionally. So I'm reading to learn about GraphQL and maybe something else I don't know.

My English isn't very good, so please, don't get offended if something I say sounds a little off or rude, sometimes the expressions doesn't translate well from one language to another.

I'm trying to be open-minded about the tutorial because I think tortilla.academy is a very good idea.
So I will try to give some impressions while I follow it instead of just ignoring potential problems.

I think this section is not adequate written.

You show the src/App.tsx file but start talking about things which is not really related to the file or the section.
Why are you talking about defining a constant if there is no constant defined on the file to be found?
Why are you talking about arrow functions if there is no arrow functions on the file?

tortilla

Even as I'm not a beginner, for a moment I got confused thinking I've missed something on the tutorial or thinking I was opening the wrong file.

Javascript/Typescrit is said as prerequisite to WhatsApp Clone tutorial in Step 0, so I don't think it's a good idea to suddenly try to teach basic Javascript concepts randomly specially if there is no direct reference to it in the file/section being discussed in question.

Maybe it was copy/paste in the wrong section for some reason?

Non-related issue, when I raised this issue, the issue title didn't automatically fill, it should passed the step/section name/number automatically in the url shouldn't?

[Server Step 3.2]

I got error as follows :

error TS2345: Argument of type 'ApolloServer' is not assignable to parameter of type 'ApolloServerBase'.
Types have separate declarations of a private property 'logger'.
12 const { query } = createTestClient(server);
when I changed code to as follows the test got run :
const server = new ApolloServer({ schema }) as any;

resolver.ts in Part13/Authentication not compiling

participantId has type any.

The edits look like this example:

const participantId:string = chat.participants.find((p:User) => p !== currentUser.id);

This version of complete file compiles:

import { withFilter } from 'apollo-server-express';
import { DateTimeResolver, URLResolver } from 'graphql-scalars';
import { User, Message, Chat, chats, messages, users } from '../db';
import { Resolvers } from '../types/graphql';
import { secret, expiration } from '../env';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { validateLength, validatePassword } from '../validators';

const resolvers: Resolvers = {
  Date: DateTimeResolver,
  URL: URLResolver,

  Message: {
    chat(message) {
      return (
        chats.find((c) => c.messages.some((m) => m === message.id)) || null
      );
    },

    sender(message) {
      return users.find((u) => u.id === message.sender) || null;
    },

    recipient(message) {
      return users.find((u) => u.id === message.recipient) || null;
    },

    isMine(message, args, { currentUser }) {
      return message.sender === currentUser.id;
    },
  },

  Chat: {
    name(chat, args, { currentUser }) {
      if (!currentUser) return null;

      const participantId:string = chat.participants.find((p:User) => p !== currentUser.id);

      if (!participantId) return null;

      const participant = users.find((u) => u.id === participantId);

      return participant ? participant.name : null;
    },

    picture(chat, args, { currentUser }) {
      if (!currentUser) return null;

      const participantId:string = chat.participants.find((p:User) => p !== currentUser.id);

      if (!participantId) return null;

      const participant = users.find((u) => u.id === participantId);

      return participant ? participant.picture : null;
    },

    messages(chat) {
      return messages.filter((m) => chat.messages.includes(m.id));
    },

    lastMessage(chat) {
      const lastMessage = chat.messages[chat.messages.length - 1];

      return messages.find((m) => m.id === lastMessage) || null;
    },

    participants(chat) {
      return chat.participants
        .map((p:User) => users.find((u:User) => u.id === p.id))
        .filter(Boolean) as User[];
    },
  },

  Query: {
    me(root, args, { currentUser }) {
      return currentUser || null;
    },
    chats(root, args, { currentUser }) {
      if (!currentUser) return [];

      return chats.filter((c) => c.participants.includes(currentUser.id));
    },

    chat(root, { chatId }, { currentUser }) {
      if (!currentUser) return null;

      const chat = chats.find((c) => c.id === chatId);

      if (!chat) return null;

      return chat.participants.includes(currentUser.id) ? chat : null;
    },

    users(root, args, { currentUser }) {
      if (!currentUser) return [];

      return users.filter((u) => u.id !== currentUser.id);
    },
  },

  Mutation: {
    signIn(root, { username, password }, { res }) {
      const user = users.find((u) => u.username === username);

      if (!user) {
        throw new Error('user not found');
      }

      const passwordsMatch = bcrypt.compareSync(password, user.password);

      if (!passwordsMatch) {
        throw new Error('password is incorrect');
      }

      const authToken = jwt.sign(username, secret);

      res.cookie('authToken', authToken, { maxAge: expiration });

      return user;
    },
    signUp(root, { name, username, password, passwordConfirm }) {
      validateLength('req.name', name, 3, 50);
      validateLength('req.username', username, 3, 18);
      validatePassword('req.password', password);
 
      if (password !== passwordConfirm) {
        throw Error("req.password and req.passwordConfirm don't match");
      }
 
      if (users.some(u => u.username === username)) {
        throw Error('username already exists');
      }
 
      const passwordHash = bcrypt.hashSync(password, bcrypt.genSaltSync(8));
 
      const user: User = {
        id: String(users.length + 1),
        password: passwordHash,
        picture: '',
        username,
        name,
      };
 
      users.push(user);
 
      return user;
    },
    addMessage(root, { chatId, content }, { currentUser, pubsub }) {
      if (!currentUser) return null;

      const chatIndex = chats.findIndex((c) => c.id === chatId);

      if (chatIndex === -1) return null;

      const chat = chats[chatIndex];
      if (!chat.participants.includes(currentUser.id)) return null;

      const messagesIds = messages.map((currentMessage) =>
        Number(currentMessage.id)
      );
      const messageId = String(Math.max(...messagesIds) + 1);
      const message: Message = {
        id: messageId,
        createdAt: new Date(),
        sender: currentUser.id,
        recipient: chat.participants.find(
          (p) => p !== currentUser.id
        ) as string,
        content,
      };

      messages.push(message);
      chat.messages.push(messageId);
      // The chat will appear at the top of the ChatsList component
      chats.splice(chatIndex, 1);
      chats.unshift(chat);

      pubsub.publish('messageAdded', {
        messageAdded: message,
      });

      return message;
    },

    addChat(root, { recipientId }, { currentUser, pubsub }) {
      if (!currentUser) return null;
      if (!users.some((u) => u.id === recipientId)) return null;

      let chat = chats.find(
        (c) =>
          c.participants.includes(currentUser.id) &&
          c.participants.includes(recipientId)
      );

      if (chat) return chat;

      const chatsIds = chats.map((c) => Number(c.id));

      chat = {
        id: String(Math.max(...chatsIds) + 1),
        participants: [currentUser.id, recipientId],
        messages: [],
      };

      chats.push(chat);

      pubsub.publish('chatAdded', {
        chatAdded: chat,
      });

      return chat;
    },

    removeChat(root, { chatId }, { currentUser, pubsub }) {
      if (!currentUser) return null;

      const chatIndex = chats.findIndex((c) => c.id === chatId);

      if (chatIndex === -1) return null;

      const chat = chats[chatIndex];

      if (!chat.participants.some((p) => p === currentUser.id)) return null;

      chat.messages.forEach((chatMessage) => {
        const chatMessageIndex = messages.findIndex(
          (m) => m.id === chatMessage
        );

        if (chatMessageIndex !== -1) {
          messages.splice(chatMessageIndex, 1);
        }
      });

      chats.splice(chatIndex, 1);

      pubsub.publish('chatRemoved', {
        chatRemoved: chat.id,
        targetChat: chat,
      });

      return chatId;
    },
  },

  Subscription: {
    messageAdded: {
      subscribe: withFilter(
        (root, args, { pubsub }) => pubsub.asyncIterator('messageAdded'),
        ({ messageAdded }, args, { currentUser }) => {
          if (!currentUser) return false;

          return [messageAdded.sender, messageAdded.recipient].includes(
            currentUser.id
          );
        }
      ),
    },

    chatAdded: {
      subscribe: withFilter(
        (root, args, { pubsub }) => pubsub.asyncIterator('chatAdded'),
        ({ chatAdded }: { chatAdded: Chat }, args, { currentUser }) => {
          if (!currentUser) return false;

          return chatAdded.participants.some((p) => p === currentUser.id);
        }
      ),
    },

    chatRemoved: {
      subscribe: withFilter(
        (root, args, { pubsub }) => pubsub.asyncIterator('chatRemoved'),
        ({ targetChat }: { targetChat: Chat }, args, { currentUser }) => {
          if (!currentUser) return false;

          return targetChat.participants.some((p) => p === currentUser.id);
        }
      ),
    },
  },
};

export default resolvers;

[Question] Exploring tutorial updates from previous steps

We have a team now working on validating tutorials and if someone is the middle of the steps and entire tutorial recieves update it is hard currently to figure it out what has changed in other steps.

What is the recommendation in this case (update happens while you are in the middle of the tutorial)?

Is it possible to see what was changed in the previous steps?
I know it is possible to get the most recent step source code so there is not an issue, but I'm curious if there is way to actually see all changes that happened in an update for previous steps?

Submit code changes does not work

On Step 15. Server Step 12.1: Retrieve profile picture from REST API the submit button to propose code changes is not working properly and rediects to a Your request URL is too long. page.

Screenshot 2020-06-25 at 20 34 16

Screenshot 2020-06-25 at 20 25 34

"Can't resolve ../../graphql/types"

First off, thanks so much for creating this tutorial. It is truly amazing.

I was following along but then decided to clone from step 16 to have a look at the finished project. After the clone, I ran the "yarn prestart" and "yarn prebuild" but I'm getting this error still.
Module not found: Can't resolve '../../graphql/types' in '/Users/ryanlozon/code/WhatsApp-Clone-Tutorial/client/WhatsApp-Clone-Client-React/src/components/ChatCreationScreen'

It looks like the when I navigate to the graphql folder on the client there is indeed no types folder, however there is a "types.tsx."

I'm rather new to typescript and codegen so I apologize if it is a straight forward question, I just couldn't find any other answers online.

Thank you for your time.

[Client Step 5.3]

Hi,

I've just added ChatsList.test.tsx in the same way as in the tutorial. However, receiving below UnhandledPromiseRejectionWarning. Am I missing something?

Thanks

(node:5312) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:5312) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
 FAIL  src/components/ChatsListScreen/ChatsList.test.tsx (17.288s)
  ● ChatsList › renders fetched chats data

    expect(element).toHaveTextContent()

    Expected element to have text content:
      02:00
    Received:
      03:00

[Client Step 10.3] Issue with cache service

I am getting the following error while running the client application.

This expression is not callable.
Each member of the union type '(<T = any, TVariables = OperationVariables>(options: Fragment, optimistic?: boolean | undefined) => T | null) | (<FragmentType, TVariables = any>(options: Fragment, optimistic?: boolean | undefined) => FragmentType | null)' has signatures, but none of those signatures are compatible with each other. TS2349

50 |   }
51 |   try {

52 | fullChat = client.readFragment({
| ^
53 | id: chatIdFromStore,
54 | fragment: fragments.fullChat,
55 | fragmentName: 'FullChat',

Step 3, chats not loaded

Hi,

First of all, fantastic tutorial !!!

I have followed all your steps until the end of Step 3 https://github.com/Urigo/WhatsApp-Clone-Tutorial/blob/master/.tortilla/manuals/views/step3.md

The server is up:
curl localhost:4000/chats response is:
[{"id":"1","name":"Ethan Gonzalez","picture":"https://randomuser.me/api/portraits/thumb/men/1.jpg","lastMessage":"1"},{"id":"2","name":"Bryan Wallace","picture":"https://randomuser.me/api/portraits/thumb/men/2.jpg","lastMessage":"2"},{"id":"3","name":"Avery Stewart","picture":"https://randomuser.me/api/portraits/thumb/women/1.jpg","lastMessage":"3"},{"id":"4","name":"Katie Peterson","picture":"https://randomuser.me/api/portraits/thumb/women/2.jpg","lastMessage":"4"}]%

However, when going back to React app and adding .env to the root with REACT_APP_SERVER_URL=http://localhost:4000

and editing ChatsList.tsx the following:


const ChatsList = () => {
  const [chats, setChats] = useState<any[]>([]);

  useMemo(async () => {
    const body = await fetch(`${process.env.REACT_APP_SERVER_URL}/chats`);
    console.log(body)
    const chats = await body.json();
    setChats(chats);
  }, []);

  return (
    <Container>
      <StyledList>
        {chats.map(chat => (
          <StyledListItem key={chat!.id} button>
            <ChatInfo>
              <ChatName>{chat.name}</ChatName>
              {chat.lastMessage && (
                <React.Fragment>
                  <MessageContent>{chat.lastMessage.content}</MessageContent>
                  <MessageDate>
                    {moment(chat.lastMessage.createdAt).format('HH:mm')}
                  </MessageDate>
                </React.Fragment>
              )}
            </ChatInfo>
          </StyledListItem>
        ))}
      </StyledList>
    </Container>
  );
}; 

The chats doent show up in the application anymore.
Any idea what i do wrong ?

[Client Step 1.11]

chat.lastMessage.createdAt is a date object, it can't be displayed as a string. chat.lastMessage.createdAt.toDateString() is okay temporarily.

Step 8: Sending messages -- Cannot read property 'chat' of undefined

After having gone through all of the steps in section 8, I'm running into a problem where the app crashes when clicking on a chat returning with the error: Cannot read property 'chat' of undefined. It appears to be stemming from ChatRoomScreen line 66, which doesn't make it any more clear.

I finally gave up debugging it and cloned the git repo at the exact steps I had finished at and it appears I'm also receiving the error there as well. Are you able to reproduce on your end?

I'm using master-step8 for the client and master-step6 which appear to align with the project at the end of step 8

4739375ac05cbf17d0b93f8c570dd9dd

[Client Step 1.3] Incorrect format command

In the docs, it says "Remember to run yarn prettier before you commit your changes!" That gave me a command-line error because no files were given nor specified. Perhaps, you should change the suggested command to yarn format?

History unknown error

I cloned the final repository for both server and client and run the necessary yarn installs and tests and everything passes. Now when I run yarn start, this error message comes up...

  The types of 'location.state' are incompatible between these types.
    Type 'unknown' is not assignable to type 'PoorMansUnknown'.
      Type 'unknown' is not assignable to type '{}'.  TS2322

    24 |         component={withAuth(
    25 |           ({ match, history }: RouteComponentProps<{ chatId: string }>) => (
  > 26 |             <ChatRoomScreen chatId={match.params.chatId} history={history} />
       |                                                          ^
    27 |           )
    28 |         )}
    29 |       />

[Server Step 1.4] change package.json

{
  "name": "whatsapp-clone-server",
  "description": "A newly created Tortilla project",
  "repository": {
    "type": "git",
    "url": "https://github.com/Urigo/WhatsApp-Clone-Server.git"
  },
  "private": true,
  "scripts": {
    "start": "ts-node index.ts",
    "format": "prettier \"**/*.ts\" --write"
  },
  "devDependencies": {
    "@types/express": "4.17.6",
    "@types/node": "14.0.4",
    "prettier": "2.0.5",
    "ts-node": "8.10.1",
    "typescript": "3.9.3"
  },
  "dependencies": {
    "express": "4.17.1"
  }
}

license. is missing

[Client Step 5.3] change src/components/ChatsListScreen/ChatsList.test.tsx

fixing date test. Date from 2:00 to correct 21:00

import React from 'react';
import ReactDOM from 'react-dom';
import { cleanup, render, waitForDomChange } from '@testing-library/react';
import ChatsList from './ChatsList';

describe('ChatsList', () => {
  afterEach(cleanup);

  it('renders fetched chats data', async () => {
    fetchMock.mockResponseOnce(
      JSON.stringify({
        data: {
          chats: [
            {
              id: 1,
              name: 'Foo Bar',
              picture: 'https://localhost:4000/picture.jpg',
              lastMessage: {
                id: 1,
                content: 'Hello',
                createdAt: new Date('1 Jan 2019 GMT'),
              },
            },
          ],
        },
      })
    );

    {
      const { container, getByTestId } = render(<ChatsList />);

      await waitForDomChange({ container });

      expect(getByTestId('name')).toHaveTextContent('Foo Bar');
      expect(getByTestId('picture')).toHaveAttribute(
        'src',
        'https://localhost:4000/picture.jpg'
      );
      expect(getByTestId('content')).toHaveTextContent('Hello');
      expect(getByTestId('date')).toHaveTextContent('21:00');
    }
  });
});

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.