简体   繁体   English

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

[英]React useRef hook causing “Cannot read property '0' of undefined” error

I'm trying to create a reference using the useRef hook for each items within an array of `data by doing the following:我正在尝试通过执行以下操作为`data数组中的每个项目使用useRef钩子创建一个引用:

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 .现在, data是通过 GraphQL 从外部获取的,需要时间才能到达,因此,在挂载阶段, dataundefined This causes the following error:这会导致以下错误:

TypeError: Cannot read property '0' of undefined TypeError:无法读取未定义的属性“0”

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?如何设置以便我可以通过data map 而不会导致错误?

    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.我正在使用useLazyQuery因为我需要在不同的时间触发它。

Update:更新:

I have modified the useRef to the following on the advise of @azundo:根据@azundo 的建议,我已将useRef修改为以下内容:

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:当我 console.log markerRef.current时,我得到以下结果: 在此处输入图像描述

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:但是,当我尝试对每个current进行 map 并调用showCallout()通过执行以下操作打开每个标记的所有标注时:

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.这显示了每个阵列的 null。

The useRef expression is only executed once per component mount so you'll need to update the refs whenever data changes. useRef表达式仅在每个组件挂载时执行一次,因此您需要在data更改时更新引用。 At first I suggested useEffect but it runs too late so the refs are not created on first render.起初我建议useEffect但它运行得太晚了,所以在第一次渲染时不会创建参考。 Using a second ref to check to see if data changes in order to regenerate the marker refs synchronously should work instead.使用第二个 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;
}

Additional edit:附加编辑:

In order to fire the showCallout on all of the components on mount, the refs must populated first.为了在挂载的所有组件上触发showCallout ,必须首先填充 refs。 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的适当时间,以便它在标记呈现并设置参考值(应该?)后立即运行。

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

Create refs using memoisation, like:使用 memoisation 创建 refs,例如:

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

Then render them like:然后像这样渲染它们:

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

And use the refs for calling imperative functions like:并使用 refs 调用命令式函数,例如:

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

See the working code with mocked Marker : https://codesandbox.io/s/muddy-bush-gfd82使用模拟Marker查看工作代码: https://codesandbox.io/s/muddy-bush-gfd82

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM