I think I would handle it like this...
In your container or top-level component, create an array for holding footnotes. Then pass this array down as a prop to any component that may render footnotes, and also to a footnote-rendering component which must be rendered after any of the other components.
const DocComponent = () => {
const footnotes = [];
return (
<div>
<SomeContent footnotes={footnotes} />
<SomeOtherContent footnotes={footnotes} />
<EvenDifferentContent footnotes={footnotes} />
<Footnotes footnotes={footnotes} />
</div>
);
};
Note that the footnotes array must be passed down the hierarchy via props to all components that could render a reference to a footnote. Every time a component renders a footnote reference, it adds a footnote to the array like so:
const SomeContent = ({footnotes}) => {
footnotes.push('This is the footnote text.');
const footnoteIndex = footnotes.length;
return (<p>Hermansen and Shihipar, et al [{footnoteIndex}]</p>);
};
When execution arrives to the Footnotes component, the same footnotes array instance will be passed via prop to it. At that point in execution, the array will be populated with all the footnotes that need to be displayed. And you can just render them in a straightforward way:
const Footnotes = ({footnotes}) => {
const inner = footnotes.map(
(footnote, index) => (<li>[{index+1}] {footnote}</li>) );
return (<ul>{inner}</ul>);
};
This implementation is definitely coupled to the rendering order of components. So the component order in your rendering should match the visual order you would want footnotes to appear in.
Here is a jsfiddle - https://jsfiddle.net/69z2wepo/79222/