skip to Main Content

I have a component that needs to render an array of divs, the array should add another div after a certain function outside the component is triggered, the function is outside the component because it needs to be exported.

The problem is that if I create the array as a state in the component I can’t set the state in the external function because it is not declared there.

How can I render and update such an array?

const codeContainer = document.querySelector('.codeContainer');

export function DragHandler() {

    codeContainer.addEventListener('dragover', function (e) {
      e.preventDefault();
    });

    codeContainer.addEventListener('drop', function (e) {
        //a lot of stuff
        //new variable containing a div element created here
    });
};


const CodePageMain = () => {

  return (
    <div className='codeContainer'>
        //a lot of stuff
        //need an array here with a new div variable every time DragHandler() is called
    </div>
  )
}

export default CodePageMain

I tried creating a state inside the component and passing a function to the SetState that only returns the new div from the function and sets the state inside the component instead of inside the function but I didn’t manage to do it.

2

Answers


  1. You can pass the array as an argument to the external function. In case you need both the function and the array (or the result of the function acting on the array) to be exported, you need to use global state.

    Login or Signup to reply.
  2. The parent could send a function that accepts an item to the child, then adds it to the collection/array. This allows the child to invoke the function with a given item without knowing any implementation details. It does not know how, or to what collection the item is added.

    Here is an example, using a drag/drop scenario:

    const us = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
    const money = (cents) => us.format(cents / 100);
    
    function App() {
      const [shoppingCard, setShoppingCard] = React.useState(new Map());
      
      // function that adds elements to a collection, like Array, Set, or Map.
      const addToShoppingCard = React.useCallback((shopItem) => {
        setShoppingCard((card) => {
          // State changes should be immutable, so we dupplicate the
          // current state and re-assign the Map value to a new objects
          // instead of `card.get(shopItem.id).amount++`.
          card = new Map(card);
          if (!card.has(shopItem.id)) card.set(shopItem.id, { ...shopItem, amount: 0 });
          const cardItem = card.get(shopItem.id);
          card.set(shopItem.id, { ...cardItem, amount: cardItem.amount + 1 });
          return card;
        });
      }, []);
      
      // Now we send the custom function to the component that needs to add elements,
      // and send the collection itself to another component.
      return (
        <div>
          {/* list shop items and allow them to be added to the card via drag/drop */}
          <ComponentThatCallsDragHandler addToShoppingCard={addToShoppingCard} />
          {/* display the current card contents */}
          <ComponentThatNeedsToDisplayCollection shoppingCard={shoppingCard} />
        </div>
      );
    }
    
    function dragHandler(event, item, addToShoppingCard) {
      addToShoppingCard({ ...item, details: <div>Your div variable here?</div> });
      // other stuff?
    }
    
    // mock shop items
    const SHOP_ITEMS = [
      { id: 1, name: "TV",   price: 140000 },
      { id: 2, name: "PC",   price:  80000 },
      { id: 3, name: "Desk", price:  35000 },
    ];
    const NON_SHOP_ITEMS = [
      { id: 4, name: "Unorderable", price: 13200 },
    ];
    const ITEMS = SHOP_ITEMS.concat(NON_SHOP_ITEMS);
    
    function ComponentThatCallsDragHandler({ addToShoppingCard }) {
      function setDataTransfer(event, item) {
        event.dataTransfer.setData("application/json", JSON.stringify(item));
      }
      
      const allowDropShopItems = React.useMemo(() => {
        const allowed = new Set(SHOP_ITEMS.map(item => JSON.stringify(item)));    
        return (event) => {
          const json = event.dataTransfer.getData("application/json");
          // only allow shop items to be dropped (attributes must be in same order)
          if (allowed.has(json)) event.preventDefault();
        };
      }, [SHOP_ITEMS]);
      
      const callDragHandler = React.useCallback((event) => {
        event.preventDefault();
        const item = JSON.parse(event.dataTransfer.getData("application/json"));
        dragHandler(event, item, addToShoppingCard);
      }, [addToShoppingCard]); // <- add dragHandler, if the identity is unstable
    
      return (
        <div>
          <h1>Shop Listing</h1>
          <ul>
            {ITEMS.map((item) => (
              <li key={item.id} draggable onDragStart={e => setDataTransfer(e, item)}>
                {item.name} ({money(item.price)})
              </li>
            ))}
          </ul>
          <div>
            <div>Add to card (dropzone):</div>
            <div
              className="shop-item-dropzone"
              onDragOver={allowDropShopItems}
              onDrop={callDragHandler}
            />
          </div>
        </div>
      );
    }
    
    function ComponentThatNeedsToDisplayCollection({ shoppingCard }) {
      return (
        <div>
          <h1>Shopping Card</h1>
          <ul>
            {Array.from(shoppingCard).map(([, item]) => (
              <li key={item.id}>
                <div>
                  {item.name} ({item.amount} x {money(item.price)} = {money(item.amount * item.price)})
                </div>
                <div>{item.details}</div>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    
    ReactDOM.createRoot(document.querySelector("#root")).render(<App />);
    .shop-item-dropzone {
      min-height: 2em;
      background-color: Teal;
    }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="root"></div>

    This example is quite a bit different from the code you provided in your question. Generally speaking you try to avoid addEventListener() calls when working with React. Most scenarios can be handled with the onEventName={} props that React provides.

    I don’t know if this answers your question in full, but I hope you can get some inspiration from this example.

    This is also called lifting state up.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search