Social Media Basics

Enable a list of users to follow and unfollow each other.


Environment

Use the environment you are most comfortable with. I recommend using create-react-app to create a local version of the project so that you can inspect the DOM easily from your browser when debugging. Alternatively, you can work from a blank React template in CodeSandbox. Starter code is provided after the problem specification.

Specification

Write a functional component to display an input field allowing the user to enter a name. When the user submits, the name should be added to a list of names displayed below the input field. Below the list, render two input fields to allow the user to enter two names, and when the user submits, the first name should follow the name. In other words, the second name should be added to the first name’s following and the first name should be added to the second name’s followers. The finished component is shown below:

The final product
The final product

When the user clicks on a name in the list, issue a browser alert that displays their following and followers counts:

Browser alert displaying Justin’s followers and following counts
Browser alert displaying Justin’s followers and following counts

Be sure to consider edge cases, e.g. if a person tries to follow themselves or someone that they already follow.

Starter Code

Here is some code to get you started:

import React, { useState } from "react";

function App() {
  return <Followers />;
}

const Followers = () => {
  // YOUR CODE HERE
};

export default App;

Feel free to divide the challenge into multiple subcomponents.

Solution

import React, { useState } from "react";

function App() {
  return <Followers />;
}

const Followers = () => {
  const [userList, setUserList] = useState([]);
  const [newUser, setNewUser] = useState("");
  const [user1, setUser1] = useState("");
  const [user2, setUser2] = useState("");

  const createUser = (e) => {
    e.preventDefault();
    if (!userList.some((user) => user.name == newUser)) {
      setUserList([
        ...userList,
        { name: newUser, followers: [], following: [] },
      ]);
      setNewUser("");
    } else alert("This user already exists");
  };

  const submitNewFollow = (e) => {
    e.preventDefault();
    const user1Index = userList.findIndex((user) => user.name == user1);
    const user2Index = userList.findIndex((user) => user.name == user2);

    if (user1 == user2) alert(user1 + "cannot follow themselves.");
    else if (user1Index == -1) alert(user1 + " is not yet a user");
    else if (user2Index == -1) alert(user2 + " is not yet a user");
    else if (userList[user1Index].following.includes(user2))
      alert(user1 + " already follows " + user2);
    else {
      // create new objects to modify and insert into array copy
      const newUser1Obj = { ...userList[user1Index] };
      const newUser2Obj = { ...userList[user2Index] };
      newUser1Obj.following = [...newUser1Obj.following, user2];
      newUser2Obj.followers = [...newUser2Obj.followers, user1];
      const shallowListCopy = [...userList];
      shallowListCopy[user1Index] = newUser1Obj;
      shallowListCopy[user2Index] = newUser2Obj;
      setUserList(shallowListCopy);
      setUser1("");
      setUser2("");
    }
  };

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        textAlign: "center",
      }}
    >
      <form onSubmit={createUser}>
        <input
          style={{ width: 200, margin: 30 }}
          value={newUser}
          required
          placeholder={"Enter new user"}
          onChange={(e) => setNewUser(e.target.value)}
        />
      </form>
      <h4>User List</h4>
      {userList.map((user) => (
        <div
          onClick={() => {
            alert(
              user.name +
                " has " +
                user.followers.length +
                " followers and is following " +
                user.following.length +
                " people."
            );
          }}
          style={{ cursor: "pointer" }}
        >
          {user.name}
        </div>
      ))}
      <form
        style={{ display: "flex", margin: 50, alignSelf: "center" }}
        onSubmit={submitNewFollow}
      >
        <input
          style={{ width: 100 }}
          value={user1}
          required
          onChange={(e) => setUser1(e.target.value)}
        />
        <div style={{ margin: "0px 10px 0px 10px" }}>will now follow</div>
        <input
          style={{ width: 100 }}
          value={user2}
          required
          onChange={(e) => setUser2(e.target.value)}
        />
        <input type={"submit"} />
      </form>
    </div>
  );
};

export default App;

The solution above first sets up four state variables — one to store an array of user objects and three to store the values of the three input fields. In the return statement, we use the map function to produce a clickable name element for each user in the userList array. On click, an alert is issued containing the user’s followers and following counts. The bulk of the component’s logic is contained in the submit handlers for the forms. When a site visitor submits a new name, the component creates a new user and adds the user to the userList array as long as there is no other user with the same name.

The submit handler for the second form is slightly longer. We need to ensure the user is not attempting to make a person follow themselves, that both people entered are present in the list, and that the first person entered does not already follow the second person. If these conditions are met, the appropriate users in the userList array can be carefully updated to ensure the array is not being directly modified in the process. This requires making a shallow copy of the userList array and copying the objects that need to be modified before finally modifying them and inserting them into the array copy.

For an additional challenge, allow the site visitor to make users unfollow each other.


useState