0

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

Nesh
  • 2,389
  • 7
  • 33
  • 54

4 Answers4

4

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>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • There is one problem if u try to remove the number from the field it converts into 0 and then u wont be able to delete it until unless u write ex- 01, 0345, 0678, and so on ..and then remove zero from the start – Nesh Apr 02 '22 at 21:38
  • I'm not seeing any problems resulting from that. Just like any text field, you can highlight the text you want to replace and then type to replace, or use control-A and then type or paste. If you really don't like it, you can parse the input in `getList` and make the state a string instead – CertainPerformance Apr 02 '22 at 21:41
1

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;

innocent
  • 864
  • 6
  • 17
0

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)

Avi
  • 1,049
  • 1
  • 5
  • 16
0

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] : [];
swapnesh
  • 26,318
  • 22
  • 94
  • 126