-1

New key is assigned like this, but old key need to remove, how can I do in a compact way without interfering with assigning?

{Object.entries(environments).map(([k, v]) => (

<input
  type="text"
  className="input-text border border-radius lightgray-background"
  value={k}
  onChange={(e) => {
    setEnvironments({
      ...environments,
      [e.target.value]: v,
    });
  }}
/>

)}
János
  • 32,867
  • 38
  • 193
  • 353
  • I don't see any reference to the word `key` anywhere in your code (maybe k). If this is the case, you can keep the keys in a state variable, or update the list of inputs when a useEffect() triggers. – Bas van der Linden Nov 28 '22 at 13:35
  • 1
    In the example you've shown (of a [controlled input](https://reactjs.org/docs/forms.html#controlled-components)), the DOM element's `value` will never update (unless there's some other logic you've not shown which updates `k` as a side effect of calling `setEnvironments`). – jsejcksn Nov 28 '22 at 13:36
  • I thought `e.target.value` will have the new value, user set in the textfield – János Nov 28 '22 at 13:41
  • What exactly do you mean by the old key ? – Tarmah Nov 28 '22 at 13:42
  • Also, how is `v` created? How is each input created? You need to provide a [mre]. At any rate, it's likely that you're not defining unique keys for each item, which is (1) currently a bug in your code (see [Lists and keys](https://reactjs.org/docs/lists-and-keys.html#keys)), and (2) likely the solution to whatever problem you're asking about. – jsejcksn Nov 28 '22 at 13:44
  • Given that an object is an unordered collection, this looks like a bad idea; your inputs will jump around; and should you ever enter the same value in two of them, they'll automatically get coalesced into a single input field. Before asking about removing the old key, please describe what your actual goal is. – Bergi Nov 28 '22 at 13:52

1 Answers1

0

Any time you render a list in React, you must provide a stable (deterministic) value for the key attribute of each item. Not doing so is a bug because — otherwise — React has no idea which data belongs with which item in the series.

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.

This behavior is covered in detail in the documentation section titled Lists and Keys.

If you need to manage a collection of items with independently editable keys and values — and especially if it's a possibility for the collection's keys to contain duplicates (as is suggested in your question code) — then the keys in the collection are not necessarily unique identifiers, and are not appropriate for use as React keys in a list of rendered nodes.

You can use a unique string (e.g. a UUIDv4 generated from Crypto.randomUUID()) as the actual, internal and unique key for each of your key-value entries.

In context of the information above, a Map is probably a better data structure for your environment data: not only can you store a key and value as a tuple in each entry — a Map also gives you stronger control over the order of its entries: unlike an object, whose keys are deterministically ordered with some restrictions (see Does JavaScript guarantee object property order?), a Map's entries are always ordered according to insertion.

Below I've created a self-contained example of using a Map as state to store a collection of editable key-value entries, including a component for visualizing the state.

Select "Full page" after running the code snippet to get an expanded viewport for the demo.

* { box-sizing: border-box; } body { font-family: sans-serif; } button, input[type="text"] { font-size: 1rem; padding: 0.5rem; } .entry-list { list-style: none; padding: 0; } .entry-row { display: flex; gap: 0.5rem; } pre.visual { background-color: hsla(0, 0%, 50%, 0.1); font-size: 1rem; padding: 1rem; } pre.visual > code { font-family: monospace; line-height: 1.5; }
<div id="root"></div><script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.20.6/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">

const {useCallback, useState} = React;

function VisualizeState ({serializable}) {
  return (
    <pre className="visual">
      <code>{JSON.stringify(serializable, null, 2)}</code>
    </pre>
  );
}

function TextInput ({placeholder, value, setValue}) {
  return (
    <input
      type="text"
      onChange={ev => setValue(ev.target.value)}
      {...{placeholder, value}}
    />
  );
}

// The default values for a new key-value entry in the environment:
const createDefaultEnvEntry = () => ['', ''];

function App () {
  const [environmentMap, setEnvironmentMap] = useState(new Map());

  const setEnvKey = useCallback(
    (uuid, key) => setEnvironmentMap(m => {
      const map = new Map([...m.entries()]);
      const entry = map.get(uuid) ?? createDefaultEnvEntry();
      entry[0] = key;
      map.set(uuid, entry);
      return map;
    }),
    [setEnvironmentMap],
  );

  const setEnvValue = useCallback(
    (uuid, value) => setEnvironmentMap(m => {
      const map = new Map([...m.entries()]);
      const entry = map.get(uuid) ?? createDefaultEnvEntry();
      entry[1] = value;
      map.set(uuid, entry);
      return map;
    }),
    [setEnvironmentMap],
  );

  const createEnvEntry = useCallback(
    () => setEnvironmentMap(m => new Map([
      ...m.entries(),
      [crypto.randomUUID(), createDefaultEnvEntry()],
    ])),
    [setEnvironmentMap],
  );

  const deleteEnvEntry = useCallback(
    (uuid) => setEnvironmentMap(m => {
      const map = new Map([...m.entries()]);
      map.delete(uuid);
      return map;
    }),
    [setEnvironmentMap],
  );

  return (
    <div>
      <ul className="entry-list">
        {
          [...environmentMap.entries()].map(([uuid, [key, value]]) => (
            // Note the use of the "key" attribute with the mapped node child:
            <li className="entry-row" key={uuid}>
              <TextInput
                placeholder="key"
                value={key}
                setValue={key => setEnvKey(uuid, key)}
              />
              <TextInput
                placeholder="value"
                value={value}
                setValue={value => setEnvValue(uuid, value)}
              />
              <button onClick={() => deleteEnvEntry(uuid)}>Delete entry</button>
            </li>
          ))
        }
      </ul>
      <button onClick={() => createEnvEntry()}>Create new entry</button>
      <div>
        <p>Visulization of state:</p>
        <VisualizeState
          serializable={Object.fromEntries([...environmentMap.entries()])}
        />
      </div>
    </div>
  );
}

const reactRoot = ReactDOM.createRoot(document.getElementById('root'));

reactRoot.render(<App />);

</script>
jsejcksn
  • 27,667
  • 4
  • 38
  • 62
  • "*if it's a possibility for the collection's keys to contain duplicates (as is suggested in your question code)*" - I don't see that, an object like `environments` cannot have duplicate property names. – Bergi Nov 28 '22 at 16:48
  • "*a `Map` also gives you complete control over the order of its entries*" - no it doesn't. It just has different deterministic rules for iterating its entries, but - just like an object - it still represents an unordered collection. – Bergi Nov 28 '22 at 16:48
  • [^](https://stackoverflow.com/questions/74601362/how-to-remove-key-from-object-before-a-new-is-set/74603559?noredirect=1#comment131686105_74603559) @Bergi "_an object like `environments` cannot have duplicate property names_" Of course that's true. However, the OP hasn't provided a [mre], so we can't say for sure what the intent actually is... – jsejcksn Nov 28 '22 at 17:07
  • [^](https://stackoverflow.com/questions/74601362/how-to-remove-key-from-object-before-a-new-is-set/74603559?noredirect=1#comment131686124_74603559) @Bergi "_it still represents an unordered collection_": This is not correct: the order of a map's entries is always that of insertion — it is guaranteed. See [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [the spec](https://tc39.es/ecma262/multipage/keyed-collections.html#sec-map-objects) for details. – jsejcksn Nov 28 '22 at 17:11
  • Yes, same as for object properties (mod integer-like properties). But a consistent guaranteed iteration order doesn't make the collection ordered, you cannot reorder or sort the entries, that is not what it is meant to represent. – Bergi Nov 28 '22 at 17:15
  • [^](https://stackoverflow.com/questions/74601362/how-to-remove-key-from-object-before-a-new-is-set/74603559?noredirect=1#comment131686698_74603559) I think it was just a semantic misunderstanding: I didn't claim sortability — if that's what you interpreted, how can I revise the phrasing to more clearly represent [this comment](https://stackoverflow.com/questions/74601362/how-to-remove-key-from-object-before-a-new-is-set/74603559?noredirect=1#comment131686619_74603559)? – jsejcksn Nov 28 '22 at 17:17
  • I'd remove the part about "*complete control over the order of its entries*". But also I'm not certain why you brought this up at all, I don't see how your answer works different than if done with an object. Given the OPs question is unclear without knowing their actual intentions, I wouldn't even claim that "*a `Map` is probably a better data structure for your environment data*". It might be, but I couldn't tell. – Bergi Nov 28 '22 at 17:24
  • [^](https://stackoverflow.com/questions/74601362/how-to-remove-key-from-object-before-a-new-is-set/74603559?noredirect=1#comment131686877_74603559) @Bergi Thanks, I'll revise that phrase. "_I don't see how your answer works different than if done with an object._" I chose UUIDs, but didn't assume that the OP would. Maps allow for non-string keys as well (another bonus) — and if the OP chose some number-like strings mixed with other strings, then the ES2015 object key sorting logic might cause problems — whereas a Map wouldn't be affected by that. – jsejcksn Nov 28 '22 at 17:28
  • Yeah, but when using uuids for the keys, an object would've worked just as well. – Bergi Nov 28 '22 at 17:30
  • [^](https://stackoverflow.com/questions/74601362/how-to-remove-key-from-object-before-a-new-is-set/74603559?noredirect=1#comment131686997_74603559) @Bergi I generally try to keep the [robustness principle](https://en.wikipedia.org/wiki/Robustness_principle) in mind when writing code examples — especially on SO where things are often not communicated with maximal clarity. – jsejcksn Nov 28 '22 at 18:24