@@ -42,15 +42,26 @@ export interface IsaacContentProps extends RouteComponentProps {
42
42
43
43
export const IsaacContent = withRouter ( ( props : IsaacContentProps ) => {
44
44
const { doc : { type, layout, encoding, value, children} , match} = props ;
45
+ const keyedProps = { ...props , key : props . doc . id } ;
46
+ { /*
47
+ Each IsaacContent is assumed to be independent, not sharing state with any other.
48
+ However, React will reuse components if they are the same type, have the same key (or undefined), and exist in the same place in the DOM.
49
+ If two components A and B meet these criteria, if you switch from component A to component B, any e.g. useStates in B will not
50
+ initialise as expected, but will retain stale data from A.
51
+
52
+ This is a problem for any structure where one of several <IsaacContent>s are displayed, e.g. quiz sections, tabs, ... .
53
+
54
+ To avoid this, we set the key of each <IsaacContent> to its content ID.
55
+ */ }
45
56
46
57
let selectedComponent ;
47
58
let tempSelectedComponent ;
48
59
if ( isQuestion ( props . doc ) ) {
49
60
// FIXME: Someday someone will remove /quiz/ and this comment too.
50
61
if ( match . path . startsWith ( "/quiz/" ) || match . path . startsWith ( "/test/" ) ) {
51
- tempSelectedComponent = < QuizQuestion { ...props } /> ;
62
+ tempSelectedComponent = < QuizQuestion { ...keyedProps } /> ;
52
63
} else {
53
- tempSelectedComponent = < IsaacQuestion { ...props } /> ;
64
+ tempSelectedComponent = < IsaacQuestion { ...keyedProps } /> ;
54
65
}
55
66
56
67
if ( type === "isaacInlineRegion" ) {
@@ -60,25 +71,25 @@ export const IsaacContent = withRouter((props: IsaacContentProps) => {
60
71
selectedComponent = < QuestionContext . Provider value = { props . doc . id } > { tempSelectedComponent } </ QuestionContext . Provider > ;
61
72
} else {
62
73
switch ( type ) {
63
- case "figure" : selectedComponent = < IsaacFigure { ...props } /> ; break ;
64
- case "image" : selectedComponent = < IsaacImage { ...props } /> ; break ;
65
- case "video" : selectedComponent = < IsaacVideo { ...props } /> ; break ;
66
- case "codeSnippet" : selectedComponent = < IsaacCodeSnippet { ...props } /> ; break ;
67
- case "interactiveCodeSnippet" : selectedComponent = < IsaacInteractiveCodeSnippet { ...props } /> ; break ;
68
- case "glossaryTerm" : selectedComponent = < IsaacGlossaryTerm { ...props } /> ; break ;
69
- case "isaacFeaturedProfile" : selectedComponent = < IsaacFeaturedProfile { ...props } /> ; break ;
70
- case "isaacQuestion" : selectedComponent = < IsaacQuickQuestion { ...props } /> ; break ;
71
- case "anvilApp" : selectedComponent = < AnvilApp { ...props } /> ; break ;
72
- case "isaacCard" : selectedComponent = < IsaacCard { ...props } /> ; break ;
73
- case "isaacCardDeck" : selectedComponent = < IsaacCardDeck { ...props } /> ; break ;
74
- case "codeTabs" : selectedComponent = < IsaacCodeTabs { ...props } /> ; break ;
74
+ case "figure" : selectedComponent = < IsaacFigure { ...keyedProps } /> ; break ;
75
+ case "image" : selectedComponent = < IsaacImage { ...keyedProps } /> ; break ;
76
+ case "video" : selectedComponent = < IsaacVideo { ...keyedProps } /> ; break ;
77
+ case "codeSnippet" : selectedComponent = < IsaacCodeSnippet { ...keyedProps } /> ; break ;
78
+ case "interactiveCodeSnippet" : selectedComponent = < IsaacInteractiveCodeSnippet { ...keyedProps } /> ; break ;
79
+ case "glossaryTerm" : selectedComponent = < IsaacGlossaryTerm { ...keyedProps } /> ; break ;
80
+ case "isaacFeaturedProfile" : selectedComponent = < IsaacFeaturedProfile { ...keyedProps } /> ; break ;
81
+ case "isaacQuestion" : selectedComponent = < IsaacQuickQuestion { ...keyedProps } /> ; break ;
82
+ case "anvilApp" : selectedComponent = < AnvilApp { ...keyedProps } /> ; break ;
83
+ case "isaacCard" : selectedComponent = < IsaacCard { ...keyedProps } /> ; break ;
84
+ case "isaacCardDeck" : selectedComponent = < IsaacCardDeck { ...keyedProps } /> ; break ;
85
+ case "codeTabs" : selectedComponent = < IsaacCodeTabs { ...keyedProps } /> ; break ;
75
86
default :
76
87
switch ( layout ) {
77
- case "tabs" : selectedComponent = < IsaacTabs { ...props } /> ; break ;
78
- case isTabs ( layout ) : selectedComponent = < IsaacTabs { ...props } style = { layout ?. split ( '/' ) [ 1 ] } /> ; break ;
79
- case "callout" : selectedComponent = < IsaacCallout { ...props } /> ; break ;
80
- case "accordion" : selectedComponent = < IsaacAccordion { ...props } /> ; break ;
81
- case "horizontal" : selectedComponent = < IsaacHorizontal { ...props } /> ; break ;
88
+ case "tabs" : selectedComponent = < IsaacTabs { ...keyedProps } /> ; break ;
89
+ case isTabs ( layout ) : selectedComponent = < IsaacTabs { ...keyedProps } style = { layout ?. split ( '/' ) [ 1 ] } /> ; break ;
90
+ case "callout" : selectedComponent = < IsaacCallout { ...keyedProps } /> ; break ;
91
+ case "accordion" : selectedComponent = < IsaacAccordion { ...keyedProps } /> ; break ;
92
+ case "horizontal" : selectedComponent = < IsaacHorizontal { ...keyedProps } /> ; break ;
82
93
case "clearfix" : selectedComponent = < > </ > ; break ;
83
94
default : selectedComponent =
84
95
< IsaacContentValueOrChildren encoding = { encoding } value = { value } >
0 commit comments