React Native Academy · Lektion

Lyfta upp tillstånd

Flytta delat tillstånd till närmaste gemensamma förfader och skicka både värdet och en callback för uppdatering som props, så att syskonkomponenter hålls synkroniserade.

Lektion 3 av 413 steg

Lyfta upp tillstånd är en gratis lektion i React Native Academy på CoddyKit. Detta är lektion 3 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för React Native Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i React Native Academy innehåller totalt 4 lektioner.

Problemet: syskon behöver delat tillstånd

Föreställ dig två syskonkomponenter som behöver dela en del data — till exempel en komponent för sökinmatning och en resultatlista som filtreras utifrån sökfrågan. Om söktillståndet finns inuti sökinmatningskomponenten har resultatlistan inget sätt att komma åt det. Ingen av syskonkomponenterna kan se den andras tillstånd direkt. Lösningen är att lyfta tillståndet till närmaste gemensamma förälder — den föräldrakomponent som renderar båda syskonen. Föräldern skickar sedan tillståndsvärdet till den ena komponenten och setter-callbacken till den andra.

// PROBLEM: SearchInput holds state that ResultsList needs
// but siblings can't communicate directly

function SearchInput() {
  const [query, setQuery] = useState(''); // stuck here
  return <TextInput value={query} onChangeText={setQuery} />;
}

function ResultsList() {
  // How do we access 'query' from the sibling? We can't!
  return <FlatList data={/* needs query */} />;
}

Lyfta tillståndet till den gemensamma föräldern

Flytta det delade tillståndet till den närmaste gemensamma föräldern — komponenten som renderar båda syskonen. Föräldern äger nu tillståndet och skickar ned det som props: sökfrågan går till resultatlistan och setterfunktionen går till sökinmatningen. Syskonen kommunicerar genom föräldern: sökinmatningen anropar setterfunktionen (som skickats som en prop), vilket uppdaterar förälderns tillstånd. Det nya frågevärdet skickas sedan vidare till resultatlistan som en ny prop. Detta är det grundläggande dataflödesmönstret i React.

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

// Parent owns the shared state
export default function SearchScreen() {
  const [query, setQuery] = useState('');
  const results = ITEMS.filter(item =>
    item.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <SearchInput query={query} onQueryChange={setQuery} />
      <ResultsList results={results} />
    </View>
  );
}

const ITEMS = ['Apple', 'Banana', 'Blueberry', 'Cherry', 'Avocado'];

Skriva underordnade komponenter

När tillståndet har lyfts blir de underordnade komponenterna enkla och fokuserade. Komponenten SearchInput tar emot query (det aktuella värdet) och onQueryChange (för att uppdatera det) som props. ResultsList tar emot arrayen med filtrerade resultat. Ingen av de underordnade komponenterna hanterar tillstånd — de är kontrollerade komponenter vars beteende helt styrs av deras props. Det gör båda komponenterna enkla att testa isolerat: skicka bara in props och kontrollera den renderade utdatan.

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

// Controlled: receives value and updater as props
function SearchInput({ query, onQueryChange }) {
  return (
    <TextInput
      value={query}
      onChangeText={onQueryChange}
      placeholder='Search...'
      style={styles.input}
    />
  );
}

// Pure display: receives filtered data as prop
function ResultsList({ results }) {
  return (
    <FlatList
      data={results}
      keyExtractor={item => item}
      renderItem={({ item }) => (
        <Text style={styles.item}>{item}</Text>
      )}
    />
  );
}

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

Hitta den närmaste gemensamma föräldern

Den centrala frågan när du lyfter tillstånd är: "Vilken är den lägsta komponenten i trädet som renderar alla komponenter som behöver detta tillstånd?" Lyft tillståndet exakt till den komponenten — inte högre. Om du lyfter det för högt (till exempel hela vägen till App) orsakar du onödiga omrenderingar av orelaterade komponenter. Den närmaste gemensamma föräldern ger rätt balans mellan åtkomst (alla användare kan ta emot tillståndet som props) och prestanda (endast det underträd som behöver tillståndet renderas om). Rita upp komponentträdet och följ vägen uppåt från varje användare för att hitta var vägarna sammanfaller.

