简体   繁体   中英

Why the map function doesn't work in my React app?

I'm learning React and I want to build an application that looks like this:

在此处输入图像描述

Basically the user can put his name and comment and post it. The posted comments will be displayed in the bottom. I'm using Tailwind CSS to style the app. Below is my code.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';


class MyName extends React.Component {
    render() {
        return (<div className="rounded border border-indigo-400 m-8 p-2">
            <div className="flex flex-col items-stretch space-y-2">
                <input type="text" placeholder="Who you are?" className="border border-gray-500" value={this.state.currentUser} onChange={(e) => {
                    this.setState({currentUser: e.target.value});
                }} />
                <textarea placeholder="Say something" className="h-32 border border-gray-500" value={this.state.currentData} onChange={(e) => {
                    this.setState({currentData: e.target.value});
                }}></textarea>
                <button className="bg-green-400 text-green-100 rounded py-1 hover:bg-green-600" onClick={() => {
                    if (this.state.currentUser && this.state.currentData) {
                        this.setState({
                            comments: this.state.comments.push({
                                user: this.state.currentUser,
                                data: this.state.currentData
                            })
                        });
                        this.state.currentUser = "";
                        this.state.currentData = "";
                    }
                }}>Post comment
                </button>
            </div>
            <div className="mt-8">
                {this.comments_section()}
            </div>
        </div>);
    }

    comments_section() {
        if (this.state.comments.length == 0) {
            return <span>No Comments.</span>
        }
        else {
            console.log(Array.isArray(this.state.comments));
            console.log(this.state.comments);
            return (<ul className="space-y-4">{this.state.comments.map((item) =>
                <li className="flex items-start space-x-2" key={item.user + item.data}>
                    <div className="flex flex-col items-start space-y-1">
                        <img className="rounded-full" src="http://placekitten.com/50/50" />
                        <span className="text-xs text-gray-500">{item.user}</span>
                    </div>
                    <span>{item.data}</span>
                </li>)}
            </ul>)
        }
    }

    constructor() {
        super();

        this.state = {
            currentUser: "",
            currentData: "",
            comments: [{
                user: "User One",
                data: "Hi, hello world!"
            }, {
                user: "User Two",
                data: "Hi, hello world!"
            }]
        }
    }
}

ReactDOM.render(
    <MyName />,
    document.getElementById('root')
);

reportWebVitals();

The problem is that when I type something in the inputs and click the button, it errors out and states this.state.comments.map is not a function. I added some console.log statement to debug it and found that at first this.state.comments is an array. But once I clicked the button, it is not. How to fix this issue? Thanks.

this.setState({
   comments: this.state.comments.push({
     user: this.state.currentUser,
     data: this.state.currentData
   })
});

array.push returns a number, which is the length of the array. So you're setting state.comments to a number, and numbers don't have a map method.

You shouldn't use push for this, since it mutates the array, and react requires your state to be immutable. Additionally, you have a couple lines where you're manually setting this.state.currentUser and this.state.currentData. Changes to the state should always be through set state, not manual modification of this.state.

const newComments = [...this.state.comments, {
  user: this.state.currentUser,
  data: this.state.currentData
}]
this.setState({ 
  comments: newComments,
  currentUser: "",
  currentData: ""
});

An additional improvement: since you're basing your new state on your old state, you should use the function version of setState. React will pass you the most recent version of the state, which eliminates the possibility that you're using a stale version of the state (your code probably can't run into this bug, but it can happen in more complicated cases, so it's good to get in the habit)

this.setState(prev => {
  const newComments = [...prev.comments, {
    user: prev.currentUser,
    data: prev.currentData
  }]
  return { 
    comments: newComments
    currentUser: "",
    currentData: ""
  };
});

array.push returns a number ie length of the array so you are basically setting comments to its length.

if (this.state.currentUser && this.state.currentData) {
  let { comments, currentUser, currentData } = this.state;
    comments.push({
     user: currentUser,
     data: currentData
    })
 this.setState({comments, currentUser : "", currentData: "" })
}

I am not a react expert but I think you need to return your jsx from the map like this:

<ul className="space-y-4">
{this.state.comments.map((item) => return (
                <li className="flex items-start space-x-2" key={item.user + item.data}>
                    <div className="flex flex-col items-start space-y-1">
                        <img className="rounded-full" src="http://placekitten.com/50/50" />
                        <span className="text-xs text-gray-500">{item.user}</span>
                    </div>
                    <span>{item.data}</span>
                </li>))}
            </ul>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM