🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

3432閲覧

[React]配列の要素が空のとき、要素をpushした後先頭のオブジェクトを削除したい

moyong

総合スコア19

Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/02/23 07:58

編集2021/02/23 08:03

前提・実現したいこと

React Hooks, Tyoescriptで配列の要素が空であれば上書きし、既にあれば末尾に追加する処理を書きたい。
配列やオブジェクトの扱い方について理解が浅く、ご教授いただきたいです。

問題

stateで持っているオブジェクト(トレーニング名、重さ、回数)

const [ trainingRecord, setTrainingRecord ] = useState({ trainingName: "", trainingWeight: "none", trainingReps: "0", }) ```を次の配列にpushして、**shift()で先頭を削除する処理を**書いてみたのですが ```ここに言語を入力 let trainingRecords = [{}];
const saveTrainingRecord = () => { if (!trainingRecords.length) { trainingRecords.push(trainingRecords); trainingRecords.shift(); } else { trainingRecords.push(trainingRecord); } console.log(trainingRecords); };

イメージ説明
上の画像のコンソールにあるように、0番目の{}という不要なものが削除できていません。
(次々とオブジェクトを追加していくことはできています。)
そもそものロジックについても、こうした方がいいというのがあれば教えていただきたいです。

備考

trainingRecordsという配列(連想配列というのが正確なんでしょうか?)は、
将来的にfirebaseに保存して、Twitterのタイムラインのように一つずつ取り出して一覧表示させる予定です。

trainigRecords = [ { trainingName: "腕立て伏せ", trainingWeight: "none", trainingReps: "20", }, { trainingName: "スクワット", trainingWeight: "100kg", trainingReps: "5", }, ]

みたいな構造のtrainingRecordsが、Twitterでいう一つのつぶやきみたいになるようなイメージです。
わざわざこういうデータ構造にしているのは、1日のトレーニングの中で複数のトレーニングを色々な重量で複数セットやる場合を考慮しているためです。

念の為、このコンポーネントを全て貼り付けておきます。