// Component tree:
// App
// └── TabBar (selected tab)
//     ├── HomeTab
//     │   └── Feed
//     └── ProfileTab
//
// If 'selected tab' is needed by TabBar AND by Feed to filter content,
// lift it to TabBar (nearest common ancestor of both).
// DO NOT lift all the way to App unnecessarily.

function TabBar() {
  const [activeTab, setActiveTab] = useState('home');
  return (
    <View>
      <TabButtons activeTab={activeTab} onChange={setActiveTab} />
      {activeTab === 'home' ? <HomeTab /> : <ProfileTab />}
    </View>
  );
}

Flera syskon som delar tillstånd

När tre eller fler syskon behöver samma tillstånd lyfter du det en gång till deras gemensamma förälder och skickar det till varje syskon som behöver det. En produkts informationsskärm kan till exempel ha en ImageGallery, en VariantPicker och en AddToCartButton. Tillståndet för den valda varianten finns i den överordnade skärmkomponenten och flödar till alla tre. VariantPicker får setterfunktionen för att ändra valet. ImageGallery visar bilder för den valda varianten. AddToCartButton lägger till den specifikt valda varianten i kundvagnen.

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

const VARIANTS = ['Red / S', 'Red / M', 'Blue / S', 'Blue / M'];

export default function ProductDetailScreen() {
  const [selectedVariant, setSelectedVariant] = useState(VARIANTS[0]);

  return (
    <View style={{ flex: 1 }}>
      <ImageGallery variant={selectedVariant} />
      <VariantPicker
        variants={VARIANTS}
        selected={selectedVariant}
        onSelect={setSelectedVariant}
      />
      <AddToCartButton variant={selectedVariant} />
    </View>
  );
}

Callback-props kontra delat tillstånd

Ibland delar komponenter en åtgärd i stället för ett tillståndsvärde. Ett vanligt exempel är en listskärm med en kundvagnsikon i sidhuvudet — när du trycker på "Add to Cart" för ett listobjekt ska antalet i sidhuvudets märke uppdateras. Både listobjekten och sidhuvudet är syskon under samma förälderskärm. Föräldern lagrar antalet i kundvagnen i tillståndet, skickar antalet till sidhuvudet via props och skickar en onAddToCart-callback till listan. Objekten anropar callbacken, föräldern uppdaterar sitt tillstånd och sidhuvudets märke renderas om med det nya antalet.

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

export default function ShopScreen() {
  const [cartCount, setCartCount] = useState(0);

  function addToCart(item) {
    // Business logic lives in the parent
    setCartCount(prev => prev + 1);
    console.log('Added:', item.name);
  }

  return (
    <View style={{ flex: 1 }}>
      {/* Sibling 1: displays count from parent state */}
      <ShopHeader cartCount={cartCount} />
      {/* Sibling 2: triggers parent state update */}
      <ProductList onAddToCart={addToCart} />
    </View>
  );
}

När det blir prop-borrning att lyfta tillstånd

Om den gemensamma föräldern ligger många nivåer ovanför komponenterna som behöver tillståndet får du prop-borrning — props skickas genom många mellanliggande komponenter som inte använder dem. Om den gemensamma föräldern till exempel är App-roten och det finns fem komponentnivåer mellan App och användarna blir varje mellanliggande komponent en budbärare. Lösningen för delning på djupet är Context API (för data som många komponenter behöver) eller ett bibliotek för tillståndshantering som Zustand eller Redux. Tumregeln är: lyft tillståndet, men om det passerar mer än 2–3 nivåer utan att användas på vägen bör du överväga Context i stället.

