Search code examples
javascriptreactjsreact-functional-component

react functional component: After first value not able to continue type the value in input element


I'm trying to update value in react functional compoenent through input element but after the first value I'm unable to type

My Code:

import React from "react";
import "./App.css";

const { useState } = React;
function App() {
    const [items, setItems] = useState([
        { value: "" },
        { value: "" },
        { value: "" },
    ]);
    const [count, setCount] = useState(0);
    const Item = React.memo(({ id, value, onChange }) => {
        return (
            <div className="item">Item
                <input
                    onChange={(e) => onChange(id, e.target.value)}
                    value={value}
                />
            </div>
        );
    });
    return (
        <div className="app">
            <h1>Parent</h1>
            <p style={{marginTop: '20px'}}>Holds state: {count}, Does't passes to it items</p>
            <p style={{marginTop: '20px'}}>{JSON.stringify(items)}</p>
            <button onClick={() => setCount((prev) => prev + 1)} style={{marginTop: '20px'}}>
                Update Parent
            </button>
            <ul className="items" style={{marginTop: '20px'}}>
                {items.map((item, index) => {
                    return (
                        <Item
                            key={index}
                            id={index}
                            value={item.value}
                            onChange={(id, value) =>
                                setItems(
                                    items.map((item, index) => {
                                        return index !== id
                                            ? item
                                            : { value: value };
                                    })
                                )
                            }
                        />
                    );
                })}
            </ul>
        </div>
    );
}

export default App;

enter image description here


Solution

  • You should move the Item declaration outside the App component. Having a component declaration inside another one is almost always a bad idea. Explanation below.

    import React, { useState } from "react";
    
    const Item = React.memo(({ id, value, onChange }) => {
      return (
        <div className="item">
          Item
          <input onChange={(e) => onChange(id, e.target.value)} value={value} />
        </div>
      );
    });
    
    function App() {
      const [items, setItems] = useState([
        { value: "" },
        { value: "" },
        { value: "" }
      ]);
      const [count, setCount] = useState(0);
    
      return (
        <div className="app">
          <h1>Parent</h1>
          <p style={{ marginTop: "20px" }}>
            Holds state: {count}, Does't passes to it items
          </p>
          <p style={{ marginTop: "20px" }}>{JSON.stringify(items)}</p>
          <button
            onClick={() => setCount((prev) => prev + 1)}
            style={{ marginTop: "20px" }}
          >
            Update Parent
          </button>
          <ul className="items" style={{ marginTop: "20px" }}>
            {items.map((item, index) => {
              return (
                <Item
                  key={index}
                  id={index}
                  value={item.value}
                  onChange={(id, value) =>
                    setItems(
                      items.map((item, index) => {
                        return index !== id ? item : { value: value };
                      })
                    )
                  }
                />
              );
            })}
          </ul>
        </div>
      );
    }
    
    export default App;
    

    When a component definition is inside another component, React will re-declare the inner component every time the parent re-renders. This means, that any state held by the inner component will be lost. In your case, since every time there is an entirely new component, the input was not the same input as in the previous render. This means that the input that was in focus in the previous render is not present anymore, and the new input is not focused anymore.

    You should also probably change

    setItems(
        items.map((item, index) => {
            return index !== id ? item : { value: value };
        })
    )
    

    to

        prev.map((item, index) => {
            return index !== id ? item : { value: value };
        })
    )
    

    It's a good idea to use the function notation for set state when the new state depends on the old state value.