Search code examples
javascriptreactjsreact-state

Array value appended instead of new value list display in React


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


Solution

  • 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>