Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.React teilt Ihnen mit, dass das Rendern eine Zustandsaktualisierung verursacht hat, die ein Rendern verursacht hat, das eine Aktualisierung verursacht hat. Die Ursache ist fast immer eines von vier Mustern, und für jedes gibt es eine eindeutige Lösung.
📋 Table of Contents
- Was passiert tatsächlich
- Ursache 1: Aufruf des Handlers statt Übergabe
- Ursache 2: useEffect ohne Abhängigkeitsarray
- Ursache 3: Eine Abhängigkeit, die sich bei jedem Rendern ändert
- Ursache 4: Status zum Ableiten eines Werts festlegen
- Die bedingte Rendering-Variante
- Den Täter finden
- Kurzreferenz
- verwenden Häufig gestellte Fragen
- Fazit
Was passiert tatsächlich
Das Rendern muss eine reine Berechnung der Benutzeroberfläche aus Requisiten und Status sein. Wenn es auch den Status festlegt, erstellen Sie einen Zyklus. React erkennt den Ausreißer und stoppt ihn, anstatt den Tab einfrieren zu lassen.
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1); // runs during render -> triggers render -> runs again
return <div>{count}</div>;
}
Die Fehlermeldung ist hier ungewöhnlich genau: Etwas in Ihrem Renderpfad aktualisiert den Status bei jeder Ausführung.
Ursache 1: Aufruf des Handlers statt Übergabe
Bei weitem die am weitesten verbreitete Version und diejenige, die jeden mindestens einmal erwischt.
function App() {
const [count, setCount] = useState(0);
// ❌ setCount(count + 1) is CALLED during render.
// Its return value (undefined) becomes the onClick handler.
return <button onClick={setCount(count + 1)}>Increment</button>;
}
onClick benötigt eine Funktion, die später aufgerufen werden kann. SchreibensetCount(...) führt es sofort aus. Wickeln Sie es in eine Pfeilfunktion ein, sodass eine Funktion und kein Ergebnis übergeben wird.
// ✅ A function is passed; it runs on click.
<button onClick={() => setCount(count + 1)}>Increment</button>
// ✅ Or reference an existing function without calling it.
<button onClick={handleClick}>Increment</button> // no parentheses
// ❌ This calls handleClick during render.
<button onClick={handleClick()}>Increment</button>
Die Regel:Klammern nach einem Handlernamen in JSX bedeuten, dass er während des Renderns ausgeführt wird. Wenn Sie Argumente übergeben müssen, schließen Sie diese ein.
<button onClick={() => handleDelete(item.id)}>Delete</button>
Ursache 2: useEffect ohne Abhängigkeitsarray
Ein Effekt ohne zweites Argument wird nach jedem Rendern ausgeführt. Wenn der Status festgelegt wird, löst diese Statusänderung ein weiteres Rendern aus, wodurch der Effekt erneut ausgeführt wird.
function Profile({ userId }) {
const [user, setUser] = useState(null);
// ❌ No dependency array — runs after every render, forever.
useEffect(() => {
fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
});
}
// ✅ Re-runs only when userId changes.
useEffect(() => {
fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
}, [userId]);
Das Weglassen des Arrays unterscheidet sich von der Übergabe eines leeren Arrays. Kein Array bedeutet „jedes Rendern“; [] bedeutet „einmal auf dem Berg“.
Ursache 3: Eine Abhängigkeit, die sich bei jedem Rendern ändert
Objekte, Arrays und Funktionen werden durch Referenz verglichen. Eine neue bei jedem Rendern bedeutet, dass die Abhängigkeit immer verändert aussieht.
function Search({ query }) {
const [results, setResults] = useState([]);
// New object identity on every render.
const options = { query, limit: 20 };
useEffect(() => {
search(options).then(setResults);
}, [options]); // ❌ always "changed" -> loop
}
Verlassen Sie sich auf Grundelemente, die nach Wert verglichen werden.
useEffect(() => {
search({ query, limit: 20 }).then(setResults);
}, [query]); // ✅ string compares by value
Wenn das Objekt außerhalb des Effekts existieren muss, merken Sie es sich.
const options = useMemo(() => ({ query, limit: 20 }), [query]);
useEffect(() => {
search(options).then(setResults);
}, [options]); // ✅ identity is stable until query changes
Ursache 4: Status zum Ableiten eines Werts festlegen
Die Verwendung von „state“ und einem Effekt, um etwas aus vorhandenen Werten zu berechnen, führt zu einem unnötigen Zyklus und einem zusätzlichen Rendering.
function Cart({ items }) {
const [total, setTotal] = useState(0);
// ❌ Unnecessary, and loops if the dependency is wrong.
useEffect(() => {
setTotal(items.reduce((sum, i) => sum + i.price, 0));
});
return <p>Total: {total}</p>;
}
Alles, was aus Requisiten oder Zustand berechnet werden kann, sollte während des Renderns berechnet werden.
function Cart({ items }) {
// ✅ No state, no effect, no possible loop.
const total = items.reduce((sum, i) => sum + i.price, 0);
return <p>Total: {total}</p>;
}
Wenn die Berechnung wirklich teuer ist, schließen Sie sie inuseMemoein – aber versetzen Sie es immer noch nicht in den Zustand.
const total = useMemo(
() => items.reduce((sum, i) => sum + i.price, 0),
[items]
);
Die bedingte Rendering-Variante
Das Festlegen des Status innerhalb einer Bedingung während des Renderns ist der gleiche Fehler wie bei einer Verkleidung.
function Form({ initialValue }) {
const [value, setValue] = useState('');
// ❌ Still a state update during render.
if (initialValue && !value) {
setValue(initialValue);
}
}
Stattdessen den Status ordnungsgemäß initialisieren. Wenn die Berechnung des Anfangswerts aufwändig ist, übergeben Sie eine Funktion, sodass sie nur einmal ausgeführt wird.
const [value, setValue] = useState(initialValue ?? '');
// Lazy initialiser — the function runs on the first render only.
const [rows, setRows] = useState(() => parseLargeCsv(raw));
Wenn der Wert zurückgesetzt werden muss, wenn sich eine Requisite ändert, ist die idiomatische Lösung einkey auf der Komponente, die sie im frischen Zustand wieder einbindet.
<Form key={userId} initialValue={user.name} />
Den Täter finden
Wenn die Ursache nicht offensichtlich ist, protokollieren Sie, woher die Renderings stammen.
function MyComponent(props) {
console.count('MyComponent render');
console.log('props:', props);
// ...
}
React DevTools Profiler mit aktivierter Option „Aufzeichnen, warum jede Komponente gerendert wird“ zeigt Ihnen an, welche Requisite oder welcher Status sich bei jedem Durchgang geändert hat. Melden Sie sich bei Effektschleifen im Effekt und im Setter an, um den Zyklus direkt zu sehen.
useEffect(() => {
console.log('effect ran, deps:', { userId, options });
}, [userId, options]);
Kurzreferenz
| Symptom | Wahrscheinliche Ursache | Korrigieren Sie |
|---|---|---|
| Fehler sofort beim Mounten | Beim Rendern aufgerufener Handler | Wickeln Sie eine Pfeilfunktion ein |
| Schleifen nach dem Laden von Daten | Effekt, bei dem ein Abhängigkeitsarray fehlt | Fügen Sie[]hinzu oder die richtigen deps |
| Schleifen trotz korrekter Deps | Objekt- oder Funktionsabhängigkeit | Verwenden Sie GrundelementeuseMemo, oderuseCallback |
| Zusätzliches Rendern bei jeder Änderung | Abgeleiteter Wert, gespeichert im Status | Berechnung während des Renderns |
| Schleifen auf einer Bedingung | setState im Renderzweig | Status initialisieren oderkey |
verwenden Häufig gestellte Fragen
F: Warum passiert das nur manchmal?
A: Schleifen, die von Daten abhängen, erfordern, dass die Daten zuerst ankommen. Der Fehler ist von Anfang an vorhanden, wird jedoch erst ausgelöst, wenn ein Abruf aufgelöst und der Status festgelegt wird.
F: Ist es jemals akzeptabel, den Status während des Renderns festzulegen?
A: React erlaubt einen engen Fall: Anpassen des Status während des Renderns, wenn sich eine Requisite ändert, Aktualisieren nur derselben Komponente und Schutz durch einen Vergleich. Es ist selten die beste Antwort; Bevorzugen Sie die Berechnung während des Renderns oder die Verwendung vonkey.
F: Passiert das in Klassenkomponenten?
A: Ja. AufrufsetState in render oder unbedingt incomponentDidUpdate erzeugt die identische Schleife.
F: Mein Effekt hat ein leeres Array, wird aber trotzdem wiederholt. Warum?
A: Dann befindet sich die Schleife woanders – meistens in einem Handler, der während des Renderns aufgerufen wird, oder in einem übergeordneten Element, das das untergeordnete Element erneut einbindet, weil eskeyist ändert sich bei jedem Rendern.
F: Wird der React Compiler das Problem für mich beheben?
A: Es entfernt die Memoisierungskategorie des Problems, indem die Identität automatisch verarbeitet wird. Der Aufruf eines Handlers während des Renderns wird nicht behoben, was ein einfacher Fehler in JSX ist.
Fazit
„Zu viele erneute Renderings“ bedeutet immer, dass der Status im Rahmen des Renderings aktualisiert wird. Überprüfen Sie die vier Ursachen in der Reihenfolge:ein Handler, der in JSX aufgerufen statt übergeben wird, auseEffectohne Abhängigkeitsarray, eine Objekt- oder Funktionsabhängigkeit, die bei jedem Rendern ihre Identität ändert, und abgeleitete Werte, die im Status gespeichert und nicht berechnet werden. Die zugrunde liegende Regel ist einfach: Das Rendern muss eine reine Berechnung sein und jede Statusaktualisierung muss von einem Ereignishandler oder einem Effekt stammen, niemals vom Rendern selbst.
🔗 Share this article
✍️ Leave a Comment