bruh
bruh

Reputation: 2295

React Native setting keys on elements

I'm trying to get AsyncStorage working in react-native, just to save some user inputted data. Although I've found some help through online resources and the SO community, this stuff is driving me crazy.

Even the React docs seem flawed. The code below is taken straight from the AsyncStorage page of the react docs and pasted into a new, clean application (I've added the final line of code to register the app, it won't work without it).

The big block of code below is posted as reference. I believe the important bit is this line:

{this.state.messages.map(function(m) {
    return <Text key={m}>{m}</Text>;
})}

I get unique key errors, although I have a key set.

'use strict';

var React = require('react');
var ReactNative = require('react-native');
var {
  AppRegistry,
  AsyncStorage,
  PickerIOS,
  Text,
  View
} = ReactNative;
var PickerItemIOS = PickerIOS.Item;

var STORAGE_KEY = '@AsyncStorageExample:key';
var COLORS = ['red', 'orange', 'yellow', 'green', 'blue'];

var BasicStorageExample = React.createClass({
  componentDidMount() {
    this._loadInitialState().done();
  },

  async _loadInitialState() {
    try {
      var value = await AsyncStorage.getItem(STORAGE_KEY);
      if (value !== null){
        this.setState({selectedValue: value});
        this._appendMessage('Recovered selection from disk: ' + value);
      } else {
        this._appendMessage('Initialized with no selection on disk.');
      }
    } catch (error) {
      this._appendMessage('AsyncStorage error: ' + error.message);
    }
  },

  getInitialState() {
    return {
      selectedValue: COLORS[0],
      messages: [],
    };
  },

  render() {
    var color = this.state.selectedValue;
    return (
      <View>
        <PickerIOS
          selectedValue={color}
          onValueChange={this._onValueChange}>
          {COLORS.map((value) => (
            <PickerItemIOS
              key={value}
              value={value}
              label={value}
            />
          ))}
        </PickerIOS>
        <Text>
          {'Selected: '}
          <Text style={{color}}>
            {this.state.selectedValue}
          </Text>
        </Text>
        <Text>{' '}</Text>
        <Text onPress={this._removeStorage}>
          Press here to remove from storage.
        </Text>
        <Text>{' '}</Text>
        <Text>Messages:</Text>
        {this.state.messages.map(function(m) {
          return <Text key={m}>{m}</Text>;
        })}
      </View>
    );
  },

  async _onValueChange(selectedValue) {
    this.setState({selectedValue});
    try {
      await AsyncStorage.setItem(STORAGE_KEY, selectedValue);
      this._appendMessage('Saved selection to disk: ' + selectedValue);
    } catch (error) {
      this._appendMessage('AsyncStorage error: ' + error.message);
    }
  },

  async _removeStorage() {
    try {
      await AsyncStorage.removeItem(STORAGE_KEY);
      this._appendMessage('Selection removed from disk.');
    } catch (error) {
      this._appendMessage('AsyncStorage error: ' + error.message);
    }
  },

  _appendMessage(message) {
    this.setState({messages: this.state.messages.concat(message)});
  },
});

exports.title = 'AsyncStorage';
exports.description = 'Asynchronous local disk storage.';
exports.examples = [
  {
    title: 'Basics - getItem, setItem, removeItem',
    render(): ReactElement { return <BasicStorageExample />; }
  },
];

AppRegistry.registerComponent('BasicStorageExample', () => BasicStorageExample);

The above code works until you select an element that you have selected before. Then you're riddled with console errors about unique child keys. I haven't been able to figure out how to avoid this.

Console errors read:

index.ios.bundle:24458Warning: flattenChildren(...): Encountered two children with the same key, '6:$Saved selection to disk: orange'. Child keys must be unique; when two children share a key, only the first child will be used.

Upvotes: 2

Views: 10015

Answers (2)

Alon Gubkin
Alon Gubkin

Reputation: 57119

If you use the message text as the key, and you have two messages with the same text, then the key is not unique. You should use the array index as a key:

{this.state.messages.map((message, i) => (
  <Text key={i}>
    {message}
  </Text>
)}

As a side note, you should make the following changes to your code:

  • Use the new React ES6 syntax instead of the old React.createClass
  • Use ES6 import instead of require()
  • 'use strict' is completely redundant in ES6 because it's strict by default
  • This is much more opinionated but you should use ESLint with something like AirBnb JS style guide. It integrates smoothly to Atom using AtomLinter.
  • But kudos on the async/await! :)

Upvotes: 3

Jake Haller-Roby
Jake Haller-Roby

Reputation: 6427

Leverage the index, rather than the value, within your map function.

{this.state.messages.map(function(m, i) {
      return <Text key={'message-' + i}>{m}</Text>;
})}

Upvotes: 3

Related Questions