import React, { useState } from "react"; import styles from "./PostInput.module.scss"; import { storage, db, auth } from "../firebase"; import firebase from "firebase/app"; import { useSelector } from "react-redux"; import { selectUser } from "../features/userSlice"; import AddPhotoAlternateIcon from '@material-ui/icons/AddPhotoAlternate'; import AddCircleIcon from '@material-ui/icons/AddCircle'; import { Avatar, Button, IconButton, TextField, MenuItem, createStyles, makeStyles, Theme, } from "@material-ui/core"; interface trainingRecords { trainingName: string; trainingWeight: string; trainingReps: string; } let trainingRecords = [{}]; const weightList = [ {value: 'none', label: 'none'}, {value: '10', label: '10lbs | 4.5kg'}, {value: '20', label: '20lbs | 9kg'}, {value: '30', label: '30lbs | 14kg'}, {value: '40', label: '40lbs | 18kg'}, {value: '50', label: '50lbs | 23kg'}, {value: '60', label: '60lbs | 27kg'}, {value: '70', label: '70lbs | 32kg'}, {value: '80', label: '80lbs | 36kg'}, {value: '90', label: '90lbs | 41kg'}, {value: '100', label: '100lbs | 45kg'}, {value: '110', label: '110lbs | 50kg'}, {value: '120', label: '120lbs | 54kg'}, {value: '130', label: '130lbs | 59kg'}, {value: '140', label: '140lbs | 64kg'}, {value: '150', label: '150lbs | 68kg'}, {value: '160', label: '160lbs | 73kg'}, {value: '170', label: '170lbs | 77kg'}, {value: '180', label: '180lbs | 82kg'}, {value: '190', label: '190lbs | 86kg'}, {value: '200', label: '200lbs | 91kg'}, ]; // const useStyles = makeStyles((theme: Theme) => // createStyles({ // trainingNameInput: { // '& > *': { // margin: theme.spacing(1), // width: '25ch', // }, // }, // trainingWeightSelect: { // '& .MuiTextField-root': { // margin: theme.spacing(1), // width: '25ch', // }, // }, // }), // ); const TweetInput: React.FC = () => { const user = useSelector(selectUser); // const classes = useStyles(); const [ image, setImage] = useState<File | null>(null); const [ trainingRecord, setTrainingRecord ] = useState({ trainingName: "", trainingWeight: "none", trainingReps: "0", }) const onChangeImageHandler = (e: React.ChangeEvent<HTMLInputElement>) => { if (e.target.files![0]) { setImage(e.target.files![0]); e.target.value = ""; } }; const saveTrainingRecord = () => { if (!trainingRecords.length) { trainingRecords.push(trainingRecords); trainingRecords.shift(); } else { trainingRecords.push(trainingRecord); } setTrainingRecord({ trainingName: "", trainingWeight: "none", trainingReps: "0", }); console.log(trainingRecords); }; // const sendTrainingPost = (e: React.FormEvent<HTMLFormElement>) => { // e.preventDefault(); // if (image) { // const S = // "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // const N = 16; // const randomChar = Array.from(crypto.getRandomValues(new Uint32Array(N))) // .map((n) => S[n % S.length]) // .join(""); // const fileName = randomChar + "_" + image.name; // const uploadImg = storage.ref(`images/${fileName}`).put(image); // uploadImg.on( // firebase.storage.TaskEvent.STATE_CHANGED, // () => {}, // (err) => { // alert(err.message); // }, // async () => { // await storage // .ref("images") // .child(fileName) // .getDownloadURL() // .then(async (url) => { // await db.collection('training_posts').add({ // avatar: user.photoUrl, // image: url, // training_name: trainingName, // training_weight: trainingWeight, // training_reps: trainingReps, // timestamp: firebase.firestore.FieldValue.serverTimestamp(), // username: user.displayName, // uid: user.uid // }); // }); // } // ); // } else { // db.collection('training_posts').add({ // avatar: user.photoUrl, // image: "", // training_name: trainingName, // training_weight: trainingWeight, // training_reps: trainingReps, // timestamp: firebase.firestore.FieldValue.serverTimestamp(), // username: user.displayName, // uid: user.uid // }); // } // setImage(null); // setTrainingName(""); // setTrainingWeight("none"); // setTrainingReps("0"); // }; return ( <> <form> <div className={styles.tweet_form}> <Avatar className={styles.tweet_avatar} src={user.photoUrl} /> <input className={styles.trainingName} placeholder="What kind of training?" type="text" value={trainingRecord.trainingName} onChange={(e) => setTrainingRecord({...trainingRecord, trainingName: e.target.value})} /> <select className={styles.trainingWeight} value={trainingRecord.trainingWeight} onChange={(e) => setTrainingRecord({...trainingRecord, trainingWeight: e.target.value})} > {weightList.map((weight) => ( <option key={weight.value} value={weight.value}> {weight.label} </option> ))} </select> <input min="0" className={styles.trainingReps} placeholder="reps" type="number" value={trainingRecord.trainingReps} onChange={(e) => setTrainingRecord({...trainingRecord, trainingReps: e.target.value})} /> <AddCircleIcon className={styles.saveTrainingRecord} onClick={() => saveTrainingRecord()} /> <IconButton> <label> <AddPhotoAlternateIcon className={ image ? styles.tweet_addIconLoaded : styles.tweet_addIcon } /> <input className={styles.tweet_hiddenIcon} type="file" onChange={onChangeImageHandler} /> </label> </IconButton> </div> {/* <Button type="submit" disabled={!trainingName} className={ trainingName ? styles.tweet_sendBtn : styles.tweet_sendDisableBtn } > Tweet </Button> */} </form> </> ); } export default TweetInput;

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2021/02/23 09:47

let trainingRecords = [{}]; としているので、最初から空オブジェクトが入ってますね。 また、trainingRecords の内容を書き換える場合、React 的には破壊的変更 (push や shift など) はせずに、useState や Redux を使うべきかと…。
moyong

