3

I am trying to implement a simple code in react.js which user can input the data using form and unless the page is refreshed the data will show in the table.

I have implemented a code in Code Sandbox and it worked as expected. Then I copied that code and used it in the IDE. Now the same code is showing error as ==> "TypeError: can't define property "email": Object is not extensible". (I am using intellij IDE ultimate edition)

This is the link for sandbox => Link for the code in sandbox

The code it self if the sand box is not working ==>

import React, {Component} from "react";

class CustomDetails extends Component {

constructor(props) {
    super(props);
    this.state = {
        items: [{email: '', country: '', new_case: '', total_case: '', total_death: ''}],
        message: ''
    }
    this.newData = React.createRef();
    this.addForm = React.createRef();

}

addData(e) {
    e.preventDefault();
    const {items} = this.state;
    
    const newData = () => ({
        email:this.addForm.email.value,
        country:this.addForm.country.value,
        new_case:this.addForm.new_case.value,
        total_case:this.addForm.total_case.value,
        total_death:this.addForm.total_death.value
    })
    const isOnTheList = items.includes(newData.country);

    if (isOnTheList) {
        this.setState(({
            message: 'This country details are already added.'
        }))
    } else {
        this.setState({
            items: [...this.state.items, newData()],
        })
    }
    this.addForm.reset();
}




render() {
    const {items, message}=this.state;
    return (
        <div>
            <div>
            
                <div>
                    <form ref={input => this.addForm = input} onSubmit={(e) => {
                        this.addData(e)
                    }}>
         
                            <label>User Email :</label><br/>
                            <input required ref={input => this.newData["email"] = input} name="email" value={this.state.items.email}
                                   type="email"
                                   placeholder="Enter email"/><br></br>

                           
                                <label>Country :</label><br/>
                                <input required ref={input => this.newData["country"] = input} name="country" value={this.state.items.country}
                                       type="text"
                                       placeholder="Enter country"/><br></br>
                           

                            
                                <label>New Cases :</label><br/>
                                <input required ref={input => this.newData["new_case"] = input}
                                name="new_case"
                                       value={this.state.items.new_case} type="text"
                                       placeholder="Enter no of new cases"/><br></br>
                           

                           
                                <label>Total cases :</label><br/>
                                <input required ref={input => this.newData["total_case"] = input}
                                name="total_case"
                                       value={this.state.items.total_case} type="text"
                                       placeholder="Enter no of total cases"/><br></br>
                           

                           
                                <label>Total death :</label><br/>
                                <input required ref={input => this.newData["total_death"] = input}
                                name="total_death"
                                       value={this.state.items.total_death} type="text"
                                       placeholder="Enter no of total deaths"/><br></br>
                            

                            <button variant="primary" type="submit">
                                Submit</button><br></br>
                       
                    </form>

                </div>
                <div>
                    
                   
                    {
                        message !== '' && <p>{this.setState.message}</p>
                    }
                    <table striped="true" bordered="true" hover="true">
                        <thead>
                        <tr>
                            <th>Email</th>
                            <th>Country</th>
                            <th>New cases</th>
                            <th>Total cases</th>
                            <th>Total deaths</th>
                        </tr>
                        </thead>
                        <tbody>
                        {items.map((item,index) => {
                            return (
                                <tr key={index}>

                                    <td>{item.email}</td>
                                    <td>{item.country}</td>
                                    <td>{item.new_case}</td>
                                    <td>{item.total_case}</td>
                                    <td>{item.total_death}</td>
                                </tr>
                            )
                        })}
                        </tbody>
                    </table>
                </div>
              
            </div>
        </div>
    )
}}export default CustomDetails;

The output in sandbox --> Sand box output

The error while running it in IDE --> ide error

Lakshya Thakur
  • 8,030
  • 1
  • 12
  • 39
Deepika
  • 737
  • 7
  • 23
  • `this.state.items` is array not object use: `this.state.items[0].email` or change `this.state.items` to object – MohammadReza Abbasi Mar 18 '21 at 05:42
  • Tried that.. But getting the same error. But the above code is working fine in sandbox – Deepika Mar 18 '21 at 05:44
  • 1
    change in `constructor`:`this.newData = {email: '', country: '', new_case: '', total_case: '', total_death: ''};` and `this.addForm = {email: '', country: '', new_case: '', total_case: '', total_death: ''};` – MohammadReza Abbasi Mar 18 '21 at 05:54

2 Answers2

1

The culprit is the below line (and if you remove it, it will be the next input line) :-

<input required ref={input => this.newData["email"] = input} name="email" value={this.state.items.email} type="email" placeholder="Enter email"/><br></br>

In your ref's callback, you are assigning a ref object with a field name of email to input.

