React Native Academy · Lektion

Håndtering af komponenttilstand med useState

Introducér useState-hooket, gem en tæller eller en til/fra-værdi i state, og udløs nye renders ved at kalde state-setterfunktionen.

Lektion 2 af 413 trin

Håndtering af komponenttilstand med useState er en gratis React Native Academy-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i React Native Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. React Native Academy-kurset indeholder 4 lektioner i alt.

Hvad er state?

State er data, der kan ændre sig over tid, og som en komponent selv ejer og administrerer internt. I modsætning til props (som kommer fra den overordnede komponent og er skrivebeskyttede) oprettes og opdateres state af komponenten selv. Når state ændres, renderer React automatisk komponenten igen, så de nye værdier afspejles i brugergrænsefladen. Almindelige eksempler på state er, om en modal er åben, den tekst brugeren har skrevet, den aktuelt valgte fane eller elementer, der er hentet fra en API. State er det, der gør React-komponenter interaktive.

import { View, Text, TouchableOpacity } from 'react-native';
import { useState } from 'react';

export default function Counter() {
  // Declare state: count starts at 0
  const [count, setCount] = useState(0);

  return (
    <View style={{ padding: 24, alignItems: 'center' }}>
      <Text style={{ fontSize: 48, fontWeight: 'bold' }}>{count}</Text>
      <TouchableOpacity
        onPress={() => setCount(count + 1)}
        style={{ marginTop: 16, padding: 12, backgroundColor: '#4f86f7', borderRadius: 8 }}
      >
        <Text style={{ color: '#fff', fontSize: 18 }}>Increment</Text>
      </TouchableOpacity>
    </View>
  );
}

useState-hookets signatur

useState er et React-hook, som du kalder på øverste niveau i din komponentfunktion. Det tager ét argument — den indledende state-værdi — og returnerer et array med to elementer: den aktuelle state-værdi og en setter-funktion. Du destrukturerer disse ved hjælp af array-destrukturering. Navnekonventionen er [value, setValue]. Setter-funktionen er den, du kalder for at opdatere state — mutér aldrig state-variablen direkte. Hver state-variabel er uafhængig; kald useState flere gange for flere dele af state.

import { useState } from 'react';

function MyComponent() {
  // Syntax: const [value, setter] = useState(initialValue);
  const [count, setCount] = useState(0);          // number
  const [name, setName] = useState('');            // string
  const [isVisible, setIsVisible] = useState(false); // boolean
  const [items, setItems] = useState([]);          // array
  const [user, setUser] = useState(null);          // object or null

  // Update state by calling the setter:
  // setCount(5);          ← sets to 5
  // setName('Alice');     ← sets to 'Alice'
  // setIsVisible(true);   ← sets to true
}

State-opdateringer udløser nye renderingeringer

Når du kalder en state-setter, planlægger React en ny rendering af komponenten. Under den næste rendering returnerer useState den nye værdi. React håndterer dette effektivt — flere state-opdateringer, der sker i den samme eventhåndtering, samles, så der kun udføres én ny rendering. Det er også vigtigt, at state-værdien i den aktuelle rendering er uforanderlig. At kalde setCount(count + 1) ændrer ikke straks count under den aktuelle udførelse af funktionen — den nye værdi vises i den næste renderingcyklus.

import { useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';

export default function Example() {
  const [count, setCount] = useState(0);

  function handlePress() {
    // These do NOT stack: count is still 0 in this render
    setCount(count + 1); // schedules count = 1
    setCount(count + 1); // also schedules count = 1, not 2!

    // To correctly base on previous value, use functional update:
    // setCount(prev => prev + 1); // prev = 0, result = 1
    // setCount(prev => prev + 1); // prev = 1, result = 2
  }

  return (
    <TouchableOpacity onPress={handlePress}>
      <Text>{count}</Text>
    </TouchableOpacity>
  );
}

Funktionelle opdateringer med den tidligere state

Når din nye tilstand afhænger af den tidligere tilstand, skal du altid bruge formen med funktionel opdatering: setState(prev => prev + 1). React garanterer, at prev er den seneste tilstandsværdi, selv når flere opdateringer samles i én gruppe. Det er afgørende, når du øger tællere, skifter mellem booleske værdier eller tilføjer elementer til arrays hurtigt efter hinanden, f.eks. ved hurtige tryk på en knap. Formen med objektliteral (setState(state + 1)) kan give fejl, når flere opdateringer sker, før komponenten gengives igen.

import { useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';

export default function SafeCounter() {
  const [count, setCount] = useState(0);

  function increment() {
    // Safe: uses previous state, handles batching correctly
    setCount(prev => prev + 1);
  }

  function decrement() {
    setCount(prev => Math.max(0, prev - 1)); // min 0
  }

  function reset() {
    setCount(0); // direct value ok when not based on previous
  }

  return (
    <View style={{ alignItems: 'center', gap: 12, padding: 24 }}>
      <Text style={{ fontSize: 48 }}>{count}</Text>
      <TouchableOpacity onPress={increment}><Text>+</Text></TouchableOpacity>
      <TouchableOpacity onPress={decrement}><Text>-</Text></TouchableOpacity>
      <TouchableOpacity onPress={reset}><Text>Reset</Text></TouchableOpacity>
    </View>
  );
}

Skift af boolesk tilstand

Et meget almindeligt mønster er at skifte en boolesk tilstand mellem true og false. Brug formen med funktionel opdatering sammen med den logiske NOT-operator: setIsOpen(prev => !prev). Det er mere overskueligt end setIsOpen(!isOpen), fordi det håndterer hurtige tryk korrekt. Almindelige anvendelser er at vise eller skjule en modal, udvide eller skjule en sektion i et harmonikaelement, skifte mellem afspilning og pause eller skifte status for et favoritstjerneikon. Booleske værdier er måske den mest anvendte type komponenttilstand.

import { useState } from 'react';
import { View, Text, TouchableOpacity, Modal, StyleSheet } from 'react-native';

export default function ToggleModal() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.btn}
        onPress={() => setIsVisible(prev => !prev)}
      >
        <Text style={styles.btnText}>Toggle Modal</Text>
      </TouchableOpacity>

      <Modal visible={isVisible} transparent animationType='fade'>
        <View style={styles.overlay}>
          <Text style={styles.modalText}>Hello from the modal!</Text>
          <TouchableOpacity onPress={() => setIsVisible(false)}>
            <Text style={{ color: '#fff', marginTop: 16 }}>Close</Text>
          </TouchableOpacity>
        </View>
      </Modal>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  btn: { padding: 12, backgroundColor: '#4f86f7', borderRadius: 8 },
  btnText: { color: '#fff', fontWeight: 'bold' },
  overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center' },
  modalText: { color: '#fff', fontSize: 20, fontWeight: 'bold' },
});

Tilstand med strenge: Kontrolleret tekstindtastning

Når du binder en TextInput til en tilstand, opretter du en kontrolleret indtastning. Egenskaben value angiver den viste tekst fra tilstanden, og tilbagekaldet onChangeText opdaterer tilstanden, hver gang brugeren skriver. Dermed er tilstanden den eneste sandhedskilde for indtastningens værdi — du kan når som helst læse, validere eller transformere teksten ved at læse tilstandsvariablen. Uden en binding til tilstanden (ukontrolleret) skulle du bruge en ref for at læse indtastningens værdi, hvilket er mindre idiomatisk i React.

import { useState } from 'react';
import { View, Text, TextInput, StyleSheet } from 'react-native';

export default function SearchBox() {
  const [query, setQuery] = useState('');

  return (
    <View style={styles.container}>
      <TextInput
        value={query}
        onChangeText={setQuery} // same as: (text) => setQuery(text)
        placeholder='Search...'
        style={styles.input}
      />
      <Text style={styles.preview}>
        You typed: <Text style={{ fontWeight: 'bold' }}>{query}</Text>
      </Text>
      <Text style={styles.count}>{query.length} characters</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 16, gap: 8 },
  input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, fontSize: 16 },
  preview: { fontSize: 14, color: '#555' },
  count: { fontSize: 12, color: '#999' },
});

Tilstand med arrays: Tilføjelse og fjernelse af elementer

