Open Veeksi opened 3 weeks ago
Yes it works with React Native. Let me know if you're having any issue with that.
Yes it works with React Native. Let me know if you're having any issue with that.
Hmm do you have an example with React Native? I am getting this warning from the library validate
function when trying to run it on react-native:
Warning: TypeError: r.querySelectorAll is not a function (it is undefined)
I feel like the problem is that react-native doesn't allow to directly access DOM elements like in web development.
My implementation looks like this:
import { useEffect, useMemo, useRef, useState } from "react";
import { Button, StyleSheet, Text, View, findNodeHandle } from "react-native";
import { type SlotItemMap, type Swapy, createSwapy } from "swapy";
const SwapyComponent = () => {
const swapyRef = useRef<Swapy>();
const containerRef = useRef(null);
const [items, setItems] = useState([
{ id: "1", title: "A" },
{ id: "2", title: "B" },
{ id: "3", title: "C" },
]);
const [slotItemsMap, setSlotItemsMap] = useState<SlotItemMap>([
...items.map((item) => ({
slotId: item.id,
itemId: item.id,
})),
// Defining an empty slot by setting itemId to null.
{ slotId: `${Math.round(Math.random() * 99999)}`, itemId: null },
]);
const slottedItems = useMemo(
() =>
slotItemsMap.map(({ slotId, itemId }) => ({
slotId,
itemId,
item: items.find((item) => item.id === itemId),
})),
[items, slotItemsMap],
);
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
// Get the newly added items and convert them to slotItem objects
const newItems = items
.filter(
(item) => !slotItemsMap.some((slotItem) => slotItem.itemId === item.id),
)
.map((item) => ({
slotId: item.id,
itemId: item.id,
}));
// Remove items from slotItemsMap if they no longer exist in items
const withoutRemovedItems = slotItemsMap.filter(
(slotItem) =>
items.some((item) => item.id === slotItem.itemId) || !slotItem.itemId,
);
/******* Below is how you would remove items and keep their slots empty ******/
// const withoutRemovedItems = slotItemsMap.map(slotItem => {
// if (!items.some(item => item.id === slotItem.itemId)) {
// return { slotId: slotItem.slotId, itemId: null }
// }
// return slotItem
// })
const updatedSlotItemsMap = [...withoutRemovedItems, ...newItems];
setSlotItemsMap(updatedSlotItemsMap);
swapyRef.current?.setData({ array: updatedSlotItemsMap });
}, [items]);
useEffect(() => {
const containerNode = findNodeHandle(containerRef.current);
swapyRef.current = createSwapy(containerNode, {
manualSwap: true,
swapMode: "hover",
autoScrollOnDrag: true,
});
swapyRef.current.onSwap(({ data }) => {
// You need to call setData because it's a manualSwap instance
swapyRef.current?.setData({ array: data.array });
setSlotItemsMap(data.array);
});
return () => {
swapyRef.current?.destroy();
};
}, []);
return (
<View style={styles.app}>
{/* ADD BUTTON */}
<Button
title="Add"
onPress={() => {
const id = `${Math.round(Math.random() * 99999)}`;
const updatedItems = [...items, { id, title: id }];
setItems(updatedItems);
}}
/>
<View ref={containerRef} style={styles.container}>
{slottedItems.map(({ itemId, slotId, item }) => (
<View style={styles.slot} key={slotId}>
{/* ITEM */}
{item ? (
<View style={styles.item} key={itemId}>
<View style={styles.handle} />
<Text>{item.title}</Text>
{/* DELETE ITEM BUTTON */}
<Button
title="x"
onPress={() => {
const updatedItems = items.filter((i) => i.id !== item.id);
setItems(updatedItems);
}}
/>
</View>
) : null}
</View>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1,
padding: 20,
},
container: {
flex: 1,
marginTop: 20,
},
slot: {
marginBottom: 10,
padding: 10,
borderWidth: 1,
borderColor: "#ccc",
},
item: {
padding: 10,
backgroundColor: "#f9f9f9",
},
handle: {
height: 10,
backgroundColor: "#ccc",
marginBottom: 5,
},
});
export default SwapyComponent;
The title says it and two words, amazing library!