Twitter Clone Part 2: Creating a Profile Page and Following Users
Tags: javascript, react, clone, webdev
Published: 2022-06-29 12:30:55

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/082fa6b5a18d3324659c5ba874e7e4d4/twitter-clone-part-2-feature-image.png?auto=compress%2Cformat&amp;ixlib=php-3.3.0)

In this article, the second part of the [Build a Twitter Clone series](https://dev.to/dillionmegida/series/18659), you will create a Profile Page for users and add the follow-users feature.

[Part 1](https://dev.to/dillionmegida/twitter-clone-part-1-connecting-users-to-stream-feeds-and-creating-a-tweet-13c2) focuses on creating the Twitter layout, authenticating users with Stream, adding the create tweet feature, and displaying the home page [activity feeds](https://getstream.io/activity-feeds/). That is a required step before you can follow the tutorial in this article, so kindly check that first before continuing with this.

## Create a Profile Page for Users

The Profile Page shows a user's information such as their cover photo, profile image, tweet count, name, username, bio, date of joining, number of followers, and followings. This page also shows the follow button, which allows other users to follow and unfollow a user. And lastly, the page shows a feed that contains the tweets made by this user.


![Profile Page](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/5e5dcaf0db16e5a4b52cf16dbcd6714c/1.-Profile-page.png?auto=compress%2Cformat&amp;ixlib=php-3.3.0)

We will break this page into different components. Let's start from the header.

### Create a ProfileHeader Component

This component holds the user's cover photo, the number of tweets created, and the user's name:

![Profile Header](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/10b2d237c1bcda1495cea98dab09ac1a/2.-Profile-Header.png?auto=compress%2Cformat&amp;ixlib=php-3.3.0)

Create a new file **src/components/Profile/ProfileHeader.js**. Start with the imports and styles:

```js
import { useContext, useEffect, useState } from 'react'
import { useStreamContext } from 'react-activity-feed'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'

import ArrowLeft from '../Icons/ArrowLeft'
import { ProfileContext } from './ProfileContent'

const Header = styled.header`
  .top {
    display: flex;
    align-items: center;
    padding: 15px;
    color: white;
    width: 100%;
    backdrop-filter: blur(2px);
    background-color: rgba(0, 0, 0, 0.5);

    .info {
      margin-left: 30px;

      h1 {
        font-size: 20px;
      }

      &__tweets-count {
        font-size: 14px;
        margin-top: 2px;
        color: #888;
      }
    }
  }

  .cover {
    width: 100%;
    background-color: #555;
    height: 200px;
    overflow: hidden;

    img {
      width: 100%;
      object-fit: cover;
      object-position: center;
    }
  }
`
```

And next, the component:

```js
export default function ProfileHeader() {
  const navigate = useNavigate()
  const { user } = useContext(ProfileContext)
  const { client } = useStreamContext()

  const [activitiesCount, setActivitiesCount] = useState(0)

  useEffect(() => {
    const feed = client.feed('user', user.id)

    async function getActivitiesCount() {
      const activities = await feed.get()

      setActivitiesCount(activities.results.length)
    }

    getActivitiesCount()
  }, [])

  const navigateBack = () => {
    navigate(-1)
  }
}
```

When the component mounts, you get all the activities and update the activities count state.

Now, for the UI:

```js
export default function ProfileHeader() {
  // ...

  return (
    <Header>
      <div className="top">
        <button onClick={navigateBack}>
          <ArrowLeft size={20} color="white" />
        </button>
        <div className="info">
          <h1>{user.data.name}</h1>
          <span className="info__tweets-count">{activitiesCount} Tweets</span>
        </div>
      </div>
      <div className="cover">
        <img src="https://picsum.photos/500/300" />
      </div>
    </Header>
  )
}
```

### Create the ProfileBio Component

This component holds the user's information and the follow button:

![Profile Bio ](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/d1717f13d86c44045dc3bce80c3da9de/3.-Profile-Bio.png?auto=compress%2Cformat&amp;ixlib=php-3.3.0)

Create a new file **src/components/Profile/ProfileBio.js**. Import the required utilities and components, and add the styles:

```js
import { useContext } from 'react'
import styled from 'styled-components'
import { format } from 'date-fns'
import { useStreamContext } from 'react-activity-feed'

import More from '../Icons/More'
import Mail from '../Icons/Mail'
import Calendar from '../Icons/Calendar'
import { formatStringWithLink } from '../../utils/string'
import { ProfileContext } from './ProfileContent'
import FollowBtn from '../FollowBtn'

const Container = styled.div`
  padding: 20px;
  position: relative;

  .top {
    display: flex;
    justify-content: space-between;
    margin-top: calc(var(--profile-image-size) / -2);

    .image {
      width: var(--profile-image-size);
      height: var(--profile-image-size);
      border-radius: 50%;
      overflow: hidden;
      border: 4px solid black;
      background-color: #444;

      img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
    }

    .actions {
      position: relative;
      top: 55px;
      display: flex;

      .action-btn {
        border: 1px solid #777;
        margin-right: 10px;
        width: 30px;
        height: 30px;
        border-radius: 50%;
        display: flex;
        justify-content: center;
        align-items: center;
      }
    }
  }

  .details {
    color: #888;
    margin-top: 20px;

    .user {
      &__name {
        color: white;
        font-weight: bold;
      }

      &__id {
        margin-top: 2px;
        font-size: 15px;
      }

      &__bio {
        color: white;
        margin-top: 10px;
        a {
          color: var(--theme-color);
          text-decoration: none;
        }
      }

      &__joined {
        display: flex;
        align-items: center;
        margin-top: 15px;
        font-size: 15px;

        &--text {
          margin-left: 5px;
        }
      }

      &__follows {
        font-size: 15px;
        display: flex;
        margin-top: 15px;

        b {
          color: white;
        }

        &__followers {
          margin-left: 20px;
        }
      }

      &__followed-by {
        font-size: 13px;
        margin-top: 15px;
      }
    }
  }
`
```

This component imports the `FollowBtn` component for the follow functionality.

`ProfileContext` comes from `ProfileContent`, which you will create soon. From that context, this component can get the user's information of the active profile.

And for the component:

```js
const actions = [
  {
    Icon: More,
    id: 'more',
  },
  {
    Icon: Mail,
    id: 'message',
  },
]

export default function ProfileBio() {
  const { user } = useContext(ProfileContext)

  const joinedDate = format(new Date(user.created_at), 'MMMM RRRR')

  const bio = formatStringWithLink(user.data.bio)

  const isLoggedInUserProfile = user.id === client.userId
}
```

The `isLoogedInUserProfile` is required so that you can conditionally render the follow button; that is, if the profile page is not for the logged-in user.

And the UI:

```js
export default function ProfileBio() {
  // ...

  return (
    <Container>
      <div className="top">
        <div className="image">
          {' '}
          <img src={user.data.image} alt="" />
        </div>
        {!isLoggedInUserProfile && (
          <div className="actions">
            {actions.map((action) => (
              <button className="action-btn" key={action.id}>
                <action.Icon color="white" size={21} />
              </button>
            ))}
            <FollowBtn userId={user.id} />
          </div>
        )}
      </div>
      <div className="details">
        <span className="user__name">{user.data.name}</span>
        <span className="user__id">@{user.id}</span>
        <span className="user__bio" dangerouslySetInnerHTML={{ __html: bio }} />
        <div className="user__joined">
          <Calendar color="#777" size={20} />
          <span className="user__joined--text">Joined {joinedDate}</span>
        </div>
        <div className="user__follows">
          <span className="user__follows__following">
            <b>{user.following_count || 0}</b> Following
          </span>
          <span className="user__follows__followers">
            <b>{user.followers_count || 0}</b> Followers
          </span>
        </div>
        <div className="user__followed-by">
          Not followed by anyone you are following
        </div>
      </div>
    </Container>
  )
}
```

### Create the TabList Component

The **TabList** component shows the "Tweets", "Tweets &amp; Replies", "Media" and "Likes" tabs:

![TabList](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/0af89f97d40d3cb4a18adff92e6e60cf/4.-Tablist.png?auto=compress%2Cformat&amp;ixlib=php-3.3.0)

Although the only functioning tab will be "Tweets", as that is the scope of this tutorial, it's nice also to have this on the UI.

Create a new file called **src/components/Profile/TabList.js** and paste the following:

```js
import classNames from 'classnames'
import { useState } from 'react'
import styled from 'styled-components'

const Container = styled.div`
  display: grid;
  grid-template-columns: 1fr 2fr 1fr 1fr;
  border-bottom: 1px solid #555;
  width: 100%;

  .tab {
    color: #777;
    padding: 0 35px;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    font-size: 15px;

    &:hover {
      background-color: #111;
    }

    &__label {
      position: relative;
      width: 100%;
      padding: 20px 7px;

      &.active {
        color: white;

        &::after {
          content: '';
          height: 3px;
          width: 100%;
          background-color: var(--theme-color);
          border-radius: 40px;
          position: absolute;
          bottom: 0;
          left: 0;
        }
      }
    }
  }
`

const tabs = [
  {
    id: 'tweets',
    label: 'Tweets',
  },
  {
    id: 'tweet-replies',
    label: 'Tweets & replies',
  },
  {
    id: 'media',
    label: 'Media',
  },
  {
    id: 'likes',
    label: 'Likes',
  },
]

export default function TabList() {
  const [activeTab, setActiveTab] = useState(tabs[0].id)

  return (
    <Container>
      {tabs.map((tab) => (
        <button
          onClick={() => setActiveTab(tab.id)}
          className="tab"
          key={tab.id}
        >
          <span
            className={classNames(
              'tab__label',
              activeTab === tab.id && 'active'
            )}
          >
            {tab.label}
          </span>
        </button>
      ))}
    </Container>
  )
}
```

This component also sets the active tab on clicking each tab.

### Create a ProfileTweets Component

This component shows a feed of tweet activities for the user on the active profile. Create a new file **src/components/Profile/ProfileTweets.js** with the following code:

```js
import { useContext } from 'react'
import { FlatFeed } from 'react-activity-feed'

import TweetBlock from '../Tweet/TweetBlock'
import { ProfileContext } from './ProfileContent'

export default function MyTweets() {
  const { user } = useContext(ProfileContext)

  return (
    <div>
      <FlatFeed
        Activity={TweetBlock}
        userId={user.id}
        feedGroup="user"
        notify
      />
    </div>
  )
}
```

From the `ProfileContext` (which you will create soon), you get the profile user. Using the `FlatFeed` component from `react-activity-feed` and the custom `TweetBlock` created in part one, you can display the activities made by this user.

### Create a ProfileContent Component

With the profile page components created, you can compose the `ProfileContent` component.

Create a new file **src/components/Profile/ProfileContent.js**. Add the imports and styles:

```js
import styled from 'styled-components'
import { createContext, useEffect, useState } from 'react'
import { useStreamContext } from 'react-activity-feed'
import { useParams } from 'react-router-dom'

import ProfileHeader from './ProfileHeader'
import LoadingIndicator from '../LoadingIndicator'
import ProfileBio from './ProfileBio'
import TabList from './TabList'
import ProfileTweets from './ProfileTweets'

const Container = styled.div`
  --profile-image-size: 120px;

  .tab-list {
    margin-top: 30px;
  }
`
```

And next, the context and component:

```js
export const ProfileContext = createContext()

export default function ProfileContent() {
  const { client } = useStreamContext()

  const [user, setUser] = useState(null)
  const { user_id } = useParams()

  useEffect(() => {
    const getUser = async () => {
      const user = await client.user(user_id).get({ with_follow_counts: true })

      setUser(user.full)
    }

    getUser()
  }, [user_id])

  if (!client || !user) return <LoadingIndicator />
}
```

In the `useEffect` hook, you get the user's details and update the `user` state with the full details. As for the UI:

```js
export default function ProfileContent() {
  // ...

  return (
    <ProfileContext.Provider value={{ user }}>
      <Container>
        <ProfileHeader />
        <main>
          <ProfileBio />
          <div className="tab-list">
            <TabList />
          </div>
          <ProfileTweets />
        </main>
      </Container>
    </ProfileContext.Provider>
  )
}
```

The `Profile.Context` provides the user object to the children components, as you have seen when creating the profile components.

Finally, the last component – the page component.

### Create a Profile Page Component

Create a new file: **src/pages/Profile.js** with the following code:

```js
import Layout from '../components/Layout'
import ProfileContent from '../components/Profile/ProfileContent'

export default function Profile() {
  return (
    <Layout>
      <ProfileContent />
    </Layout>
  )
}
```

The next step is to add a route for this page in **App.js**. Import the `ProfileContent` component first:

```js
// other imports
import Profile from './pages/Profile'
```

And the route:

```js
<Route element={<Profile />} path="/:user_id" />
```

With your development server on, when you click on the profile link in the left section or go to a user, for example, **localhost:3000/getstream_io**, you will see the profile page of this user with their tweets.

## Add a Follow Feature

When a user, say **userA**, follows another user, say **userB**, userA subscribes to userB's feed. They can then see the activities made by the user they followed. Bringing this idea to tweets, when userA follows userB, userA can see the tweets made by userB on userA's timeline (the homepage).

Let us implement the follow feature.

### Build a Custom useFollow Hook

Although this implementation will only be used in the **FollowBtn** component, it will be helpful to have this as a custom hook to avoid making the component file ambiguous.

Create a new file **src/hooks/useFollow.js**. I will walk you through building this hook gradually. Add the imports and initialize the state:

```js
import { useEffect, useState } from 'react'
import { useStreamContext } from 'react-activity-feed'

export default function useFollow({ userId }) {
  const { client } = useStreamContext()

  const [isFollowing, setIsFollowing] = useState(false)
}
```

The component receives the `userId` prop. This prop is the **id** of the user that is to be followed or unfollowed. The `client` object from `useStreamContext` provides the `id` of the logged-in user. Going forward, I will refer to the logged-in user as **userA** and the user to be followed as **userB**.

The next step is to check if **userA** is already following **userB**. You can do this when the component mounts with `useEffect`:

```js
useEffect(() => {
  async function init() {
    const response = await client
      .feed('timeline', client.userId)
      .following({ filter: [`user:${userId}`] })

    setIsFollowing(!!response.results.length)
  }

  init()
}, [])
```

In the `useEffect` hook, you have an `init` function which, when called, gets **userA**'s **timeline** feed and filters the results based on **following** to include **userB**. If the final results array is not empty, it means **userA** already follows **userB**'s timeline feed; else, A does not follow B.

Using that result, you can update the `following` state.

Next, create a **toggleFollow** function:

```js
const toggleFollow = async () => {
  const action = isFollowing ? 'unfollow' : 'follow'

  const timelineFeed = client.feed('timeline', client.userId)
  await timelineFeed[action]('user', userId)

  setIsFollowing((isFollowing) => !isFollowing)
}
```

In this function, you get the `timeLineFeed` on the logged-in user, and on that feed, you can either call the `follow()` or `unfollow()` method on **userB**'s feed. Both methods accept the "user" feed type and the `userId`.

At the end of this hook, you will return the `isFollowing` state and the `toggleFollow` method. The hook file should include this code:

```js
import { useEffect, useState } from 'react'
import { useStreamContext } from 'react-activity-feed'

export default function useFollow({ userId }) {
  const { client } = useStreamContext()

  const [isFollowing, setIsFollowing] = useState(false)

  useEffect(() => {
    async function init() {
      const response = await client
        .feed('timeline', client.userId)
        .following({ filter: [`user:${userId}`] })

      setIsFollowing(!!response.results.length)
    }

    init()
  }, [])

  const toggleFollow = async () => {
    const action = isFollowing ? 'unfollow' : 'follow'

    const timelineFeed = client.feed('timeline', client.userId)
    await timelineFeed[action]('user', userId)

    setIsFollowing((isFollowing) => !isFollowing)
  }

  return { isFollowing, toggleFollow }
}
```

### Add Follow Functionality to the FollowBtn Component

Now, you can add this hook to **FollowBtn**. Go to **src/components/FollowBtn.js**, remove the `useState` import and import the follow hook:

```js
// other imports
import useFollow from '../hooks/useFollow'
```

Then, replace the `useState` declaration in the component with the hook and also update the component UI with the values from the hook:

```js
export default function FollowBtn({ userId }) {
  const { isFollowing, toggleFollow } = useFollow({ userId })

  return (
    <Container>
      <button
        className={classNames(isFollowing ? 'following' : 'not-following')}
        onClick={toggleFollow}
      >
        {isFollowing ? (
          <div className="follow-text">
            <span className="follow-text__following">Following</span>
            <span className="follow-text__unfollow">Unfollow</span>
          </div>
        ) : (
          'Follow'
        )}
      </button>
    </Container>
  )
}
```

Now, you have the follow functionality. You can test it by going to a different user's profile and clicking the follow button:

![Twitter Follow Feature](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/b5ede07b42c7ad64bd243cb5270d4d1f/5.-follow-feature.png?auto=compress%2Cformat&amp;ixlib=php-3.3.0)

## Show Tweets of a User You Follow

When **userA** follows **userB**, A should see the tweets of B on A's homepage. Currently, the homepage shows A's tweets (as we concluded in Part 1), so let us fix that.

Go to **src/components/Home/Timeline.js**. In this component, you will see the `Feed` component with a `feedGroup` prop of "user". Change the prop value to "timeline" to show the timeline feed on the homepage. The timeline feed shows activities from different user feeds that the timeline feed follows.

Now, when you go to the homepage of a logged-in user, you should see the tweets made by the users they follow.

To ensure you have the following, I'll use user **getstream_io** and user **iamdillion** to show you what to do:

1. Go to the start page (**/**), and select user **getstream_io**
2. Create two tweets
3. Go back to the start page and select user **iamdillion**
4. Go to user **getstream_io**'s profile, and follow the user
5. Go to the homepage, and you should see **getstream_io**'s tweets

![Show Timeline from homepage](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/b90815b02d32a184916596d6da9e1c22/6.-Home-page-showing-timeline.png?auto=compress%2Cformat&amp;ixlib=php-3.3.0)

## Conclusion

In this tutorial, you have successfully created a profile page, added the follow functionality, and populated the homepage with the tweets of users that the logged-in user follows. What Streamer lacks now is reactions (likes and comments), tweet threads (which show the list of comments made to a tweet), and notifications.

Stay tuned for part three (coming soon) where you learn how to add reactions, threads and a notifications page.