Når tilstanden er et array, må du aldrig ændre det direkte med push eller splice. Opret i stedet et nyt array med ændringen, og giv det til setter-funktionen. Sådan tilføjer du et element: setItems(prev => [...prev, newItem]). Sådan fjerner du et element: setItems(prev => prev.filter(item => item.id !== id)). Sådan opdaterer du et element: setItems(prev => prev.map(item => item.id === id ? {...item, done: true} : item)). Disse uforanderlige mønstre lader React registrere ændringer og planlægge effektive gengivelser igen.

import { useState } from 'react';
import { View, Text, TouchableOpacity, TextInput, FlatList } from 'react-native';

export default function TodoList() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');

  function addTodo() {
    if (!input.trim()) return;
    setTodos(prev => [...prev, { id: Date.now().toString(), text: input.trim(), done: false }]);
    setInput('');
  }

  function toggleDone(id) {
    setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));
  }

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <TextInput value={input} onChangeText={setInput} placeholder='Add a todo' />
      <TouchableOpacity onPress={addTodo}><Text>Add</Text></TouchableOpacity>
      <FlatList
        data={todos}
        keyExtractor={t => t.id}
        renderItem={({ item }) => (
          <TouchableOpacity onPress={() => toggleDone(item.id)}>
            <Text style={{ textDecorationLine: item.done ? 'line-through' : 'none' }}>
              {item.text}
            </Text>
          </TouchableOpacity>
        )}
      />
    </View>
  );
}

Tilstand med objekter

Gruppér relaterede dele af tilstanden i en enkelt tilstandsvariabel, der er et objekt. Når du opdaterer en objekttilstand, skal du sprede den tidligere tilstand for at bevare uændrede felter: setState(prev => ({ ...prev, name: 'Alice' })). Det overskriver kun feltet name, mens alle andre felter bevares. Du kan også lade hvert felt have sin egen useState — ingen af metoderne er universelt bedre. Brug et objekt, når felterne altid ændres sammen, f.eks. i en formular med navn, e-mail og adgangskode. Brug separate tilstande, når de ændres uafhængigt af hinanden.

import { useState } from 'react';
import { View, TextInput, Text, StyleSheet } from 'react-native';

export default function ProfileForm() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    bio: '',
  });

  function updateField(field, value) {
    setForm(prev => ({ ...prev, [field]: value }));
  }

  return (
    <View style={{ padding: 16, gap: 12 }}>
      <TextInput
        value={form.name}
        onChangeText={v => updateField('name', v)}
        placeholder='Name'
        style={styles.input}
      />
      <TextInput
        value={form.email}
        onChangeText={v => updateField('email', v)}
        placeholder='Email'
        style={styles.input}
      />
      <Text style={{ color: '#666' }}>Preview: {form.name} — {form.email}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12 },
});

Doven initial tilstand

Hvis det er dyrt at beregne den initiale tilstand, f.eks. ved at læse fra AsyncStorage eller fortolke et stort datasæt, skal du give en funktion til useState i stedet for en værdi: useState(() => computeExpensiveValue()). React kalder kun denne initialiseringsfunktion én gang ved komponentens første gengivelse, ikke ved hver efterfølgende gengivelse. Det kaldes doven initialisering. Hvis du giver en dyr beregning som en almindelig værdi (useState(expensiveCompute())), køres den ved hver gengivelse, hvilket udfører unødvendigt arbejde, selv om den initiale tilstand kun bruges én gang.

import { useState } from 'react';

// Without lazy init: computeExpensiveList() runs on EVERY render
const [list, setList] = useState(computeExpensiveList()); // BAD

// With lazy init: function is called ONCE on mount
const [list, setList] = useState(() => computeExpensiveList()); // GOOD

// Real example: JSON.parse is cheap, but still good practice:
const [settings, setSettings] = useState(() => {
  const stored = SomeStorage.get('settings');
  return stored ? JSON.parse(stored) : { theme: 'light', lang: 'en' };
});

Afledte værdier kontra overflødig tilstand

Gem ikke værdier i tilstanden, hvis de kan afledes af eksisterende tilstand eller egenskaber. Hvis du f.eks. har en liste over todoer i tilstanden, skal du ikke også gemme completedCount i tilstanden — beregn den under gengivelsen som todos.filter(t => t.done).length. Når afledte værdier gemmes i tilstanden, opstår der synkroniseringsfejl: Du glemmer at opdatere den ene, når du opdaterer den anden. Tilføj kun ting til tilstanden, som du ikke kan aflede, ikke kan beregne under gengivelsen, eller som angives asynkront, f.eks. data fra et API-svar. Denne regel holder tilstanden minimal og fri for fejl.

