How to size a React-embedded notebook at runtime?

Thanks to earlier help, I was able to bring my notebook into a React component.

However, the component renders at the dimensions of the viewport, plus some additional pixels, leading to vertical and horizontal scrollbars being shown that hide content. The internal dimensions are not calculated correctly, which also leads to some unwanted vertical space between the two top panels and the bottom one:

In my notebook, I have two variables mutable viewportWidth and mutable viewportHeight which rely on the value of width to set up, size and render the genomeSpace cell within the Observablehq notebook environment.

I would like to use standard React routines to resize this notebook component before it is rendered in a React application.

Custom sizing will allow me to try to integrate the notebook component with other React components and UI widgets.

I am attempting to glue the instantiation routine here to a resize event:

import React, {Component} from "react";
import {Runtime, Inspector, Library} from "@observablehq/runtime";
import notebook from "0f7b50a23d98b321";

class App extends Component {

  constructor(props) {
    super(props);
    this.state = { 
      width: 0, 
      height: 0 
    };
    this.genomeSpaceRef = React.createRef();
    this.viewportWidth = {};
    this.viewportHeight = {};
  }

  componentDidMount() {
    const genomeSpace = this.genomeSpaceRef.current;
    const library = new Library();
    const runtime = new Runtime();
    const main = runtime.module(notebook, name => {
      if (name === "viewof genomeSpace") {
        return new Inspector(genomeSpace);
      }
      if (name === "mutable viewportWidth") {
        return {fulfilled: (value) => {
          this.viewportWidth = value;
        }};
      }
      if (name === "mutable viewportHeight") {
        return {fulfilled: (value) => {
          this.viewportHeight = value;
        }};
      }
    });
    main.redefine("width", library.Generators.observe(notify => {
      let width = notify(genomeSpace.clientWidth);
      function resized() {
        let newWidth = genomeSpace.clientWidth;
        if (newWidth !== width) {
          notify(width = newWidth)
        }
      }
      window.addEventListener("resize", resized);
      return () => window.removeEventListener("resize", resized);
    }));
    window.addEventListener('resize', this.updateDimensions);
    setTimeout(() => {
      window.dispatchEvent(new Event('resize'));
    }, 2500);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.updateDimensions);
  }

  componentDidUpdate(nextProps, nextState) {}

  updateDimensions = () => {
    this.setState({ 
      width: window.innerWidth, 
      height: window.innerHeight
    }, () => {
      this.viewportWidth.value = parseInt(this.state.width);
      this.viewportHeight.value = parseInt(this.state.height);
    });
  };

  render() {
    return (
      <div id="parent" className="parent">
        <div ref={this.genomeSpaceRef} className="genomeSpace"></div>
      </div>
    );
  }
}

export default App;

While updateDimensions will be called as a result of dispatching a resize event via window.dispatchEvent(new Event('resize')), the problem is that the notebook does not always redraw correctly. Sometimes it works, sometimes it doesn’t.

When it does redraw correctly, there is a delay of 2.5 sec (give or take) before that redraw occurs.

What I would like to do is size the notebook viewof genomeSpace cell before it is drawn, by setting the mutable viewportWidth and mutable viewportHeight values via this.setState(...).

What is the correct and reliable way to dynamically resize a notebook component to whatever is set in application state?

I can’t speak on the parts relating to React, but my initial guess for the delay is that your dependency on Observable’s width variable is so far up that any change practically invalidates and recomputes your entire notebook. You can review your notebook’s dependency graph with the help of the Notebook Visualizer, but be sure to enable the checkbox “builtins”.

It might help to know that there’s nothing magical about width – it’s just a resize event handler wrapped in a generator: stdlib/width.js at main · observablehq/stdlib · GitHub

1 Like

I forked the notebook and removed references in it to width and height, replacing my view width and height parameters with some preset integers.

Removing references to the width function breaks automatic full-screen redraw support in Observable — that’s fine — but the idea is that I am using mutable variables to specify layout, and so in theory I should be able to at least set or adjust them elsewhere, such as in a React component.

I pulled this fork into my test React project, and unfortunately the notebook still behaves as if its layout is dependent on width, even though it isn’t being used — at least not by me.

I opened the fork with the Notebook Visualizer tool and verified that width is not part of the dependency graph.

Still, I can’t seem to get a basic resize event to reliably override the notebook’s internal width and height from application state. Not sure if this is a bug or how this is intended to work?