4

I have a react component that represents a document with text and some footnotes. The text should be rendered like this:

This the first footnote[1], this is the second[2].

Here is another [3].

As I'm rendering my component, I want to count up every time I see a footnote so that it's incremented. The tree can be many levels deep so you can't assume that all the footnotes are direct children of the main component.

This should also be dynamic, so that adding references updates the count.

I can't think of a very 'Reacty' way of doing this. Context (as frowned upon as it is) does not seem like the right thing, and otherwise, you have no information about neighboring components.

Thariq Shihipar
  • 1,072
  • 1
  • 12
  • 27
  • 1
    What does your data structure look like? This seems like the kind of thing that should be sorted out with the source data, so the components just render what they're given. – Aaron Beall May 22 '17 at 16:37
  • I agree, could be down with pre-processing on the data itself. I was looking for a React like solution to it, so that I wouldn't have to manually go through the tree and update all of those nodes. – Thariq Shihipar May 22 '17 at 16:40
  • Are you saving this document? Doesn't it become some data structure? Can't you get the footnote count from that? Do they get dynamically added? If so, you can fire an event up the DOM hierarchy. You can also use redux to store all your footnotes. – Ruan Mendes May 22 '17 at 16:55

1 Answers1

3

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/

Erik Hermansen
  • 2,200
  • 3
  • 21
  • 41
  • Thanks, this is definitely an interesting approach but passing down the footnotes through the tree is difficult. It could be done with a context like system I suppose, but then it wouldn't reset on every render. – Thariq Shihipar May 22 '17 at 17:02
  • Opinions vary on this. I always err on the side of explicitly passing variables to components, while others like to tuck away variables inside of context. You are in React Country, baby. It's time to pass some props, I say. – Erik Hermansen May 22 '17 at 17:08
  • 2
    Context system: redux global store... :) – Ruan Mendes May 22 '17 at 17:11
  • 1
    Using a store like Juan suggested is fine too, though I like to think of a case like yours (tracking footnotes) as data that only needs to exist in the scope of one render. So I prefer my solution. – Erik Hermansen May 22 '17 at 18:25