I have a parent component ParentItem
which renders ChildItem
components.
ParentItem
passes removeItem()
to ChildItem
components during render.
Normally, anytime useState
is called and the new state is different from the previous state, the component should re-render.
For some reason, in this case it seems like removeItem()
is passed to the children with childItemList
at the time of render, so if one ChildItem
calls removeItem()
the next child will have stale a childItemList
(ie they will have the initial childItemList
with all children, when I want the previously removed child to be reflected in the following calls to removeItem()
. I feel like the problem is that handleClick()
is being called inside of panResponder
which is useRef
, but i'm not sure if thats really the case, and if it is why.
I have resorted to using a useRef
copy of childItemList
, make changes in the useRef
copy and passing it to setChildItemList()
within removeItem()
.
But I don't like how I need two variables to track the list of child components. I feel like there is probably a better way to go about this.
Parent:
const ParentItem = () => {
const [childItemList, setChildItemList] = useState([
{id:"a", name: "ChildItemA"},
{id:"b", name: "ChildItemB"},
{id:"c", name: "ChildItemC"},
{id:"d", name: "ChildItemD"}
]);
const removeItem = (itemId) => {
setChildItemList([...items].filter(item => item.id !== itemId));
}
return(
<View>
{
childItemList.map((item) => {
return(
<ChildItem
key={Math.random()}
handleClick={removeItem}
details={item}
/>
)
})
}
</View>
)
}
export default ParentItem;
Child:
const ChildItem = (props) => {
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef({
PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
Animated.timing( pan, {
toValue: { x: -10, y -10 },
duration: 1000,
useNativeDriver: false
}).start(()=>{
props.handleClick(props.details.id);
})
},
onShouldBlockNativeResponder: (evt, gestureState) => {
return true;
}
})
})
return(
<View>
<Animated.View
{...panResponder.panHandlers}
>
</Animated.View>
</View>
);
}
export default ChildItem;
Parent: Current solution using useRef copy of childItemList
const ParentItem = () => {
const [childItemList, setChildItemList] = useState([
{id:"a", name: "ChildItemA"},
{id:"b", name: "ChildItemB"},
{id:"c", name: "ChildItemC"},
{id:"d", name: "ChildItemD"}
]);
/* useRef copy of childItemListRef */
const childItemListRef = useRef([childItemList]);
const removeItem = (itemId) => {
/* Set childItemListRef, then pass it to setChildItemList */
childItemListRef.current = childItemListRef.current.filter(item => item.id !== itemId);
setChildItemList(childItemListRef);
}
return(
<View>
{
childItemList.map((item) => {
return(
<ChildItem
key={Math.random()}
handleClick={removeItem}
details={item}
/>
)
})
}
</View>
)
}
export default ParentItem;
First of, don't use a Math.random for key. You have the ID right there so use item.id instead since it is unique.
Also for the remove Item function, I would bind a unique function with the id in for every Child instead.
By doing this you should not need to have a useRef hook for this.
You have no use for useRef in the removeItem function either. Just go back to the first version of removeItem you where using.
This works fine for me. Here is a working example: https://codepen.io/nbjorling/pen/YzjVzvK