Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A few improvements in CorpComment project #17

Open
Rope-a-dope opened this issue Jun 12, 2024 · 0 comments
Open

A few improvements in CorpComment project #17

Rope-a-dope opened this issue Jun 12, 2024 · 0 comments

Comments

@Rope-a-dope
Copy link

  1. Since we use Zustand to manage the state, there is no need to pass props to FeedbackForm and HashtagItem, we can just use Zustand in them directly.
  2. It's always a good idea to use custom hooks as explained in https://tkdodo.eu/blog/working-with-zustand and your video https://www.youtube.com/watch?v=I7dwJxGuGYQ&t=69s&ab_channel=ByteGrad. It's the same for other tools as for useContext. But because we drive the state companList and FilteredFeedbackItems inside the store, we must export getCompanyList() and getFilteredFeedbackItems() with () to make sure they are executed inside the store.

feedbackItemsStore.ts

import { create } from "zustand";
import { TFeedbackItem } from "../lib/types";

type Store = {
  feedbackItems: TFeedbackItem[];
  isLoading: boolean;
  errorMessage: string;
  selectedCompany: string;
  actions: {
    getCompanyList: () => string[];
    getFilteredFeedbackItems: () => TFeedbackItem[];
    addItemToList: (text: string) => Promise<void>;
    selectCompany: (company: string) => void;
    fetchFeedbackItems: () => Promise<void>;
  };
};

export const useFeedbackItemsStore = create<Store>((set, get) => ({
  feedbackItems: [],
  isLoading: false,
  errorMessage: "",
  selectedCompany: "",
  actions: {
    getCompanyList: () => {
      const state = get();
      return state 
        .feedbackItems.map((item) => item.company)
        .filter((company, index, array) => {
          return array.indexOf(company) === index;
        });
    },
    getFilteredFeedbackItems: () => {
      const state = get();

      return state.selectedCompany
        ? state.feedbackItems.filter(
            (feedbackItem) => feedbackItem.company === state.selectedCompany
          )
        : state.feedbackItems;
    },
    addItemToList: async (text: string) => {
      const companyName = text
        .split(" ")
        .find((word) => word.includes("#"))!
        .substring(1);

      const newItem: TFeedbackItem = {
        id: new Date().getTime(),
        text: text,
        upvoteCount: 0,
        daysAgo: 0,
        company: companyName,
        badgeLetter: companyName.substring(0, 1).toUpperCase(),
      };

      set((state) => ({
        feedbackItems: [...state.feedbackItems, newItem],
      }));

      await fetch(
        "https://bytegrad.com/course-assets/projects/corpcomment/api/feedbacks",
        {
          method: "POST",
          body: JSON.stringify(newItem),
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
          },
        }
      );
    },
    selectCompany: (company: string) => {
      set(() => ({
        selectedCompany: company,
      }));
    },
    fetchFeedbackItems: async () => {
      set(() => ({
        isLoading: true,
      }));

      try {
        const response = await fetch(
          "https://bytegrad.com/course-assets/projects/corpcomment/api/feedbacks"
        );

        if (!response.ok) {
          throw new Error();
        }

        const data = await response.json();
        set(() => ({
          feedbackItems: data.feedbacks,
        }));
      } catch (error) {
        set(() => ({
          errorMessage: "Something went wrong. Please try again later.",
        }));
      }

      set(() => ({
        isLoading: false,
      }));
    },
  },
}));

export const useFeedbackItems = () => useFeedbackItemsStore((state) => state.feedbackItems);
export const useIsLoading = () => useFeedbackItemsStore((state) => state.isLoading);
export const useErrorMessage = () => useFeedbackItemsStore((state) => state.errorMessage);
export const useSelectedCompany = () => useFeedbackItemsStore((state) => state.selectedCompany);
export const useFeedbackItemActions = () => useFeedbackItemsStore((state) => state.actions);
export const useCompanyList = () => useFeedbackItemsStore((state) => state.actions.getCompanyList()); //Make sure it executes inside Zustand store.
export const useFilteredFeedbackItems = () => useFeedbackItemsStore((state) => state.actions.getFilteredFeedbackItems()); //Make sure it executes inside Zustand store.

