Closed darrylyoung closed 3 years ago
any update? ðŸ˜
Need help on this too! I'm working on closing Swipeable
by index instead of last swiped item.
Need help on this too! I'm working on closing Swipeable by index instead of last swiped item.
Yeah, I played around with a solution like that but nothing felt that good so, unfortunately, I removed the whole swiping gesture from my app.
I tried using ref
as well and it seems to be working fine except that the close animation is delayed a little.
Hi, can someone help me. T tried the example project but when i tried to close the swipeable view but its not working. Am I missing or doingsomething.. Any help is really appreciated. Thank you so much.
this is the sample project link : https://github.com/kmagiera/react-native-gesture-handler/blob/master/Example/swipeable/AppleStyleSwipeableRow.js
Any chance we can get an update on this? It seems like a few people are wondering the same thing. Thanks!
I would like to introduce this function i my apps
I tried using
ref
as well and it seems to be working fine except that the close animation is delayed a little.
can you show us your workaround ?
I tried using
ref
as well and it seems to be working fine except that the close animation is delayed a little.can you show us your workaround ?
@AmanSharma2609 The idea is using ref
and close()
. Hope it helps
let row: Array<any> = [];
let prevOpenedRow;
renderItem ({ item, index }) {
return (
<Swipeable
ref={ref => row[index] = ref}
friction={2}
leftThreshold={80}
rightThreshold={40}
renderRightActions={renderRightActions}
containerStyle={style.swipeRowStyle}
onSwipeableOpen={closeRow(index)}
...
>
...
</Swipeable>);
}
closeRow(index) {
if (prevOpenedRow && prevOpenedRow !== row[index]) {
prevOpenedRow.close();
}
prevOpenedRow = row[index];
}
i use array of ref to fixed this issue, i think this issue cause by single ref, when we iterate over array the ref became the last ref of item in list so i did give all the list of item a ref based on index or item.id and called the ref based on index or id
first
constructor(props) {
super(props);
this.state = {
};
this.refsArray = []; // add this
}
then in swipeable collect the ref by passing index or your custom id
<Swipeable
ref={ref => {
this.refsArray[index] = ref; //or this.refsArray[item.id]
}}
friction={2}
leftThreshold={30}
rightThreshold={40}
renderRightActions={(progress) => this.renderRightActions(progress, item)}
>
then to close one item do this in your method
this.refsArray[index].close(); // or this.refsArray[item.id].close(); if you are using custom id
I tried using
ref
as well and it seems to be working fine except that the close animation is delayed a little.can you show us your workaround ?
@AmanSharma2609 The idea is using
ref
andclose()
. Hope it helpslet row: Array<any> = []; let prevOpenedRow; renderItem ({ item, index }) { return ( <Swipeable ref={ref => row[index] = ref} friction={2} leftThreshold={80} rightThreshold={40} renderRightActions={renderRightActions} containerStyle={style.swipeRowStyle} onSwipeableOpen={closeRow(index)} ... > ... </Swipeable>); } closeRow(index) { if (prevOpenedRow && prevOpenedRow !== row[index]) { prevOpenedRow.close(); } prevOpenedRow = row[index]; }
@anniewey It worked thank you!!
I tried using
ref
as well and it seems to be working fine except that the close animation is delayed a little.
This solution works for me. I'm using react-native-gesture-handler v1.4.1 and we have the method onSwipeableWillOpen(). Try to use this instead onSwipeableOpen().
List.js
let swipedCardRef = null;
const onOpen = ref => {
if (swipedCardRef) swipedCardRef.current.close();
swipedCardRef = ref;
};
const onClose = ref => {
if (ref == swipedCardRef) {
swipedCardRef = null;
}
};
const renderItem = ({ item, index }) => {
return (
<Item
item={item}
index={item}
removeItem={removeItem(item)}
onOpen={onOpen}
onClose={onClose}
/>
);
};
<Flatlist data={[...]} renderItem={renderItem} ... />
Item.js
const onSwipeOpen = () => {
if (!isOpened) {
isOpened = true;
onOpen(rowRef);
}
};
const onSwipeClose = () => {
if (isOpened) {
isOpened = false;
onClose(rowRef);
}
};
return (
<Swipeable
ref={rowRef}
renderLeftActions={renderLeftActions}
onSwipeableOpen={onSwipeOpen}
onSwipeableClose={onSwipeClose}
>
<TouchableOpacity
onPressIn={onSwipeOpen}
>
...
</TouchableOpacity>
</Swipeable>
);
It can help you!
I wrapped my list in a SwipeProvider
. It uses Context to maintain the state of which item is open. When an item opens, it updates the context with an id. All the other swipe-ables use a hook to see if the currently open id matches their id. If it doesn't, they close themselves.
@calendee i’m trying to do the same thing. What prop do you use to force close the non matching rows?
@Lsleiman Sorry for the slow response.
Here's a sample: https://gist.github.com/calendee/ba37861b237b57ee49b7949766c9a0da
This seems to be working for me. Sharing just in case
let rowRefs = new Map();
const renderItem = ({item}) => (
<Swipeable
key={item.key}
ref={ref => {
if (ref && !rowRefs.get(item.key)) {
rowRefs.set(item.key, ref);
}
}}
onSwipeableWillOpen={()=>{
[...rowRefs.entries()].forEach(([key, ref]) => {
if (key !== item.key && ref) ref.close();
});
}}
>
</Swipeable>
);
rowRef
I'm getting rowRef is undefined
@ShravanMeena I used rowRefs in my example
@Lsleiman Sorry for the slow response.
Here's a sample: https://gist.github.com/calendee/ba37861b237b57ee49b7949766c9a0da
Thank you @calendee . Your solution worked for me. I had three tabs and also had to close all swipeables when switching tabs. Using react-navigation as the navigation solution. Here is a gist in case someone needs it https://gist.github.com/barunprasad/a738d944fa9abf4e6993f719b13827ad
@Lsleiman this solves my issue! Thanks alot
Here what I did:
const EMPTY_KEY = '';
const ComponentList = (props) => {
const row: Array<any> = [];
const [key, setKey] = React.useState<string | any>(EMPTY_KEY);
const handleWillOpen = (index : any) => () => (key !== EMPTY_KEY) && (key !== index) && row[key].close();
const handleOpen = (index : any) => () => setKey(index);
return <Swipeable
ref={ref => row[index] = ref}
{....renders}
onSwipeableRightWillOpen={handleWillOpen(index)}
onSwipeableLeftWillOpen={handleWillOpen(index)}
onSwipeableOpen={handleOpen(index)}>
<View
{....components}
</View>
</Swipeable>
not using useCallback, or creating context whatever, just save the key, and make sure that before open some different key close the prior one.
Here what I did:
const EMPTY_KEY = ''; const row: Array<any> = []; const [key, setKey] = React.useState<string | any>(EMPTY_KEY); const handleWillOpen = (index : any) => () => (key !== EMPTY_KEY) && (key !== index) && row[key].close(); const handleOpen = (index : any) => () => setKey(index); return <Swipeable ref={ref => row[index] = ref} {....renders} onSwipeableRightWillOpen={handleWillOpen(index)} onSwipeableLeftWillOpen={handleWillOpen(index)} onSwipeableOpen={handleOpen(index)}> <View {....components} </View> </Swipeable>
not using useCallback, or creating context whatever, just save the key, and make sure that before open some different key close the prior one.
Hi @theristes, I've tried your code but it seems not to work on my end. I've created a gist, if you are willing to take a quick look. Thanks :) https://gist.github.com/DeveloperDanX/9aac8af280f01b84fbb35bbd57717441
@anniewey I used your solution and it worked but Idk if this happened to you but when I connect the app to my device and I got this log from xCode.
Can anyone confirm this is also happening to you or it's just me? I logged (prevOpenedRow) in the console from the closeRow function and this is what I found in the xCode console.
@calendee Thank you for your solution!
Because we are using a FlatList
with +100 swipeable items we had to use use-context-selector to only re-render the opened item and item to close.
The naming and Context state is sligthy different from your Gist but to give you an idea of the useContextSelector
usage:
export function useSwipeableList(id: string) {
const isOpenedItem = useContextSelector(
SwipeableListContext,
(state) => state?.openedItemId === id
);
const setOpenedItemId = useContextSelector(
SwipeableListContext,
(state) => state?.setOpenedItemId
);
return { isOpenedItem, setOpenedItemId };
}
It took me a long time to find a complete solution, but this reply on StackOverflow by user nodir.dev has a minimal and complete example like this:
const YourComponent = () => {
const swipeRef = React.useRef()
const closeSwipable = () => {
swipeRef?.current?.close()
}
return (
<Swipeable ref={swipeRef}>
</Swipeable>
)
}
The trick is to get the reference and React.useRef() solves that (not intuitive if you never used it). With this, I could easily add the close-method exactly where I wanted in my flow.
The way I handled it was to create a a prevRef
outside of the item list, and to create a swipeRef
for each row. Then onSwipeableOpen
, I close the prevRef
row, and then set prevRef
to the current swipeRef
. This way I don't need to keep an array of refs.
const prevRef = React.useRef<Swipeable>(null)
...
<FlatList
renderItem={<Item ref={ref} .../>
/>
const Item = React.forwardRef<Swipeable, ItemProps>(props, prevRef) => {
const swipeRef = React.useRef<Swipeable>(null)
return (
<Swipeable
ref={swipeRef}
onSwipeableOpen={() => {
if(prevRef
&& typeof prevRef !== "function"
&& prevRef.current !== null) {
if (prevRef.current !== swipeRef.current) {
prevRef.current.close();
}
prevRef.current = swipeRef.current;
}
}}
/>
)
}
This seems to be working for me. Sharing just in case
let rowRefs = new Map(); const renderItem = ({item}) => ( <Swipeable key={item.key} ref={ref => { if (ref && !rowRefs.get(item.key)) { rowRefs.set(item.key, ref); } }} onSwipeableWillOpen={()=>{ [...rowRefs.entries()].forEach(([key, ref]) => { if (key !== item.key && ref) ref.close(); }); }} > </Swipeable> );
This is simple and works perfectly! in case anyone comes across this, if you are using child components to render your row items set your 'rowRefs = new Map();' on the parent and pass it down to the child.
@Lsleiman Desculpe pela resposta lenta.
Aqui está um exemplo: https://gist.github.com/calendee/ba37861b237b57ee49b7949766c9a0da
Thanks, i searched for how to implement the onpenLeft and right method everywhere
This solution is the same as @sammiepls proposed, but with some additionals.
My <Swipeable>
component works with onSwipeableOpen
and onSwipeableWillOpen
. It looks like component closes faster:
/* List.tsx */
const ref = useRef<Swipeable>(null);
const renderItem = (itemData: {item: ITodo}) => (
<ListItem ref={ref} {...itemData.item} />
);
<FlatList
data={data}
renderItem={item => renderItem(item)}
...
/>
/* ListItem.tsx */
export const ListItem = React.forwardRef((props, previousRef) => {
const swipeRef = useRef<Swipeable>(null);
const handleSwipeableWillOpen = () => {
if (previousRef && previousRef.current !== null) {
if (previousRef.current !== swipeRef.current) {
previousRef.current?.close();
}
}
};
const handleSwipeableOpen = () => {
previousRef.current = swipeRef.current;
};
return (
<Swipeable
ref={swipeRef}
onSwipeableOpen={handleSwipeableOpen}
onSwipeableWillOpen={handleSwipeableWillOpen}
...
>
<View style={[styles.row, theme.row]}>
{/* something */}
</View>
</Swipeable>
);
}
Who wrote this on the hook?
This is how it looks on a functional component with hooks
let swipeRow = [], prevOpenRow;
const deleteItemHandler = async (type, id) => {
await dispatch(deleteItem(type, id));
};
const closeRow = useCallback((id) => {
if (prevOpenRow && prevOpenRow !== swipeRow[id]) {
prevOpenRow.close();
}
prevOpenRow = swipeRow[id];
}, [swipeRow]);
const renderRightAction = (
iconName,
text,
color,
x,
progress,
editItemParams
) => {
const { item } = editItemParams;
const { id, type } = item;
const trans = progress.interpolate({
inputRange: [0, 1],
outputRange: [x, 0],
extrapolate: 'clamp'
});
const navigateToEditScreen = (params) => {
closeRow(id);
navigation.navigate(EditScreen, params)
}
const onPressHandler = () => {
if (text === trashBtn) {
deleteItemHandler(type, id)
} else {
navigateToEditScreen(editItemParams)
}
}
return (
<Animated.View style={{ flex: 1, transform: [{ translateX: trans }] }}>
<TouchableOpacity
style={[styles.swipeBtn, { backgroundColor: color }]}
onPress={onPressHandler}
>
<Icon name={iconName} color={appColors.white} size={25} />
<Text style={styles.swipeBtnTextStyle}>{text}</Text>
</TouchableOpacity>
</Animated.View>
);
};
const renderRightActions = (
progress,
_dragAnimatedValue,
editItemParams
) => {
return (
<View
style={{
width: 192,
flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
}}>
{renderRightAction(editIcon, editBtn, appColors.lightGray, 190, progress, editItemParams)}
{renderRightAction(trashIcon, trashBtn, appColors.deleteRed, 95, progress, editItemParams)}
</View>
)
};
const renderItem = (index, item, type) => {
const { id, amount, tag, timestamp } = item;
const editItemParams = {
item: {
type,
id,
tag,
amount,
timestamp
}
}
return (
<Swipeable
key={id}
ref={ref => swipeRow[id] = ref}
renderRightActions={(progress, _dragAnimatedValue) => renderRightActions(progress, _dragAnimatedValue, editItemParams)}
onSwipeableWillOpen={() => closeRow(id)}
overshootRight={false}
rightThreshold={10}
friction={1}
>
<Pressable
style={[itemContainerStyle, styles.itemContainer]}
onPress={() => closeRow(id)}
>
<Item
type={type}
tag={tag}
amount={amount}
timestamp={timestamp}
/>
</Pressable >
</Swipeable>
)
};
I tried using
ref
as well and it seems to be working fine except that the close animation is delayed a little.can you show us your workaround ?
@AmanSharma2609 The idea is using
ref
andclose()
. Hope it helpslet row: Array<any> = []; let prevOpenedRow; renderItem ({ item, index }) { return ( <Swipeable ref={ref => row[index] = ref} friction={2} leftThreshold={80} rightThreshold={40} renderRightActions={renderRightActions} containerStyle={style.swipeRowStyle} onSwipeableOpen={closeRow(index)} ... > ... </Swipeable>); } closeRow(index) { if (prevOpenedRow && prevOpenedRow !== row[index]) { prevOpenedRow.close(); } prevOpenedRow = row[index]; }
This works fine, the only thing I changed to get rid of the delay was to use "onBegan" instead of "onSwipeableOpen"
Can anyone help me out? I was trying to do the same in FlatList but unfortunately it is not working. Here is my approach.
const swipeRow = [];
let prevOpenRow;
const closeRow = useCallback((id) => {
if (prevOpenRow && prevOpenRow !== swipeRow[id]) {
prevOpenRow.close();
}
prevOpenRow = swipeRow[id];
}, [swipeRow]);
return (
<Swipeable
ref={ref => swipeRow[index] = ref}
friction={2}
enableTrackpadTwoFingerGesture
rightThreshold={40}
renderRightActions={(progress, dragX) => (
<RightActions progress={progress} dragX={dragX} onPress={() => { }} />
)}
onSwipeableOpen={() => closeRow(index) }
style={{ flex: 1 }}
>
{..view}
</Swipeable>
}
Anything I miss out or done wrongly?
state = { swipeRow=[]; }
render (item,index){
return ( <Swipeable ref={ref => this.state.swipeRow[index] = ref} renderRightActions={() => this.leftSwipe(item)}> ) }
onClose() { this.state.swipe.map((reference) => { if (reference) { reference.close(); this.setState({isConfirmModalVisible: false}); } }); }
I needed to do this, and none of the above solutions felt right. So I pushed this PR which once merged in will allow us to do the following:
Close any previously opened Swipeable views when one is opened
const openedRow = useRef();
function renderItem({item: payable, index, separators}) {
return (
<Swipeable
onSwipeableWillOpen={() => openedRow.current?.close()} // Close a previously opened row
onSwipeableOpen={(_, swipeable) => (openedRow.current = swipeable)} // Keep a link to the currenlty
Close a Swipeable view when it is being deleted.
renderRightActions={(_, __, swipeable) => {
return (
<TouchableOpacity
onPress={() => {
swipeable.close(); // Close the Swipeable when the user presses the button
you have so save the key, otherwise you will not be able to recognise the Swipeable
you have so save the key, otherwise you will not be able to recognise the Swipeable
Not sure what you mean by that, but the code does work.
For # 1, I am keeping a reference to the last opened Swipeable object (which is the same that the other codes samples are doing in their array).
For # 2, the swipeable object ref is kept in the context.
So I don't follow why you are asking for the keys to be kept. If you see a flaw with this, please do point it out on my PR.
Don't know why this is so complicated here what i do it self closing and you don't have to wait for another row to get close
let row: Array<any> = [];
const closeRow = (index: number) => {
setTimeout(() => {
console.log('close row', index)
if(row[index]){
row[index].close();
}
},2000)
};
<Swipeable renderRightActions={(progress, dragX) => rightActions(progress, dragX, props.onDelete)}
onSwipeableOpen={() => closeRow(props.index)}
ref={ref => (row[props.index] = ref)}
>
<View style={{
flexDirection: 'row',
backgroundColor: 'blue',
marginVertical: '2%',
padding: 10,
justifyContent: 'center'
}}>
<Text>{props.item.title}</Text>
</View>
</Swipeable>
It's work for me.
I created an array for refs.
const swipeableRef = useRef<Swipeable[]>([])
I pushed each ref for my array, for example:
<Swipeable
ref={(ref) => {
if (ref) {
swipeableRef.current.push(ref)
}
To closed the specific line, I'm using.
swipeableRef.current?.[index].close()
When a swipeable row is opened and I navigate to a different stack (using tab navigation for example) and then come back to the page with the swipeable row the row is still opened. Is there a way to "reset" the swipeables? So that whenever I return to the page with the swipeable rows all rows are always closed?
I know this isn't exactly the topic but it is kind of related. Hope this question doesn't violate any rules.
I tried using
ref
as well and it seems to be working fine except that the close animation is delayed a little.can you show us your workaround ?
@AmanSharma2609 The idea is using
ref
andclose()
. Hope it helpslet row: Array<any> = []; let prevOpenedRow; renderItem ({ item, index }) { return ( <Swipeable ref={ref => row[index] = ref} friction={2} leftThreshold={80} rightThreshold={40} renderRightActions={renderRightActions} containerStyle={style.swipeRowStyle} onSwipeableOpen={closeRow(index)} ... > ... </Swipeable>); } closeRow(index) { if (prevOpenedRow && prevOpenedRow !== row[index]) { prevOpenedRow.close(); } prevOpenedRow = row[index]; }
How you are using the prevOpenedRow and what is its intail value,
I'm currently using
Swipeable
to create a list of items, each of which the user can swipe to delete. It's working fine but if I swipe a row and open one and then swipe another, the previously-opened row is still swiped open but I'd like it to be closed.I've searched for solutions and made a little progress myself using refs but I've not been able to get it working perfectly. I've also come across quite a few people asking for the same thing and it seems like pretty standard behaviour. It'd be great if you could add some documentation with examples covering this.
Thank you.