來學 React 吧之七_React 實作留言板


Posted by Christy on 2021-11-22

一、先整理一下資料夾吧!

1. 重點:

a. SRC 資料夾底下的 index.js 檔案,不可以被放在任何資料夾底下

b. SRC 資料夾底下的 index.js 檔案,先 import App from "./components/App";

c. App 資料夾底下的 index.js 裡面可以寫成

import App from "./App";
export default App;

或者比較新的語法 re-export 可以用 export { default } from "./App";

d. 等於是 SRC 資料夾底下的 index.js 檔案去找到 -> components/App 再去找到 -> App 資料夾裡面的 index.js -> App.js

e. 這是常用的小撇步

2. 目前的結構是:

第一層 SRC: 內含 components 資料夾、constants 資料夾、.eslintrc.json 檔案、index.css 檔案、index.js 檔案

第二層 components 資料夾,內含三個資料夾,分別是:

a. App: 裡面有三個檔案,App.js、App test.js、index.js

b. Message Board

c. Todo: 之前做的 todo list

第二層 constants 裡面有 breakpoint.js 檔案

二、先來切個版吧!

1.犯的錯誤:

a. css 寫錯 component,畫面就跑不出來了,下次要邊寫邊確認畫面

2. 切版內容

// App.js code

import React from "react";
import styled from "styled-components";

const Page = styled.div`
  width: 300px;
  margin: 0 auto;
`;

const Title = styled.h1`
  color: blue;
`;

const MessageForm = styled.form`
  margin-top: 16px;
`;

const MessageTextArea = styled.textarea`
  display: block;
  width: 100%;
`;

const SubmitButton = styled.button`
  margin-top: 8px;
`;

const MessageList = styled.div`
  margin-top: 16px;
`;

const MessageContainer = styled.div`
  border: 1px solid black;
  padding: 8px 16px;
  border-radius: 5px;
`;

const MessageHead = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
`;

const MessageAuthor = styled.div`
  color: blue;
  font-size: 14px;
`;

const MessageTime = styled.div``;

const MessageBody = styled.div`
  margin-top: 16px;
  font-size: 16px;
`;

function Message({ author, time, children }) {
  return (
    <MessageContainer>
      <MessageHead>
        <MessageAuthor>{author}</MessageAuthor>
        <MessageTime>{time}</MessageTime>
      </MessageHead>
      <MessageBody>{children}</MessageBody>
    </MessageContainer>
  );
}
function App() {
  return (
    <Page>
      <Title>留言板</Title>
      <MessageForm>
        <MessageTextArea rows={10} />
        <SubmitButton>Submit</SubmitButton>
      </MessageForm>
      <MessageList>
        <Message author={"christy"} time={"2021-11-11 11:11:11"}></Message>
      </MessageList>
    </Page>
  );
}

export default App;

三、來拿資料吧!把 API 串上去

Lidemy 學生專用 API Server

1. 串完 API 內容

先抓資料 -> 處理錯誤 -> 修 ESlint 提示:PropTypes

// App.js code
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";

const API_ENDPOINT = "https://student-json-api.lidemy.me/comments";

const Page = styled.div`
  width: 360px;
  margin: 0 auto;
`;

const Title = styled.h1`
  color: blue;
`;

const MessageForm = styled.form`
  margin-top: 16px;
`;

const MessageTextArea = styled.textarea`
  display: block;
  width: 100%;
`;

const SubmitButton = styled.button`
  margin-top: 8px;
`;

const MessageList = styled.div`
  margin-top: 16px;
`;

const MessageContainer = styled.div`
  border: 1px solid black;
  padding: 8px 16px;
  border-radius: 5px;
  & + & {
    margin-top: 8px;
  }
`;

const MessageHead = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 3px;
  border-bottom: 3px solid white;
`;

const MessageAuthor = styled.div`
  color: blue;
  font-size: 14px;
`;

const MessageTime = styled.div``;

const MessageBody = styled.div`
  margin-top: 16px;
  font-size: 16px;
`;

const ErrorMessage = styled.div`
  margin-top: 16px;
  color: red;
`;

function Message({ author, time, children }) {
  return (
    <MessageContainer>
      <MessageHead>
        <MessageAuthor>{author}</MessageAuthor>
        <MessageTime>{time}</MessageTime>
      </MessageHead>
      <MessageBody>{children}</MessageBody>
    </MessageContainer>
  );
}

Message.propTypes = {
  author: PropTypes.string,
  time: PropTypes.string,
  children: PropTypes.node,
};

