diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index c8e5893f19..c8596b0465 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -280,10 +280,20 @@ class BaseFormSet(RenderableFormMixin):
             )
         return [form.cleaned_data for form in self.forms]
 
+    @cached_property
+    def forms_valid(self):
+        return all(
+            [
+                form.is_valid()
+                for form in self.forms
+                if not (self.can_delete and self._should_delete_form(form))
+            ]
+        )
+
     @property
     def deleted_forms(self):
         """Return a list of forms that have been marked for deletion."""
-        if not self.is_valid() or not self.can_delete:
+        if not self.forms_valid or not self.can_delete:
             return []
         # construct _deleted_form_indexes which is just a list of form indexes
         # that have had their deletion widget set to True
@@ -303,7 +313,7 @@ class BaseFormSet(RenderableFormMixin):
         Return a list of form in the order specified by the incoming data.
         Raise an AttributeError if ordering is not allowed.
         """
-        if not self.is_valid() or not self.can_order:
+        if not self.forms_valid or not self.can_order:
             raise AttributeError(
                 "'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__
             )
@@ -384,14 +394,7 @@ class BaseFormSet(RenderableFormMixin):
         self.errors
         # List comprehension ensures is_valid() is called for all forms.
         # Forms due to be deleted shouldn't cause the formset to be invalid.
-        forms_valid = all(
-            [
-                form.is_valid()
-                for form in self.forms
-                if not (self.can_delete and self._should_delete_form(form))
-            ]
-        )
-        return forms_valid and not self.non_form_errors()
+        return self.forms_valid and not self.non_form_errors()
 
     def full_clean(self):
         """
