How to create recursive component in ReasonReact?

reasonreact

#1

Hi. I wonder how to create recursive components in ReasonReact. For example, I have code:

let component = ReasonReact.statelessComponent("Comment");
let make = (~descendants, ~data, ~error, ~kidsOnly, _children) => {
  {
    ...component,
    render: (_self) =>
    <div className={"comments"}>
      { switch error {
          | true => ReasonReact.nullElement
          | false => if (descendants > 0) {
              <h2 className={"numberOfComments"}>{textEl({j|$descendants comments|j})}</h2>
            } else {
              ReasonReact.nullElement
            }
        }
      }
      { switch error {
        | true => <Text text={Some("Unable to load comments.")} isComment={false} />
        | false =>  <section>
            <Comment data={data} kidsOnly={true} />
          </section>
        }
      }
    </div>
  }
}

and the error is

  25 ┆ | true => <Text text={Some("Unable to load comments.")} isComment={fal
       se} />
  26 ┆ | false =>  <section>
  27 ┆     <Comment data={data} kidsOnly={true} />
  28 ┆   </section>
  29 ┆ }

  The module or file Comment can't be found.
  - If it's a third-party dependency:
    - Did you list it in bsconfig.json?
    - Did you run `bsb` instead of `bsb -make-world`
      (latter builds third-parties)?
  - Did you include the file's directory in bsconfig.json?

#2

if you use an inline module (see https://reasonml.github.io/docs/en/module.html), you can use “module rec” syntax to have two mutually-recursive modules. This would require breaking your Comments component apart—but you might find yourself doing that naturally as your code grows (for example, having a dedicated CommentTree component, and just have Comments live at the leaves? I of course can’t speak directly to your use case).

The hackier, escape-hatch way to do this would be to use mutable state: you can define a ref before your module with a signature that matches the component creation function, and then mutate that ref after the fact. This would look something like this scratchpad — but again: I would first consider other options.

These are probably not the only ways to solve this—others with more experience than I might want to weigh in—but that could at least unblock you.


#3

@Riwsky thanks for the help. I haven’t fixed issue yet. It seems I can write recursive functions, but not recursive modules in Reason and ReasonReact. I found following posts:

But still no luck. How to use val keyword in ReasonML?


#4

were you not able to split your component into two?

does the ref-based workaround not work for your use case?

it’s easier for people to help you if you provide feedback on what did or didn’t work from earlier posts, or if you provide a scratchpad link or some sample code with the problem you’re trying to solve. Otherwise, folks can answer the surface-level questions you ask (for an example of val usage, you can check this scratchpad)—but they can’t ensure it’s, well, actually helpful.


#5

Full code is here https://github.com/reasonml-community/reason-react-hacker-news/pull/11. I will try to create scratchpad with isolated example later. As of now I’m trying to create recursive module definition in Reason (there is example of how to do this), but those modules also have to be React components (contain make method etc.).


#6

This conversation was a long time ago now but I found it while searching for the same question. The result I reached that worked for a self-recursive solution was:

module type NodeType = {
	let make: ({. "id": int}) => ReasonReact.reactElement;
	let makeProps: (~id: 'id, ~key: string=?, unit) => {. "id": 'id};
};

module rec NodeChild: NodeType {
	[@react.component]
	let make = (~id, ~nodeChildren) => {
		let childNodeValues = try (Hashtbl.find(nodeChildren, id)) {
			| Not_found => [] 
			};

		let childNodes = List.map((child) => {
			<NodeChild key=string_of_int(child) id=child />
		}, childNodeValues);

		<div>
      <div> id->React.string  </div>
      <div> {ReasonReact.array(Array.of_list(childNodes))} </div>
		</div>;
	}
};

let make = NodeChild.make
let makeProps = NodeChild.makeProps