2021/02/23 10:16

ありがとうございます。 恥ずかしながらuseStateを使うという考えがありませんでした。 試してみます。
moyong

2021/02/23 11:04

配列をもつようなstateをつくり、そこに名前、重さ、回数をもつオブジェクトを追加するような処理を書いてみましたが、型エラー?のようなものがでてしまいました。 質問を作り直しましたので、アドバイスいただけると幸いですm(__)m https://teratail.com/questions/324279
guest

回答1

0

ベストアンサー

新しい質問には回答がついてるので、こちらに書きます。まず interface trainingRecords ですが、

  • 型の名前の先頭の文字は大文字にしましょう。
  • この型は一つのレコードを表すので、単数形にしましょう。

ということで、型名を TrainingRecord にしましょう。これによって、変数名 trainingRecord, trainingRecords との区別が付きやすくなります。

TypeScript

1interface TrainingRecord { 2 trainingName: string; 3 trainingWeight: string; 4 trainingReps: string; 5}

このようにキー (trainingName, trainingWeight など) とそれに対する値からなるものを「連想配列」と呼ぶ言語もありますが、JavaScript では単にオブジェクトと呼びます。ので、trainingRecord の型は TrainingRecord 型のオブジェクト、trainingRecords は TrainingRecord 型のオブジェクトの配列になります。


useState の使い方ですが、初期値が [] の場合は型推論が働かないので、useState<型名>(初期値) とすると良いでしょう。なお、TypeScript では TrainingRecord 型のオブジェクトの配列を表す型を TrainingRecord[] と書きます。

TypeScript

1const [trainingRecords, setTrainingRecords] = useState<TrainingRecord[]>([]);

次に saveTrainingRecord ですが、trainingRecords は配列なので、要素が一つの場合でも [ 〜 ] で囲んで配列にする必要があります。
また、配列に要素を追加する場合も、やはり [ 〜 ] で囲む必要があります。特に、配列に要素を追加する書き方 [...配列, 要素] はよく出てきますね。(なお、{ 〜 } で囲むとオブジェクトになってしまうのでご注意ください。)

TypeScript

1const saveTrainingRecord = () => { 2 if (!trainingRecords.length) { 3 setTrainingRecords([trainingRecord]); 4 } else { 5 const newRecord = [...trainingRecords, trainingRecord]; 6 setTrainingRecords(newRecord); 7 } 8};

ちなみに、[...trainingRecords, trainingRecord] は trainingRecords が空の場合には trainingRecord ひとつだけの配列になります。ので、次のように書いても同じ動作になります。

TypeScript

1const saveTrainingRecord = () => { 2 setTrainingRecords([...trainingRecords, trainingRecord]); 3};

ところで、TweetInput コンポーネント (TrainingInput の方がいいような気がしないでもない…) では trainingRecords は表示してないようですが、たぶん他のコンポーネントで表示するのでしょうね。その場合、trainingRecords は親コンポーネントで useState してプロパティで渡すか、Redux などを使う方がいい気がします。もっとも、Firebase を使う場合はまた違う (そもそも自分で trainingRecords に追加する必要がない?) かも…。

投稿2021/02/23 13:17

hoshi-takanori

総合スコア7899

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

moyong

2021/02/23 13:59 編集

ありがとうございます! 今朝からずっとこの問題に取り組んでいまして、ご親切に教えていただいて涙がでそうなほど嬉しいです さっそく試してみます。
moyong

2021/02/24 03:44

おかげさまで、うまくいきました! 何がオブジェクトで何が配列なのかというところから勉強になりました! 結果的には、useState<TrainingRecord[]>([]);で型宣言をセットしたことで実現しました TweetInputという名前については、udemy教材で作ったアプリを改造している途中でして、改名するのを忘れていました笑ありがとうございます。 https://gyazo.com/abe888ba6d3cb6e62814a64a8349f57b
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問