FeedbackForm.tsx

import { useState } from "react";
import { MAX_CHARACTERS } from "../../lib/constants";
import { useFeedbackItemActions } from "../../stores/feedbackItemsStore";

export default function FeedbackForm() {
  const {addItemToList} = useFeedbackItemActions();
  const [text, setText] = useState("");
  const [showValidIndicator, setShowValidIndicator] = useState(false);
  const [showInvalidIndicator, setShowInvalidIndicator] = useState(false);
  const charCount = MAX_CHARACTERS - text.length;

  const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
    const newText = event.target.value;
    if (newText.length > MAX_CHARACTERS) {
      return;
    }
    setText(newText);
  };

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    // basic validation
    if (text.includes("#") && text.length >= 5) {
      setShowValidIndicator(true);
      setTimeout(() => setShowValidIndicator(false), 2000);
    } else {
      setShowInvalidIndicator(true);
      setTimeout(() => setShowInvalidIndicator(false), 2000);
      return;
    }

    addItemToList(text);
    setText("");
  };

  return (
    <form
      onSubmit={handleSubmit}
      className={`form ${showValidIndicator && "form--valid"} ${
        showInvalidIndicator && "form--invalid"
      }`}
    >
      <textarea
        value={text}
        onChange={handleChange}
        id="feedback-textarea"
        placeholder="blabla"
        spellCheck={false}
      />

      <label htmlFor="feedback-textarea">
        Enter your feedback here, remember to #hashtag the company
      </label>

      <div>
        <p className="u-italic">{charCount}</p>
        <button>
          <span>Submit</span>
        </button>
      </div>
    </form>
  );
}

FeedbackList.tsx

import {
  useErrorMessage,
  useFilteredFeedbackItems,
  useIsLoading
} from "../../stores/feedbackItemsStore";
import ErrorMessage from "../ErrorMessage";
import Spinner from "../Spinner";
import FeedbackItem from "./FeedbackItem";

export default function FeedbackList() {
  const isLoading = useIsLoading();
  const errorMessage = useErrorMessage();
  const filteredFeedbackItems = useFilteredFeedbackItems();

  return (
    <ol className="feedback-list">
      {isLoading && <Spinner />}

      {errorMessage && <ErrorMessage message={errorMessage} />}

      {filteredFeedbackItems.map((feedbackItem) => (
        <FeedbackItem key={feedbackItem.id} feedbackItem={feedbackItem} />
      ))}
    </ol>
  );
}

HashtagItem.tsx

import { useFeedbackItemActions } from "../../stores/feedbackItemsStore";

type HashtagItemProps = {
  company: string;
};

export default function HashtagItem({
  company,
}: HashtagItemProps) {
  const { selectCompany } = useFeedbackItemActions(); 
  return (
    <li key={company}>
      <button onClick={() => selectCompany(company)}>#{company}</button>
    </li>
  );
}

HashtagList.tsx

import { useCompanyList } from "../../stores/feedbackItemsStore";
import HashtagItem from "./HashtagItem";

export default function HashtagList() {
  const companyList = useCompanyList();

  return (
    <ul className="hashtags">
      {companyList.map((company) => (
        <HashtagItem
          key={company}
          company={company}
        />
      ))}
    </ul>
  );
}

App.tsx

import { useEffect } from "react";
import { useFeedbackItemActions } from "../stores/feedbackItemsStore";
import HashtagList from "./hashtag/HashtagList";
import Container from "./layout/Container";
import Footer from "./layout/Footer";

function App() {
  const { fetchFeedbackItems } = useFeedbackItemActions();

  useEffect(() => {
    fetchFeedbackItems();
  }, [fetchFeedbackItems]);

  return (
    <div className="app">
      <Footer />

      <Container />

      <HashtagList />
    </div>
  );
}

export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant