反应useRef钩子导致“无法读取未定义的属性'0'”错误

时间:2019-10-08 20:11:41

标签: reactjs react-native react-hooks react-native-maps

我正在尝试通过执行以下操作,使用useRef钩子为`data数组中的每个项目创建引用:

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

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

  

TypeError:无法读取未定义的属性“ 0”

我尝试以下操作均未成功:

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时,得到以下结果: enter image description here

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

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

什么都不会执行。

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

这显示每个数组为空。

2 个答案:

答案 0 :(得分:2)

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

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

其他修改:

为了在安装的所有组件上触发showCallout,必须先填充引用。对于useLayoutEffect,这可能是一个合适的时间,这样它就可以在渲染标记并设置参考值(应该?)之后立即运行。

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

答案 1 :(得分:1)

使用备忘录创建引用,例如:

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

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

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

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