I'm trying to create a reference using the useRef
hook for each items within an array of `data by doing the following:
const markerRef = useRef(data.posts.map(React.createRef))
Now, data
is fetched externally through GraphQL and it takes time to arrive, therefore, during the mounting phase, data
is undefined
. This causes the following error:
TypeError: Cannot read property '0' of undefined
I've tried the following with no success:
const markerRef = useRef(data && data.posts.map(React.createRef))
How do I set up so that I can map through the data
without causing the error?
useEffect(() => {
handleSubmit(navigation.getParam('searchTerm', 'default value'))
}, [])
const [loadItems, { called, loading, error, data }] = useLazyQuery(GET_ITEMS)
const markerRef = useRef(data && data.posts.map(React.createRef))
const onRegionChangeComplete = newRegion => {
setRegion(newRegion)
}
const handleSubmit = () => {
loadItems({
variables: {
query: search
}
})
}
const handleShowCallout = index => {
//handle logic
}
if (called && loading) {
return (
<View style={[styles.container, styles.horizontal]}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
)
}
if (error) return <Text>Error...</Text>
return (
<View style={styles.container}>
<MapView
style={{ flex: 1 }}
region={region}
onRegionChangeComplete={onRegionChangeComplete}
>
{data && data.posts.map((marker, index) => (
<Marker
ref={markerRef.current[index]}
key={marker.id}
coordinate={{latitude: marker.latitude, longitude: marker.longitude }}
// title={marker.title}
// description={JSON.stringify(marker.price)}
>
<Callout onPress={() => handleShowCallout(index)}>
<Text>{marker.title}</Text>
<Text>{JSON.stringify(marker.price)}</Text>
</Callout>
</Marker>
))}
</MapView>
</View>
)
I'm using the useLazyQuery
because I need to trigger it at different times.
Update:
I have modified the useRef
to the following on the advise of @azundo:
const dataRef = useRef(data);
const markerRef = useRef([]);
if (data && data !== dataRef.current) {
markerRef.current = data.posts.map(React.createRef);
dataRef.current = data
}
When I console.log markerRef.current
, I get the following result:
which is perfectly fine. However, when I attempt to map each current
and invoke showCallout()
to open all the callouts for each marker by doing the following:
markerRef.current.map(ref => ref.current && ref.current.showCallout())
nothing gets executed.
console.log(markerRef.current.map(ref => ref.current && ref.current.showCallout()))
This shows null for each array.
The useRef
expression is only executed once per component mount so you'll need to update the refs whenever data
changes. At first I suggested useEffect
but it runs too late so the refs are not created on first render. Using a second ref to check to see if data
changes in order to regenerate the marker refs synchronously should work instead.
const dataRef = useRef(data);
const markerRef = useRef([]);
if (data && data !== dataRef.current) {
markerRef.current = data.posts.map(React.createRef);
dataRef.current = data;
}
Additional edit:
In order to fire the showCallout
on all of the components on mount, the refs must populated first. This might be an appropriate time for useLayoutEffect
so that it runs immediately after the markers are rendered and ref values (should?) be set.
useLayoutEffect(() => {
if (data) {
markerRef.current.map(ref => ref.current && ref.current.showCallout());
}
}, [data]);