Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subview flickering on children index changes #5021

Closed
night opened this issue Apr 1, 2024 · 3 comments
Closed

Subview flickering on children index changes #5021

night opened this issue Apr 1, 2024 · 3 comments
Labels
bug Something isn't working invalid This doesn't seem right wontfix

Comments

@night
Copy link

night commented Apr 1, 2024

Summary

When using react-native-maps with dynamic/changing Marker children arrays, memoization appears not enough to prevent render flicker.

I suspect from reading the native source code that this may be caused by annotations being added and removed on the native side, instead of preserving existing annotations added. More specifically, I think on the native side you may be receiving insertReactSubview and removeReactSubview when indexes change for a subview, even when that subview's reference is not changing.

After trying to debug and work around this issue, I found that explicitly passing an array of children at fixed indices, I can add and remove children without seeing subview flicker.

Ref: #5014

Reproducible sample code

import React, {useEffect, useState} from 'react';
import MapView, {Marker} from 'react-native-maps';

function shuffleArray(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

export default function App() {
  const [markers, setMarkers] = useState([
    {latitude: 37.8067698, longitude: -122.4578827},
    {latitude: 37.7735338, longitude: -122.4635475},
    {latitude: 37.7552137, longitude: -122.4405449},
    {latitude: 37.8047354, longitude: -122.4206322},
    {latitude: 37.7858175, longitude: -122.5035452},
  ]);

  useEffect(() => {
    setInterval(() => {
      // if you comment out the following shuffleArray line, the flickering stops.
      shuffleArray(markers);
      setMarkers([...markers]);
    }, 1000);
  }, []);

  return (
    <MapView
      style={{flex: 1}}
      googleRenderer="LEGACY"
      initialRegion={{
        longitude: -122.4194,
        latitude: 37.7749,
        latitudeDelta: 0.1,
        longitudeDelta: 0.2,
      }}>
      {markers.map((coordinate) => (
        <Marker
          key={`marker_${coordinate.latitude}_${coordinate.longitude}`}
          coordinate={coordinate}
          tracksViewChanges={false}
        />
      ))}
    </MapView>
  );
}
RPReplay_Final1711993263.MP4

Steps to reproduce

Load the sample code provided. Notice that the library re-renders Markers with a flicker instead of keeping the existing Markers rendered.

Expected result

Children should be able to be resorted without subview re-render.

Actual result

Subviews are re-rendered.

React Native Maps Version

1.13.0

What platforms are you seeing the problem on?

iOS (Apple Maps)

React Native Version

0.73.4

What version of Expo are you using?

SDK 50

Device(s)

iPhone 15 Pro (17.3.1)

Additional information

No response

@night night added the bug Something isn't working label Apr 1, 2024
@yanksyoon
Copy link

yanksyoon commented Apr 6, 2024

Hi, you should probably use a ref and use a forced rerender to not assign a whole new array which causes the markers to flicker when re-rendering.

const [ignored, forceUpdate] = useReducer((x) => x + 1, 0);
const markers = useRef([])

useEffect(() => {
  markers.current[<index_to_update>] = <new marker>
  forceUpdate()
}, [])

@Neiso
Copy link

Neiso commented May 19, 2024

Seems like a duplicate of #4988 for iOS ? I'm experiencing the same problem

@salah-ghanim
Copy link
Collaborator

@night @Neiso I was actually curious to analyze this bug and it took me too long to figure out what's going on.

Long story short is that this is not a bug per say, rather an expectation of a feature that we don't have.

specifically react-native does not support shuffling of children in a parent container without re-rendering the subviews, the DOM tree changes with the reshuffling and that triggers a re-render, you can easily test that by creating a custom react-native view and add some child views to it and shuffle them, you will see insertReactSubview and removeReactSubview being called.

I then looked into FlatList Implementation to find out that there is handling via keyExtractor to practically keep children ordered regardless of the input based on key, that way the list doesn't refresh content when they're shuffled, at least that's my current understanding.

but to get back to the problem you have, please look into @yanksyoon idea, or even better try to use Memorize it works well for blocking re-renders.

so this won't be considered as a bug, but feel free to add a suggestion for a new feature that makes it possible for MapView to keep track of markers by ID. here is the discussion we have for the roadmap: #4975

@salah-ghanim salah-ghanim added invalid This doesn't seem right wontfix labels May 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working invalid This doesn't seem right wontfix
Projects
None yet
Development

No branches or pull requests

4 participants