Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Emoji-Picker-App in React Native

Emoji-Picker-App

~11 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026

Eine Emoji-Auswahl (wie in Chat-Apps) verbindet Kategorien-Tabs, ein gefiltertes Raster und einen "zuletzt verwendet"-Verlauf – ein kompaktes, aber vollständiges Beispiel für kategorisierte Rasteransichten.

1. Beschreibung

Emojis sind ganz normale Unicode-ZEICHEN, keine Bilder – sie werden als Text gerendert wie jeder andere String. Die App organisiert eine feste Emoji-Liste nach Kategorie, zeigt sie in einem Raster (numColumns) und merkt sich kürzlich ausgewählte Emojis in einer separaten Liste.

2. Kurzes Beispiel

<TouchableOpacity onPress={() => onAuswahl('????')}>
  <Text style={{ fontSize: 28 }}>????</Text>
</TouchableOpacity>

3. Komplettes Projekt: Emoji-Picker mit Verlauf

npx create-expo-app emoji-picker-app
cd emoji-picker-app
daten/emojis.js
export const EMOJI_KATEGORIEN = [
  {
    name: 'Smileys',
    emojis: ['????', '????', '????', '????', '????', '????', '????', '????', '????', '????', '????', '????'],
  },
  {
    name: 'Tiere',
    emojis: ['????', '????', '????', '????', '????', '????', '????', '????', '????', '????', '????', '????'],
  },
  {
    name: 'Essen',
    emojis: ['????', '????', '????', '????', '????', '????', '????', '????', '????', '????', '????', '????'],
  },
];
App.js
import { useState } from 'react';
import { View, Text, TouchableOpacity, FlatList, StyleSheet } from 'react-native';
import { EMOJI_KATEGORIEN } from './daten/emojis';

const MAX_VERLAUF = 12;

export default function App() {
  const [kategorieIndex, setKategorieIndex] = useState(0);
  const [ausgewaehlt, setAusgewaehlt] = useState(null);
  const [verlauf, setVerlauf] = useState([]);

  function emojiAuswaehlen(emoji) {
    setAusgewaehlt(emoji);
    setVerlauf((liste) => {
      const ohneDuplikat = liste.filter((e) => e !== emoji);
      return [emoji, ...ohneDuplikat].slice(0, MAX_VERLAUF);
    });
  }

  const aktuelleKategorie = EMOJI_KATEGORIEN[kategorieIndex];

  return (
    <View style={styles.container}>
      <View style={styles.vorschau}>
        <Text style={styles.vorschauEmoji}>{ausgewaehlt ?? '❔'}</Text>
        <Text style={styles.vorschauText}>
          {ausgewaehlt ? 'Ausgewähltes Emoji' : 'Noch nichts ausgewählt'}
        </Text>
      </View>

      {verlauf.length > 0 && (
        <View style={styles.verlaufBereich}>
          <Text style={styles.verlaufTitel}>Zuletzt verwendet</Text>
          <FlatList
            horizontal
            data={verlauf}
            keyExtractor={(item, index) => `${item}-${index}`}
            showsHorizontalScrollIndicator={false}
            renderItem={({ item }) => (
              <TouchableOpacity onPress={() => emojiAuswaehlen(item)} style={styles.verlaufZelle}>
                <Text style={styles.emojiText}>{item}</Text>
              </TouchableOpacity>
            )}
          />
        </View>
      )}

      <View style={styles.tabZeile}>
        {EMOJI_KATEGORIEN.map((kategorie, index) => (
          <TouchableOpacity
            key={kategorie.name}
            style={[styles.tab, index === kategorieIndex && styles.tabAktiv]}
            onPress={() => setKategorieIndex(index)}
          >
            <Text style={[styles.tabText, index === kategorieIndex && styles.tabTextAktiv]}>
              {kategorie.name}
            </Text>
          </TouchableOpacity>
        ))}
      </View>

      <FlatList
        key={kategorieIndex}
        data={aktuelleKategorie.emojis}
        numColumns={6}
        keyExtractor={(item) => item}
        renderItem={({ item }) => (
          <TouchableOpacity style={styles.rasterZelle} onPress={() => emojiAuswaehlen(item)}>
            <Text style={styles.emojiText}>{item}</Text>
          </TouchableOpacity>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, paddingTop: 60, paddingHorizontal: 16 },
  vorschau: { alignItems: 'center', marginBottom: 16 },
  vorschauEmoji: { fontSize: 48 },
  vorschauText: { color: '#6b7280', marginTop: 4 },
  verlaufBereich: { marginBottom: 16 },
  verlaufTitel: { color: '#6b7280', marginBottom: 6, fontSize: 13 },
  verlaufZelle: { marginRight: 12 },
  tabZeile: { flexDirection: 'row', marginBottom: 8 },
  tab: { flex: 1, paddingVertical: 8, borderBottomWidth: 2, borderBottomColor: 'transparent', alignItems: 'center' },
  tabAktiv: { borderBottomColor: '#2563eb' },
  tabText: { color: '#6b7280' },
  tabTextAktiv: { color: '#2563eb', fontWeight: 'bold' },
  rasterZelle: { flex: 1, aspectRatio: 1, alignItems: 'center', justifyContent: 'center' },
  emojiText: { fontSize: 28 },
});

4. Erklärung

  • verlauf nutzt filter + slice(0, MAX_VERLAUF) statt einfachem Anhängen – so wandert ein BEREITS im Verlauf vorhandenes Emoji beim erneuten Antippen an den ANFANG statt sich zu duplizieren, und die Liste wächst nie über 12 Einträge hinaus.
  • key={{kategorieIndex}} auf der Raster-FlatList zwingt React, die Liste beim Kategoriewechsel komplett neu aufzubauen – wichtig, damit die Scroll-Position beim Wechseln der Kategorie zurückgesetzt wird.
  • Die horizontale Verlaufsliste (horizontal auf FlatList) und das vertikale Emoji-Raster (numColumns={{6}}) sind zwei UNTERSCHIEDLICHE Konfigurationen DERSELBEN Komponente – zeigt, wie flexibel FlatList für verschiedene Layouts einsetzbar ist.
  • Emojis als Text zu rendern hat den Vorteil, dass Größe/Farbe wie bei jedem anderen Text über fontSize gesteuert werden – kein Bild-Loading, keine Netzwerk-Anfrage nötig.

5. Ausgaben

Ausgabe
Oben eine große Vorschau ("❔" initial, Text "Noch nichts ausgewählt"), darunter (nach der ersten Auswahl) eine horizontal scrollbare "Zuletzt verwendet"-Leiste, darunter drei Tabs ("Smileys" aktiv/blau unterstrichen), darunter ein 6-spaltiges Emoji-Raster. Antippen eines Emojis vergrößert es in der Vorschau und fügt es vorne im Verlauf ein.