✍️
React(Next.js)で作るチャット風タスク返信機能
できるもの
前提
Next.jsの勉強中です
チャット画面の実装に関する備忘録として記載します
相手はタスクを覚えてくれるロボットくんという想定です
フロントのみの記事になりますが、フロントで生成している配列のデータの部分をAPIを叩く挙動にしてバックエンドと繋げてあげればデータの保存ができると思います
後は認証機能とユーザー間のテーブルをpostできるようにすれば、ユーザー同士てチャットができるのでは、とワクワクしています
実装内容
-
覚えてという言葉の後にタスクを入力すると覚えてくれる
-
教えてと送るとタスクを返してくれる
-
エンターキーを押してフォーム送信の挙動
-
チャット相手と自分のデータを一緒の配列にして、フラグで誰の発言か判定し、動的にCSSを変更する機能
-
配列の中で配列を展開する実装(チャットの中で登録したタスク一覧を表示する)
-
常に最新の文言に焦点が合うようにした自動スクロール機能
-
ちょっとした自動返信機能
-
自動返信機能の返信を待つ間に連投できない機能
-
送信後にフォームを空にする機能
-
配列にデータを追加して、それを画面に即時反映というのが、Reactだとpushでできないみたいで、データを追加するたびに配列自体を更新していました
使用言語、ライブラリ、フレームワーク、プラグイン
- Next.js
- typeScript
- styled-components
- react-custom-scrollbars-2
- iconify
何かご意見や改良点などあれば、教えていただけると非常にありがたいです。私自身も、改善に取り組んでまいります。
コード
import styled from "styled-components";
import React, { useState, useEffect, useRef, useLayoutEffect } from "react";
import { Icon } from '@iconify/react';
import { Scrollbars } from "react-custom-scrollbars-2";
const style = {
width: "100%",
height: "500px",
};
const ChatPage = () => {
const footRef = useRef(null)
const [content, setContent] = useState([])
const [rememberContent, setRememberContent] = useState([])
const [phrase, setPhrase] = useState("")
const [changeColor, setChangeColor] = useState("#e0e0e0")
const [stop, setStop] = useState(false)
const [remember, setRemember] = useState(false)
const handleKeyDown = (e) => {
if (stop) { return }
if (e.keyCode === 13) {
if (!phrase) { return }
setContent([
...content, {
user: 1,
word: phrase,
side: "flex-end"
}])
setPhrase("")
}
}
const handleSubmit = () => {
if (stop) { return }
setContent([
...content, {
user: 1,
// user:1が自分、user:2が相手(ユーザー間だったらuser_idとか入れたい)
word: phrase,
side: "flex-end"
}])
setPhrase("")
}
const replay = () => {
const flag = content.slice(-1)[0]
if (!flag) { return }
if (flag.user == 1) {
if (flag.word.match(/remember|覚えて/)) {
setStop(true)
setTimeout(function () {
setContent([
...content, {
user: 2,
word: "何を覚えますか?",
side: "flex-start"
}]), setRemember(true), setStop(false)
}, 1000)
}
if (remember == true) {
setRememberContent([...rememberContent, {
word: flag.word,
complete: false,
side: "flex-start"
}])
setRemember(false)
setTimeout(function () {
setContent([
...content, {
user: 2,
word: "覚えました",
side: "flex-start"
}])
}, 1000)
}
if (flag.word.match(/教えて|notify/)) {
setStop(true)
setTimeout(function () {
setContent([
...content, {
user: 2,
word: "こちらです",
side: "flex-start",
rememberContent: rememberContent
}]), setStop(false)
}, 1000)
}
if (flag.word.match(/こんにちは/)) {
setStop(true)
const num = Math.floor(Math.random() * 2)
const kuji = ['こんにちは', 'どうかされましたか?'][num]
setTimeout(function () {
setContent([
...content, {
user: 2,
word: kuji,
side: "flex-start"
}]), setStop(false)
}, 1000)
}
if (flag.word.match(/画像/)) {
setStop(true)
setTimeout(function () {
setContent([
...content, {
user: 2,
word: "どうぞ",
image: "任意の画像.png",
side: "flex-start"
}]), setStop(false)
}, 1000)
}
} else if (flag.user == 2) {
if (flag.word.match(/どうかされましたか?/)) {
setTimeout(function () {
setContent([
...content, {
user: 2,
word: `${"覚えて と送信したあとに打ち込んだ言葉を覚えます。教えて と送信すると覚えた言葉を返します。"}`,
side: "flex-start"
}])
}, 1000)
}
}
}
useEffect(() => {
replay()
}, [content]);
useLayoutEffect(() => {
if (footRef.current) {
footRef.current.scrollIntoView();
}
}, [content]);
return (
<>
<Span>
</Span>
<Scrollbars style={style}>
<Display color={changeColor}>
<Column>
{content.map((phraseArray, index) => (
<div key={phraseArray}>
<Phrase side={phraseArray.side}>
<Background>
{phraseArray.word}
</Background>
</Phrase>
{function () {
if (phraseArray.image) {
return (
<Phrase side={phraseArray.side}>
<ImageBackground>
<ChatImage src={phraseArray.image} />
</ImageBackground>
</Phrase>
)
}
}()}
{function () {
if (phraseArray.rememberContent) {
return (<>
<Phrase side={"flex-start"}>
<ListBackground>
{
rememberContent.map((rememberArray) => (
<div key={rememberArray}>
・{rememberArray.word}
</div>
))
}
</ListBackground>
</Phrase>
</>
)
}
}()}
{index === content.length - 1 && <div ref={footRef}></div>}
</div>
))}
</Column>
</Display>
</Scrollbars>
<InputFormArea>
<Form>
<TextInputArea
onKeyDown={(e) => handleKeyDown(e)}
onChange={(event) => setPhrase(event.target.value)}
value={phrase}
>
</TextInputArea>
<SubmitButton
onClick={() => handleSubmit()}
>
<Icon icon="fluent:send-16-regular" width="20" height="20" />
</SubmitButton>
</Form>
</InputFormArea>
</>
);
};
export default ChatPage;
const ChatImage = styled.img`
max-width:90%;
object-fit:cover;
`
const Span = styled.div`
height:50px;
width:100%;
`
const Background = styled.div`
background-color:white;
height:100%;
min-height:45px;
padding:10px 20px;
border-radius:10px;
display:flex;
align-items: center;
text-align:left;
max-width:70%;
`
const ListBackground = styled.div`
background-color:white;
height:100%;
min-height:45px;
padding:10px 20px;
border-radius:10px;
display:flex;
align-items: flex-start;
text-align:left;
max-width:70%;
flex-flow:column;
`
const ImageBackground = styled.div`
height:100%;
min-height:45px;
padding:10px 0px;
border-radius:10px;
display:flex;
align-items: center;
text-align:left;
max-width:70%;
`
const Column = styled.span`
width:100%;
height:100%;
margin:0 auto;
display:flex;
flex-direction: column;
justify-content: flex-end;
`
const Phrase = styled.div<any>`
width:90%;
text-align:right;
min-height:45px;
margin :0 auto 15px;
display:flex;
justify-content:${(props) => props.side};
align-items: center;
`
const Form = styled.div`
display:flex;
width:90%;
height:70%;
justify-content: center;
justify-content: space-between;
`
const SubmitButton = styled.button`
margin-left:15px;
width:80px;
border-radius:10px;
border:none;
color:#666666;
padding:2px 0 0 2px;
`
const TextInputArea = styled.input`
width: calc(100% - 80px);
border-radius:10px;
border:1px solid #e0e0e0;
background-color:#fbf9ff;
text-indent: 1em;
&:focus {
outline: none;
border: 2px solid#666666;
}
`
const Display = styled.div`
width:70%;
max-width:600px;
min-height:500px;
margin: 0 auto ;
background-color:${(props) => props.color};
border:solid 1px #e0e0e0;
border-radius:10px;
display:flex;
justify-content: center;
align-items: flex-end;
padding-top:15px;
`
const InputFormArea = styled.div`
width:70%;
max-width:600px;
height:50px;
margin:10px auto 0;
border-radius:10px;
background-color:#e0e0e0;
border:solid 1px #e0e0e0;
display:flex;
align-items: center;
justify-content:center;
`
結果
補足
この機能に認証機能をつければユーザー間でチャットができるので、ぜひ実装してみたい。
Discussion