import { useState } from 'react';
import { Text, View } from 'react-native';

export default function TodoStats() {
  const [todos, setTodos] = useState([
    { id: '1', text: 'Buy groceries', done: true },
    { id: '2', text: 'Write code', done: false },
    { id: '3', text: 'Go for a walk', done: true },
  ]);

  // DERIVED: computed from existing state, not stored in state
  const total = todos.length;
  const completed = todos.filter(t => t.done).length;
  const remaining = total - completed;

  return (
    <View style={{ padding: 16 }}>
      <Text>Total: {total}</Text>
      <Text>Completed: {completed}</Text>
      <Text>Remaining: {remaining}</Text>
    </View>
  );
}

useState kontra useRef: Hvornår du ikke skal bruge tilstand

Ikke alle værdier, der ændrer sig i en komponent, behøver at være i tilstanden. Hvis en værdi ændrer sig, men ikke skal udløse en ny gengivelse, skal du gemme den i en useRef i stedet. Almindelige eksempler er et timer-id, der returneres fra setTimeout, et flag, der angiver, om en netværksanmodning er i gang, eller en egenskabs tidligere værdi. Hvis du lægger disse værdier i tilstanden, udløser det unødvendige gengivelser. En useRef bevarer sin værdi på tværs af gengivelser ligesom tilstanden, men en opdatering af den planlægger ikke en ny gengivelse. Tommelfingerreglen er: Hvis brugergrænsefladen ikke skal ændres, når værdien ændres, skal du bruge useRef.

import { useState, useRef, useEffect } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';

export default function Timer() {
  const [elapsed, setElapsed] = useState(0);
  const intervalRef = useRef(null); // timer ID in ref — no re-render needed

  function start() {
    if (intervalRef.current) return; // already running
    intervalRef.current = setInterval(() => {
      setElapsed(prev => prev + 1); // state — needs re-render to show
    }, 1000);
  }

  function stop() {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
  }

  useEffect(() => () => clearInterval(intervalRef.current), []); // cleanup

  return (
    <View style={{ alignItems: 'center', padding: 24, gap: 16 }}>
      <Text style={{ fontSize: 48 }}>{elapsed}s</Text>
      <TouchableOpacity onPress={start}><Text>Start</Text></TouchableOpacity>
      <TouchableOpacity onPress={stop}><Text>Stop</Text></TouchableOpacity>
    </View>
  );
}

Hurtig kontrol

Test din forståelse af begreberne inden for React Native Mobile Development fra denne lektion.

Opsummering af lektionen

I denne lektion har du lært, at useState erklærer tilstandsvariabler og leverer en setter-funktion, der udløser nye gengivelser, at funktionelle opdateringer sikkert udleder en ny tilstand fra den tidligere værdi, og at afledte værdier skal beregnes under gengivelsen i stedet for at blive gemt som overflødig tilstand. Dernæst undersøger vi, hvordan du løfter tilstanden op for at dele data mellem søskendekomponenter.

Gratis at komme i gang

Lær JavaScript med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
30
Lektioner
120

Ofte stillede spørgsmål

Er lektionen “Håndtering af komponenttilstand med useState” gratis?

Ja — alle 3 lektioner i læringssporet React Native Academy, inklusive “Håndtering af komponenttilstand med useState”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. React Native Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Håndtering af komponenttilstand med useState”?

Introducér useState-hooket, gem en tæller eller en til/fra-værdi i state, og udløs nye renders ved at kalde state-setterfunktionen. Du øver dig i React Native Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på React Native Academy?

Der kræves ingen tidligere erfaring. React Native Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.

Hvor lang tid tager lektionen “Håndtering af komponenttilstand med useState”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne React Native Academy-lektion?

Ja. Alle React Native Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Videregivelse af data med props
  2. Håndtering af komponenttilstand med useState
  3. Flytning af state op
  4. Opbygning af en interaktiv tællerapp
← Tilbage til React Native Academy