Search code examples
reactjsrecoiljs

How can I set a nested recoil state?


I have a nested plan list. And I want to add plans and display it using recoil. But I don't need to add the name attribute but only plans by inputs.

import React, { useState } from "react";
import "./styles.css";
import { RecoilRoot, atom, useRecoilState } from "recoil";

const todoListState = atom({
  key: "TodoList",
  default: {
    name: "Lee",
    plans: [
      {
        plan: "",
        price: ""
      }
    ]
  }
});

export function TodoApp() {
  const [list, setList] = useRecoilState(todoListState);
  const [input, setInput] = useState({
    plan: "",
    price: ""
  });

  const handleChange = (e) => {
    setInput({
      ...input,
      [e.target.name]: e.target.value
    });
  };

  const handleClick = async (e) => {
    e.preventDefault();
    setList({
      plan: input.plan,
      price: parseInt(input.price)
    });
    setInput({
      plan: "",
      price: ""
    });
  };

  return (
    <>
      <div>Plans</div>
      {list.plans.map((x, i) => {
        return (
          <div key={i}>
            <div>{x.plan}</div>
            <div>{x.price}</div>
          </div>
        );
      })}
      <div>
        <label htmlFor="plan">plan</label>
        <input
          type="text"
          value={input.plan}
          onChange={handleChange}
          name="plan"
        />
      </div>
      <div>
        <label htmlFor="price">price</label>
        <input
          type="text"
          value={input.price}
          onChange={handleChange}
          name="price"
        />
      </div>
      <button onClick={handleClick}>Add Item</button>
    </>
  );
}

export default function App() {
  return (
    <RecoilRoot>
      <div className="App">
        <TodoApp />
      </div>
    </RecoilRoot>
  );
}

To implement this, what do I need to use or fix? The code is below:

https://codesandbox.io/embed/nested-arrays-forked-49idv?fontsize=14&hidenavigation=1&theme=dark


Solution

  • You just need to refactor your click handler so that when you update your recoil atom, you spread in the existing parts of the object, effectively appending to the array:

    const handleClick = async (e) => {
      e.preventDefault();
    
      setList(list => ({
        ...list,
        plans: [
          ...list.plans,
          {
            plan: input.plan,
            price: parseInt(input.price),
          },
        ],
      }));
    
      setInput({plan: "", price: ""});
    };