// When prop drilling gets too deep, switch to Context
// Instead of:
function App() {
  const [user, setUser] = useState(null);
  return <Nav user={user}>   // not used
    <Drawer user={user}>     // not used
      <Screen user={user}>  // not used
        <ProfileCard user={user} />  // finally used
      </Screen>
    </Drawer>
  </Nav>;
}

// Use Context:
const UserContext = React.createContext(null);
function App() {
  const [user, setUser] = useState(null);
  return (
    <UserContext.Provider value={user}>
      <Nav><Drawer><Screen><ProfileCard /></Screen></Drawer></Nav>
    </UserContext.Provider>
  );
}

Praktiskt exempel: temperaturkonverterare

En klassisk demonstration av att lyfta tillstånd är två temperaturinmatningsfält (Celsius och Fahrenheit) som hålls synkroniserade. Varje inmatningsfält tar emot den aktuella temperaturen och en onChange-callback. Föräldern lagrar temperaturen i en enhet (exempelvis Celsius) och konverterar den för det andra inmatningsfältet. När något av fälten ändras konverterar föräldern värdet och uppdaterar sitt tillstånd. Båda fälten tar emot rätt värde från förälderns tillstånd, så att de hålls perfekt synkroniserade genom det delade tillståndet hos föräldern.

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

function TempInput({ label, value, onChangeText }) {
  return (
    <View style={styles.row}>
      <Text style={styles.label}>{label}</Text>
      <TextInput
        value={value}
        onChangeText={onChangeText}
        keyboardType='decimal-pad'
        style={styles.input}
      />
    </View>
  );
}

export default function TemperatureConverter() {
  const [celsius, setCelsius] = useState('');

  const fahrenheit = celsius !== '' ? (parseFloat(celsius) * 9/5 + 32).toFixed(1) : '';

  function handleFahrenheitChange(f) {
    const c = f !== '' ? ((parseFloat(f) - 32) * 5/9).toFixed(1) : '';
    setCelsius(c);
  }

  return (
    <View style={{ padding: 24 }}>
      <TempInput label='Celsius' value={celsius} onChangeText={setCelsius} />
      <TempInput label='Fahrenheit' value={fahrenheit} onChangeText={handleFahrenheitChange} />
    </View>
  );
}

const styles = StyleSheet.create({
  row: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 },
  label: { width: 100, fontSize: 16 },
  input: { flex: 1, borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 10 },
});

Placera tillstånd nära den plats där det används

Motsvarigheten till att lyfta tillstånd är att hålla tillståndet så långt ner i komponentträdet som möjligt. Om bara en komponent behöver en del av tillståndet ska du behålla det i den komponenten — lyft det inte i onödan. En rullgardinsmenys öppna/stängda-tillstånd behöver till exempel inte finnas i förälderskärmen om inte föräldern behöver reagera på det. Om du håller lokalt tillstånd lokalt förbättras prestandan (färre komponenter renderas om) och koden blir enklare att förstå (tillståndet och användargränssnittet som använder det finns på samma plats). Lyft endast tillståndet när två eller fler komponenter verkligen behöver samma data.

// Local state: Accordion open/close is private to Accordion
function Accordion({ title, children }) {
  const [open, setOpen] = useState(false); // stays local — parent doesn't care
  return (
    <View>
      <TouchableOpacity onPress={() => setOpen(prev => !prev)}>
        <Text>{title} {open ? '▲' : '▼'}</Text>
      </TouchableOpacity>
      {open && <View style={{ paddingLeft: 16 }}>{children}</View>}
    </View>
  );
}

// Only lift open state to parent IF the parent needs to:
// - Know which accordion is open to close others
// - Save/restore the open state on navigation
// Otherwise, keep it local!

Okontrollerade kontra kontrollerade komponenter