function App() {
  const [messages, setMessages] = useState(null);
  const [apiError, setApiError] = useState(null);

  useEffect(() => {
    fetch(API_ENDPOINT)
      .then((res) => res.json())
      .then((data) => {
        setMessages(data);
      })
      .catch((err) => {
        setApiError(err.message);
      });
  }, []);

  return (
    <Page>
      <Title>留言板</Title>
      <MessageForm>
        <MessageTextArea rows={10} />
        <SubmitButton>Submit</SubmitButton>
      </MessageForm>
      {apiError && (
        <ErrorMessage>Something went wrong. {apiError.toString()}</ErrorMessage>
      )}
      {messages && messages.length === 0 && <div>No Message</div>}
      <MessageList>
        {messages &&
          messages.map((message) => (
            <Message
              key={message.id}
              author={message.nickname}
              time={new Date(message.createdAt).toLocaleString()}
            >
              {message.body}
            </Message>
          ))}
      </MessageList>
    </Page>
  );
}

export default App;

2. 實作新增留言功能

import React, { useState, useEffect } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";

const API_ENDPOINT =
  "https://student-json-api.lidemy.me/comments?_sort=createdAt&_order=desc";

const Page = styled.div`
  width: 360px;
  margin: 0 auto;
`;

const Title = styled.h1`
  color: blue;
`;

const MessageForm = styled.form`
  margin-top: 16px;
`;

const MessageTextArea = styled.textarea`
  display: block;
  width: 100%;
`;

const SubmitButton = styled.button`
  margin-top: 8px;
`;

const MessageList = styled.div`
  margin-top: 16px;
`;

const MessageContainer = styled.div`
  border: 1px solid black;
  padding: 8px 16px;
  border-radius: 5px;
  & + & {
    margin-top: 8px;
  }
`;

const MessageHead = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 3px;
  border-bottom: 3px solid white;
`;

const MessageAuthor = styled.div`
  color: blue;
  font-size: 14px;
`;

const MessageTime = styled.div``;

const MessageBody = styled.div`
  margin-top: 16px;
  font-size: 16px;
`;

const ErrorMessage = styled.div`
  margin-top: 16px;
  color: red;
`;

const Loading = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  color: white;
  display: flex;
  font-size: 30px;
  align-items: center;
  justify-content: center;
`;

function Message({ author, time, children }) {
  return (
    <MessageContainer>
      <MessageHead>
        <MessageAuthor>{author}</MessageAuthor>
        <MessageTime>{time}</MessageTime>
      </MessageHead>
      <MessageBody>{children}</MessageBody>
    </MessageContainer>
  );
}

Message.propTypes = {
  author: PropTypes.string,
  time: PropTypes.string,
  children: PropTypes.node,
};

function App() {
  const [messages, setMessages] = useState(null);
  const [messageApiError, setMessageApiError] = useState(null);
  const [value, setValue] = useState();
  const [postMessageError, setPostMessageError] = useState();
  const [isLoadingPostMessage, setIsLoadingPostMessage] = useState(false);

  const fetchMsgs = () => {
    return fetch(API_ENDPOINT)
      .then((res) => res.json())
      .then((data) => {
        setMessages(data);
      })
      .catch((err) => {
        setMessageApiError(err.message);
      });
  };

  const handleTextareaChange = (e) => {
    setValue(e.target.value);
  };

  const handleTextareaFocus = () => {
    setPostMessageError(null);
  };

  const handleFormSubmit = (e) => {
    e.preventDefault();
    setIsLoadingPostMessage(true);
    fetch("https://student-json-api.lidemy.me/comments", {
      method: "POST",
      headers: {
        "content-type": "application/json",
      },
      body: JSON.stringify({
        nickname: "hi",
        body: value,
      }),
    })
      .then((res) => res.json())
      .then((data) => {
        setIsLoadingPostMessage(false);
        if (data.ok === 0) {
          setPostMessageError(data.message);
          return;
        }
        setValue("");
        fetchMsgs();
      })
      .catch((err) => {
        setIsLoadingPostMessage(false);
        setPostMessageError(err.message);
      });
  };

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

  return (
    <Page>
      {isLoadingPostMessage && <Loading>Loading...</Loading>}
      <Title>留言板</Title>
      <MessageForm onSubmit={handleFormSubmit}>
        <MessageTextArea
          value={value}
          onChange={handleTextareaChange}
          onFocus={handleTextareaFocus}
          rows={10}
        />
        <SubmitButton>Submit</SubmitButton>
        {postMessageError && <ErrorMessage>{postMessageError}</ErrorMessage>}
      </MessageForm>
      {messageApiError && (
        <ErrorMessage>
          Something went wrong. {messageApiError.toString()}
        </ErrorMessage>
      )}
      {messages && messages.length === 0 && <div>No Message</div>}
      <MessageList>
        {messages &&
          messages.map((message) => (
            <Message
              key={message.id}
              author={message.nickname}
              time={new Date(message.createdAt).toLocaleString()}
            >
              {message.body}
            </Message>
          ))}
      </MessageList>
    </Page>
  );
}

export default App;









Related Posts

[筆記]Netlist 01 -Introduction & Framework

[筆記]Netlist 01 -Introduction & Framework

Web開發學習筆記01—Primitive type v.s. Object type

Web開發學習筆記01—Primitive type v.s. Object type

[GIT101] Git 超新手入門--筆記

[GIT101] Git 超新手入門--筆記


Comments