Search code examples
javascriptreactjsarraysreact-dnd

Passing an Array from one UseState to Another


I'm currently trying to figure out how to pass an array from one useState object to another across two different components. In my first useState I have an array called imagesOriginal, which gets filled with file paths dynamically to various different images like in the following:

[
    "https://source.unsplash.com/WLUHO9A_xik/900x900",
    "https://source.unsplash.com/R4K8S77qtwI/900x900",
    "https://source.unsplash.com/jJGc21mEh8Q/900x900"
]

In my App.js, I construct it like so.

import React, { useCallback, useState } from 'react';
import ShowImage from './ShowImage.jsx';
import DropBox from './DropBox.js';

function App() {
    const [imagesOriginal, setImages] = useState([]);

    const onDrop = useCallback((acceptedFiles) => {
        acceptedFiles.map((file, index) => {
            const reader = new FileReader();

            reader.onload = function (e) {
                setImages((prevState) => [
                    ...prevState,
                    { id: index, src: e.target.result },
                ]);
            };

            reader.readAsDataURL(file);
            return file;
        });
    }, []);

    return (
        <div className="App">
            <div class="parent">
                <div>
                    <h3>Originals</h3>
                    <DropBox onDrop={onDrop} />
                    <ShowImage images={imagesOriginal}/>
                </div>
            </div>
        </div>
    );
}

export default App;

The main issue comese in the ShowImage.jsx, where I want to pass that array of images to another useState, as I need to use both the array and the setItems to sort the array with a new order.

import React, { useState } from 'react';
import {
    DndContext,
    closestCorners,
    MouseSensor,
    TouchSensor,
    DragOverlay,
    useSensor,
    useSensors,
} from '@dnd-kit/core';
import "./ShowImage.css"
import {arrayMove, SortableContext} from '@dnd-kit/sortable';
import {SortablePhoto} from './SortablePhoto.jsx';

const ShowImage = ({images}) => {

    const [items, setItems] = useState([]);
    setItems([...images]);
    const [activeId, setActiveId] = useState(null);
    const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));

    return(
        <div class="scroll">
            <DndContext
                sensors={sensors}
                collisionDetection={closestCorners}
                onDragStart={handleDragStart}
                onDragOver={handleDragOver}
                onDragEnd={handleDragEnd}
                onDragCancel={handleDragCancel}
            >
                <SortableContext items={items} strategy={() => {}}>
                    <div columns={1}
                        style={{
                        display: "grid",
                        gridAutoRows: `100px`,
                        gridGap: 10
                        }}
                    >
                        {items.map((url, index) => (
                            <SortablePhoto key={url.src} url={url.src} index={index}/>
                        ))}
                    </div>
                </SortableContext>
            </DndContext>
        </div>
    );

    function handleDragStart(event) {
        setActiveId(event.active.id);
    }
    
    function handleDragOver(event) {
        const {active, over} = event;
    
        if (active.id !== over.id) {
          setItems((items) => {
            const oldIndex = items.indexOf(active.id);
            const newIndex = items.indexOf(over.id);
    
            return arrayMove(items, oldIndex, newIndex);
          });
        }
    }
    
    function handleDragEnd(event) {
        setActiveId(null);
    }
    
    function handleDragCancel() {
        setActiveId(null);
    }
};

export default ShowImage;

I've tried using the line setItems([...images]); to try and pass the new items in, and also const [items, setItems] = useState(images);, but It never seems to update the items array. I'm probably doing something really stupid, but any help would be greatly appreciated.


Solution

  • You can create a function in your App component that wraps your setItems state modifier and pass this function as a prop to your nested ShowImage component where you could use it to manipulate the state. This way you won't need to maintain 2 different states.

    // App.js
    
    function App() {
        const [imagesOriginal, setImages] = useState([]);
        const setImagesWrapper = useCallback(val => {
          setImages(val);
        }, [setImages]);
        //...
    
        return (
            <div className="App">
                <div class="parent">
                    <div>
                        <h3>Originals</h3>
                        <DropBox onDrop={onDrop} />
                        <ShowImage 
                          images={imagesOriginal} 
                          setImages={setImagesWrapper}
                        />
                    </div>
                </div>
            </div>
        );
    }
    
    export default App;
    
    // ShowImage.js
    
    const ShowImage = ({ images, setImages }) => {
        const [activeId, setActiveId] = useState(null);
        const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
        // ...
    };
    
    export default ShowImage;