If you go ahead and console.log(Object.isExtensible(this.newData) in your render method, you will see it will show false. So you're trying to add an email property to an object which doesn't allow it. Your codesandbox is silently failing but your IDE is running in strict mode.

You need to initialize this.newData to an object by making this.newData equivalent to {email: '', country: '', new_case: '', total_case: '', total_death: ''}; as per your use-case. So your this.newData will not be a React ref anymore but just an instance variable for your Class.

Here are MDN links that helped :-

Do see @Drew's answer on how to deal with inputs in a better way.

Lakshya Thakur
  • 8,030
  • 1
  • 12
  • 39
1

There really isn't a point to using React refs in this case as you can easily access the field values from the form's onSubmit event object.

  1. Initial state should be an empty array.

    this.state = {
      items: [],
    };
    
  2. The addData callback needs this of the class bound to it.

  3. addData should access the form field values from the onSubmit event object. The newData should be an object of the form field values you want to push into your items state array. Use a functional state update to update from the previous state. When searching the items array for existing entries you need to use an array function that allows examining object properties as Array.prototype.includes really only check reference equality, you want to search for an item that has a matching country property using Array.prototype.some to return a boolean.

    addData = (e) => {
      e.preventDefault();
      const { items } = this.state;
    
      const newData = {
        email: e.target.email.value,
        country: e.target.country.value,
        new_case: e.target.new_case.value,
        total_case: e.target.total_case.value,
        total_death: e.target.total_death.value
      };
    
      const isOnTheList = items.some(item => item.country === newData.country);
    
      if (isOnTheList) {
        this.setState({
          message: "This country details are already added."
        });
      } else {
        this.setState((prevState) => ({
          items: [...prevState.items, newData]
        }));
      }
      e.target.reset();
    };
    
  4. Since you've uncontrolled inputs you should remove the old "legacy" style ref attachments and value prop. Example:

    <input
      required
      name="email"
      type="email"
      placeholder="Enter email"
    />
    
  5. You've a typo when trying to render an error message, {message !== "" && <p>{this.setState.message}</p>}, it should be {message !== "" && <p>{this.state.message}</p>}.

Demo

Edit the-code-is-working-fine-in-codesandbox-but-showing-error-while-doing-in-the-id

Full code:

class CustomDetails extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [],
      message: ""
    };
  }

  addData = (e) => {
    e.preventDefault();
    const { items } = this.state;

    const newData = {
      email: e.target.email.value,
      country: e.target.country.value,
      new_case: e.target.new_case.value,
      total_case: e.target.total_case.value,
      total_death: e.target.total_death.value
    };
    const isOnTheList = items.includes(newData.country);

    if (isOnTheList) {
      this.setState({
        message: "This country details are already added."
      });
    } else {
      this.setState((prevState) => ({
        items: [...prevState.items, newData]
      }));
    }
    e.target.reset();
  };

  render() {
    const { items, message } = this.state;
    return (
      <div>
        <div>
          <div>
            <form onSubmit={this.addData}>
              <label>User Email :</label>
              <br />
              <input
                required
                name="email"
                type="email"
                placeholder="Enter email"
              />
              <br></br>

              <label>Country :</label>
              <br />
              <input
                required
                name="country"
                type="text"
                placeholder="Enter country"
              />
              <br></br>

              <label>New Cases :</label>
              <br />
              <input
                required
                name="new_case"
                type="text"
                placeholder="Enter no of new cases"
              />
              <br></br>

              <label>Total cases :</label>
              <br />
              <input
                required
                name="total_case"
                type="text"
                placeholder="Enter no of total cases"
              />
              <br></br>

              <label>Total death :</label>
              <br />
              <input
                required
                name="total_death"
                type="text"
                placeholder="Enter no of total deaths"
              />
              <br></br>

              <button variant="primary" type="submit">
                Submit
              </button>
              <br></br>
            </form>
          </div>
          <div>
            {message !== "" && <p>{this.state.message}</p>}
            <table striped="true" bordered="true" hover="true">
              <thead>
                <tr>
                  <th>Email</th>
                  <th>Country</th>
                  <th>New cases</th>
                  <th>Total cases</th>
                  <th>Total deaths</th>
                </tr>
              </thead>
              <tbody>
                {items.map((item, index) => {
                  return (
                    <tr key={index}>
                      <td>{item.email}</td>
                      <td>{item.country}</td>
                      <td>{item.new_case}</td>
                      <td>{item.total_case}</td>
                      <td>{item.total_death}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    );
  }
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thankyou so much for that best explanation. In order to add only 1 data to a country name i have added a error message when user input another data for the same country. I order to get that message do in need to replace the key value as item.country ? – Deepika Mar 18 '21 at 06:17
  • 1
    @Deepika Totally correct, the `.includes` array function won't work for object properties, I suggest using the `.some` to check if *some* item has a matching `country` property value. I updated my answer and linked codesandbox. Just noticed you also had a type when trying to render the message, it's `{message !== "" &&

    {this.state.message}

    }`, you had `this.setState.message`.
    – Drew Reese Mar 18 '21 at 06:23
  • Thank-you so much. from this explanation I was able to understand the code and what I have done wrong. – Deepika Mar 18 '21 at 06:41
  • If I want to calculate the total (if 4 uses input data calculate the 4 data of the followings) of new_cases, total_cases and total_deaths , and add them in the table by creating new columns how to do that using functions? @Drew – Deepika Mar 23 '21 at 13:36
  • @Deepika Sounds like you want to compute row totals for new cases, total cases, and total deaths, and add a row summarizing the totals, is this correct? Or you want new columns? Either way, you can reduce the `items` array data into the totals I think you're asking about. If you provide a little clarity and still need help I can help here. – Drew Reese Mar 23 '21 at 15:51
  • Like, The first person added the new_cases as 10. The second person added as 45. The 3rd person added as 24. So in the end or in a new table need to show the total new_cases (10+45+24=79). Like wise need to show the total for total_death and total recover @Drew – Deepika Mar 23 '21 at 16:06
  • 1
    @Deepika https://codesandbox.io/s/the-code-is-working-fine-in-codesandbox-but-showing-error-while-doing-in-the-id-forked-okcgi?file=/src/App.js:1036-1454 – Drew Reese Mar 23 '21 at 16:16
  • I have tried like this. But can't we do this in a function out from the render(). – Deepika Mar 23 '21 at 16:24
  • @Deepika You can factor this logic into a function if you really need to, this is a trivial change. – Drew Reese Mar 23 '21 at 16:27
  • Thankyou so much sir, I'll do that. This is really helpful :) – Deepika Mar 23 '21 at 16:32