Home > database >  Array value appended instead of new value list display in React
Array value appended instead of new value list display in React

Time:04-03

Scenario -

Default Input number in text field - 1

Listed Items -

  • 1
  • 6
  • 11

Now as soon as I remove 1 from the text field -

List Items -

  • NaN
  • NaN
  • NaN

Now enter 4 in the input field.

List Items -

  • NaN
  • NaN
  • 4
  • 9
  • 14

Expected Behavior -

List Item should display only - 4, 9 and 14 instead of NaN, NaN, 4, 9, 14.

Let me know what I am doing wrong here.

Code -

import React, { useState } from "react";
import List from "./List";

const ReRendering = () => {
  const [number, setNumber] = useState(1);

  const getList = () => [number, number   5, number   10];

  return (
    <div>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(parseInt(e.target.value, 10))}
      />
      <div>
        <List getList={getList} />
      </div>
    </div>
  );
};

export default ReRendering;

List.js

import { useEffect, useState } from "react";

const List = ({ getList }) => {
  const [item, setItem] = useState([]);

  useEffect(() => {
    setItem(getList());
  }, [getList]);
  return (
    <div className="list">
      {item.map((x) => (
        <div key={x}>{x}</div>
      ))}
    </div>
  );
};

export default List;

Working Example - https://codesandbox.io/s/gallant-gagarin-l18ni1?file=/src/ReRendering.js:0-463

CodePudding user response:

Doing setNumber(parseInt(e.target.value, 10)) will set the number state to NaN if the input field is empty, because the empty string can't be parseIntd.

console.log(parseInt(''));

And NaN causes problems because, as the React warning says:

Warning: Encountered two children with the same key, NaN. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.

Duplicate NaNs cause duplicate keys.

Instead, use valueAsNumber, and alternate with 0 if the value doesn't exist.

const { useState, useEffect } = React;
const ReRendering = () => {
  const [number, setNumber] = useState(1);

  const getList = () => [number, number   5, number   10];

  return (
    <div>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.valueAsNumber || 0)}
      />
      <div>
        <List getList={getList} />
      </div>
    </div>
  );
};

const List = ({ getList }) => {
  const [item, setItem] = useState([]);
  useEffect(() => {
    setItem(getList());
  }, [getList]);
  return (
    <div className="list">
      {item.map((x) => (
        <div key={x}>{x}</div>
      ))}
    </div>
  );
};


ReactDOM.render(<ReRendering />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

A better way to structure this would be to call getList immediately in the child, instead of using a separate state and effect hook inside it.

const { useState, useEffect } = React;
const ReRendering = () => {
  const [number, setNumber] = useState(1);

  const getList = () => [number, number   5, number   10];

  return (
    <div>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.valueAsNumber || 0)}
      />
      <div>
        <List getList={getList} />
      </div>
    </div>
  );
};

const List = ({ getList }) => (
  <div className="list">
    {getList().map((x) => (
      <div key={x}>{x}</div>
    ))}
  </div>
);


ReactDOM.render(<ReRendering />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

CodePudding user response:

Make the following changes and it should result in exactly what you are looking for.

Its basically taking in the case when the input is empty and it tells it to display none when no input is provided.

List.js


import { useEffect, useState } from "react";

const List = ({ getList }) => {
  const [item, setItem] = useState([]);

  useEffect(() => {
    setItem(getList());
  }, [getList]);
  return (
    <div className="list">
      {item ? item.map((x) => (               // changes
        <div key={x}>{x}</div>
      )):""}
    </div>
  );
};

export default List;


ReRendering.js

import React, { useState } from "react";
import List from "./List";

const ReRendering = () => {
  const [number, setNumber] = useState(1);

  const getList = () => {
    if(number === ""){                     // changes
      return;
    }
    return [number, number   5, number   10];
  };

  return (
    <div>
      <input
        type="number"
        value={number}
        onChange={(e) => {
          if (e.target.value !== "") {               //changes
            setNumber(parseInt(e.target.value, 10));
          } else {
            setNumber(""); 
          }
        }}
      />
      <div>
        <List getList={getList} />
      </div>
    </div>
  );
};

export default ReRendering;

CodePudding user response:

I think it happens because an empty input's value is an empty string which parseInt doesn't know what to do with, therefore returns NaN. I think you need to create a function that will handle that and set state to 0 if the input's value is an empty string. Something like:

const handleChange = (e) => {
  const value = e.target.value;
  setNumber(value === '' ? 0 : Number(value));
}

Also I thought I should let you know that you don't need your useState and useEffect in the <List /> component. Your list can will update when the prop change anyway, so you can do the mapping like this: getList().map(...rset of the code)

CodePudding user response:

I believe @CertainPerformance already answered your -

Let me know what I am doing wrong here.

Looking at your comments specifically about the 0 zero issue in the input field, I think u can then modify your getList() method with a number check and passing blank array if the number is blank.

Change in your getList() method -

const getList = () => number ? [number, number   5, number   10] : [];
  • Related