En komponent är kontrollerad när dess tillstånd helt styrs av props (och ändras via callback-funktioner) — som i exemplet med sökinmatningen. Den är okontrollerad när den hanterar sitt eget interna tillstånd utan att exponera det för föräldern. I React använder du oftast kontrollerade komponenter eftersom de är förutsägbara och enkla att testa. Okontrollerade inmatningar (där ref används för att läsa värden) används ibland av prestandaskäl (för att undvika omrenderingar vid varje knapptryckning) eller för integration med kod som inte använder React. Standardkomponenterna för formulär i React Native, som TextInput, är kontrollerade när du skickar in value och onChangeText.

import { TextInput, useRef } from 'react-native';

// Uncontrolled: parent reads value on demand via ref
function UncontrolledInput({ inputRef }) {
  return (
    <TextInput
      ref={inputRef}
      // No value prop: TextInput manages its own text
      defaultValue='Initial text'
    />
  );
}

// Parent reads the value when the form is submitted:
function Form() {
  const inputRef = useRef(null);
  function handleSubmit() {
    // Use _lastNativeText (not recommended) or store value in ref.current
    console.log('Value:', inputRef.current?.props?.value);
  }
}

Verkligt exempel: filter- och resultatpanel

Ett praktiskt exempel på upplyft state är en sökskärm med en filterrad (kategoriväljare, reglage för prisintervall och sorteringsväljare) ovanför en resultatlista. Varje filtervärde finns i den överordnade komponenten SearchScreen. Filterraden tar emot aktuella filtervärden och setter-callbacks som props. Resultatlistan tar emot de aktuella filtren och renderas om när något filter ändras. Den överordnade komponenten hämtar också nya resultat varje gång filtren ändras (via useEffect med filterberoenden). Denna samordning genom en enda överordnad komponent gör sökfunktionen förutsägbar och enkel att testa.

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

export default function SearchScreen() {
  // All filter state lifted here
  const [query, setQuery] = useState('');
  const [category, setCategory] = useState('all');
  const [sortBy, setSortBy] = useState('relevance');
  const [results, setResults] = useState([]);

  // Fetch results whenever any filter changes
  useEffect(() => {
    fetchResults({ query, category, sortBy }).then(setResults);
  }, [query, category, sortBy]);

  return (
    <View style={{ flex: 1 }}>
      <FilterBar
        query={query} onQueryChange={setQuery}
        category={category} onCategoryChange={setCategory}
        sortBy={sortBy} onSortChange={setSortBy}
      />
      <ResultsList results={results} />
    </View>
  );
}

Snabbtest

Testa er förståelse av React Native Mobile Development-koncepten från den här lektionen.

Sammanfattning av lektionen

I den här lektionen har ni lärt er att lyfta state till den närmaste gemensamma föräldern gör det möjligt för syskonkomponenter att dela data via sin förälder, att kontrollerade komponenter tar emot sitt värde och sin uppdateringsfunktion som props, vilket gör dem förutsägbara och enkla att testa, samt att state bör hållas så långt ner i komponentträdet som möjligt — lyft det bara när flera komponenter verkligen behöver det. Nästa steg är att bygga en interaktiv räknarapp som kombinerar alla dessa koncept.

Gratis att börja

Lär dig JavaScript med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
30
Lektioner
120

Vanliga frågor

Är lektionen ”Lyfta upp tillstånd” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen React Native Academy, inklusive ”Lyfta upp tillstånd”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i React Native Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Lyfta upp tillstånd”?

Flytta delat tillstånd till närmaste gemensamma förfader och skicka både värdet och en callback för uppdatering som props, så att syskonkomponenter hålls synkroniserade. Ni övar på React Native Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig React Native Academy?

Du behöver inga förkunskaper. Utbildningen i React Native Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Lyfta upp tillstånd”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här React Native Academy-lektionen?

Ja. Varje React Native Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Skicka data med props
  2. Hantera komponenttillstånd med useState
  3. Lyfta upp tillstånd
  4. Bygga en interaktiv räknarapp
← Tillbaka till React Native Academy