aachh
aachh

Reputation: 103

"Error: you attempted to set the key `latitude` with the value `37.785834` on an object that is meant to be immutable and has been frozen."

I'm using the navigator.geolocation API in React Native to set the user coordinates as part of the component state, which the MapView is supposed to use as the region prop, and running getCurrentPosition() throws an error.

I'm not sure how to fix this. The error says this is an immutability issue, although I made sure I used let and const where they're supposed to be.

This is my initial state:

this.state = {
      data: {
        name: "Nie powinieneś tego widzieć.",
        address: "Wyślij zrzut ekranu tego widoku do nas na stronie dokosciola.pl w zakładce Kontakt.",
        url: "https://dokosciola.pl",
        coords: {
          latitude: undefined,
          longitude: undefined,
          latitudeDelta: 0.00922 * 1.5,
          longitudeDelta: 0.00421 * 1.5
        },
        hours: ["8:00", "10:00", "12:00"]
      }
};

This is how I use the geolocating API:

locateUser = () => {
    navigator.geolocation.getCurrentPosition(
      position => {
        let state = this.state;
        state.data.coords.latitude = position.coords.latitude;
        state.data.coords.longitude = position.coords.longitude;
        this.setState(state);
        console.log(this.state);
      },
      error => Alert.alert("Ups!", `Wystąpił wewnętrzny błąd:\n${error}`),
      { enableHighAccuracy: true, timeout: 30000, maximumAge: 5000 }
    );
};

I run the locateUser() function before mounting the app, so:

componentWillMount() {
    this.locateUser();
}

This is how I use the MapView component from react-native-maps:

<View style={{ flex: 1 }}>
        <MapView
          style={{ flex: 1 }}
          initialRegion={this.state.data.coords}
          region={this.state.data.coords}
          mapType={"mutedStandard"}
        >
          {data.map(element => {
            return (
              <ChurchMarker
                coords={element.coords}
                key={element.id}
                onPress={() => this.update(element)}
              />
            );
          })}
        </MapView>
</View>

ChurchMarker is a precooked Marker, also from react-native-maps, and data - a simple array of objects, mocking a potential API response:

[
    {
        id: string,
        name: string,
        address: string,
        url: string,
        coords: {
            latitude: number,
            longitude: number
        },
        hours: string[]
    },
    ...
]

I expect the MapView to focus on the user coordinates as the app mounts, but the error I specified in locateUser() executes, with the following message:

Error: you attempted to set the key `latitude` with the value `37.375834` on an object that is meant to be immutable and has been frozen.

After that there's also a warning:

Warning: Failed prop type: The prop `region.latitude` is marked as required in `MapView`, but its value is `undefined`.

and the same for the longtitude. This means the state didn't update. Any fixes to this? What am I implementing wrong?

Upvotes: 0

Views: 1270

Answers (2)

samzmann
samzmann

Reputation: 2566

Your issue is clearly described in the error message: you attempted to set a key on an object that is meant to be immutable.

When you write let state = this.state, you are creating a reference to this.state, which is immutable.

So when you try to assign a new value to state.data.coords.latitude you are mutating the state object, which is illegal.

Instead you should refactor your code in two steps:

  1. Simplify the state object to reduce nesting (get rid of the data layer):
this.state = {
    name: "Nie powinieneś tego widzieć.",
    address: "Wyślij zrzut ekranu tego widoku do nas na stronie dokosciola.pl w zakładce Kontakt.",
    url: "https://dokosciola.pl",
    coords: {
      latitude: undefined,
      longitude: undefined,
      latitudeDelta: 0.00922 * 1.5,
      longitudeDelta: 0.00421 * 1.5
    },
    hours: ["8:00", "10:00", "12:00"]
}
  1. Create a newCoords object and set it with the correct values, then overwrite state.coords by using setState:
locateUser = () => {
    navigator.geolocation.getCurrentPosition(
      position => {
        const newCoords = {}
        newCoords.latitude = position.coords.latitude
        newCoords.longitude = position.coords.longitude
        this.setState({ coords: newCoords })
      },
      error => Alert.alert("Ups!", `Wystąpił wewnętrzny błąd:\n${error}`),
      { enableHighAccuracy: true, timeout: 30000, maximumAge: 5000 }
    );
};

Upvotes: 0

Robert Hovhannisyan
Robert Hovhannisyan

Reputation: 3335

You must always update Your state with the object way. In Your case You can try this:

locateUser = () => {
    navigator.geolocation.getCurrentPosition(
      position => {
        this.setState({
         data.coords.latitude: position.coords.latitude,
         data.coords.longitude: position.coords.longitude 
       });
       console.log(this.state)
      },
      error => Alert.alert("Ups!", `Wystąpił wewnętrzny błąd:\n${error}`),
      { enableHighAccuracy: true, timeout: 30000, maximumAge: 5000 }
    );
};

Or if for the new state You want to manipulate the current state You must use the functional way of this.setState

For more information read this part of docs.

Good Luck :)

Upvotes: 1

Related Questions