React-native-reanimated: Creating sharedValue inside an hook

Created on 5 Oct 2020  路  6Comments  路  Source: software-mansion/react-native-reanimated

Description

I'm creating an Animated list so that I can do

type ItemType = {
    id: string
    color: string
}

const Screen > = function ({}) {

    const [items, setItems] = useState<ItemType[]>([])

    ...

    return (
     <DraggableList
         data={items}
         renderItem={_renderItem}
      />
    )
}

In DraggableList I want to maintain an up-to-date Map containing shared values like offset.
What I would do normally is using useEffect inside DraggableList so that I have something like

const myMap = useRef(new Map()).current

useEffect(() => {
   data.map((x) => {
     if(!myMap.has(x.id))
        myMap.set(x.id, { offset: useSharedValue(0)})
   })
}, [data])

and some logic to handle delete / add of new items.

However, as expected, I cannot use useSharedValue inside useEffect.
How can I create sharedValues like I could do in V1 with new Animated.Value ?

Package versions

  • React: 16.13.1
  • React Native: 0.63.2
  • React Native Reanimated: 2.0.0-alpha.7
鉂換uestion 馃彔 Reanimated2

All 6 comments

I would define that shared value inside the item of the list and pass the callback to set it into the map on useEffect.

In that way they'll always be up to date with the list.

You can also handle remove in that useEffect too.

Thanks @terrysahaidak, It's what I'm doing now but I would like to abstract this so that the user of the DraggableList API does'nt have to know we use shared values inside. Is it possible ?

Use context for this.

So you'll need to wrap the list with a context, and the user should call useDraaggableListItem() where you useContext to grab all the data you need and do whatever logic you need.

It can be either in the user's component or in your wrapper around the list item you render (where you handle measurements and onPress).

Just to be clear so I understand fully how you would do.
Let's take this simple example that does nothing but tries to dynamically define a SharedValue offset if an item is added :
How would you define DragListContext and useDraggableListItem ?

Ok I think I got it :) Thanks !

For reference:


type DragListContextProps = {
    items: Map<Item['id'], AnimatedItem>
    updateItems: (itemId: Item['id'], item: AnimatedItem) => void
}
const DragListContext = React.createContext<DragListContextProps>({
    items: new Map(),
    updateItems: () => {}
})

const CardItem: FC<{
    style: StyleProp<ViewStyle>
    id: Item['id']
}> = function ({ style, id }) {
    const offset = useSharedValue(0)

    const { items, updateItems } = useContext(DragListContext)

    useEffect(() => {
        if (!items.has(id)) updateItems(id, { offset })
    }, [items])

    function onPress() {
        console.log('Pressing : ', items.size)
    }

    const onGestureEvent = useAnimatedGestureHandler(
        {
            onStart: (_, ctx) => {},
            onActive: (event, ctx) => {
                console.log('active: ', items.size)
            },
            onEnd: (event) => {}
        },
        [items]
    )

    return (
        <PanGestureHandler {...{ onGestureEvent }}>
            <Animated.View>
                <RectButton onPress={onPress}>
                    <Card style={style} />
                </RectButton>
            </Animated.View>
        </PanGestureHandler>
    )
}

const DynamicItemsScreen: RNNFC<Props> = function ({}) {
    const [items, setItems] = useState<Item[]>([])
    const [animItems, setAnimItems] = useState<Map<string, AnimatedItem>>(
        new Map()
    )

    function onPress() {
        setItems((old) => [
            ...old,
            {
                id: 'item-' + old.length,
                color: colors[Math.floor(Math.random() * colors.length)]
            }
        ])
    }

    function updateItems(itemId: Item['id'], item: AnimatedItem){
        setAnimItems((old) => {
            const newMap = new Map([...old])
            newMap.set(itemId, item)
            return newMap
        })
    }

    return (
        <View style={styles.container}>
            <DragListContext.Provider value={{ items: animItems, updateItems}}>
                {items.map((x, i) => (
                    <CardItem
                        key={i}
                        id={x.id}
                        style={{ backgroundColor: x.color }}
                    />
                ))}
            </DragListContext.Provider>

            <View style={styles.btnsContainer}>
                <RectButton style={styles.btn} onPress={onPress}>
                    <Text style={styles.text}>Add Item</Text>
                </RectButton>
            </View>
        </View>
    )
}

Yeah, something like that. :+1:

Was this page helpful?
0 / 5 - 0 ratings