反应 useRef 钩子导致 "Cannot read property '0' of undefined" 错误

React useRef hook causing "Cannot read property '0' of undefined" error

我正在尝试使用 useRef 挂钩为 `data 数组中的每个项目创建引用,方法如下:

const markerRef = useRef(data.posts.map(React.createRef))

现在,data是通过GraphQL从外部获取的,需要时间才能到达,因此,在挂载阶段,dataundefined。这会导致以下错误:

TypeError: Cannot read property '0' of undefined

我尝试了以下但没有成功:

const markerRef = useRef(data && data.posts.map(React.createRef))

如何设置才能通过 data 映射而不导致错误?

    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>
    )

我正在使用 useLazyQuery 因为我需要在不同的时间触发它。

更新:

我已根据@azundo 的建议将 useRef 修改为以下内容:

const dataRef = useRef(data);
const markerRef = useRef([]);
if (data && data !== dataRef.current) {
    markerRef.current = data.posts.map(React.createRef);
    dataRef.current = data
}

当我 console.log markerRef.current 时,我得到以下结果:

非常好。但是,当我尝试映射每个 current 并调用 showCallout() 以通过执行以下操作打开每个标记的所有标注时:

markerRef.current.map(ref => ref.current && ref.current.showCallout())

什么都不执行。

console.log(markerRef.current.map(ref => ref.current && ref.current.showCallout()))

这对每个数组显示 null。

useRef 表达式仅在每个组件安装时执行一次,因此您需要在 data 更改时更新引用。起初我建议 useEffect 但它运行得太晚了,所以在第一次渲染时不会创建引用。使用第二个 ref 检查 data 是否发生变化以同步重新生成标记 refs 应该可以代替。

const dataRef = useRef(data);
const markerRef = useRef([]);
if (data && data !== dataRef.current) {
  markerRef.current = data.posts.map(React.createRef);
  dataRef.current = data;
}

补充编辑:

为了在挂载的所有组件上触发 showCallout,必须首先填充 refs。这可能是 useLayoutEffect 的合适时间,以便它在标记被渲染并设置 ref 值(应该?)后立即运行。

useLayoutEffect(() => {
  if (data) {
    markerRef.current.map(ref => ref.current && ref.current.showCallout());
  }
}, [data]);

使用记忆创建参考,例如:

const markerRefs = useMemo(() => data && data.posts.map(d => React.createRef()), [data]);

然后像这样渲染它们:

  {data &&
    data.posts.map((d, i) => (
      <Marker key={d} data={d} ref={markerRefs[i]}>
        <div>Callout</div>
      </Marker>
    ))}

并使用 refs 调用命令式函数,例如:

  const showAllCallouts = () => {
    markerRefs.map(r => r.current.showCallout());
  };

查看带有模拟 Marker 的工作代码:https://codesandbox.io/s/muddy-bush-gfd82