javascriptreactjsgetcomputedstyle

Generating a Flicking carousel with React leads to the getComputedStyle error


This question is about egjs-flicking library but perhaps problem is more general.

Let us consider two examples of components that will differ only by their render() method. First I provide the whole component.

import React from 'react';

import Flicking, { MoveEvent, FlickingError } from "@egjs/react-flicking";

class Credits extends React.Component {
    constructor(props) {
        super(props);
        this.readtime = 1000;
        this.maxElements = 4;
        this.actionReady = this.actionReady.bind(this);
        this.actionReset = this.actionReset.bind(this);
        this.actionMove = this.actionMove.bind(this);
        this.processFlicking = this.processFlicking.bind(this);
    }

    async actionReady(e) {
        const status = e.currentTarget.getStatus();
        setTimeout(() => {
            e.currentTarget.moveTo(status.position.panel + 1);
        }, this.readTime);
    }

    async actionReset(e, delay) {
        setTimeout(() => {
            e.currentTarget.moveTo(0);
        }, delay);
    }

    async actionMove(e) {
        const status = e.currentTarget.getStatus();
        setTimeout(() => {
            e.currentTarget.moveTo(status.position.panel + 1);
        }, this.readTime);
    }

    async processFlicking(e) {
        const status = e.currentTarget.getStatus();
        const remaining = status.panels.length - status.position.panel;
        if (remaining == this.maxElements) {
            this.actionReset(e, this.readtime*this.maxElements);
        } else {
            this.actionMove(e);
        }
    }

    getMStyle() {
        return {
            'overflow': 'hidden'
        };
    }

    render() {
        // this is where the examples differ
    }
}

Static case

Now the first case is the children of the Flicking container are pre-defined.

render() {
        const PanelComponent = this.props.panelComponent;
        return (
            <Flicking
              style={this.getMStyle()}
              circular={false}
              align={"prev"}
              horizontal={false}
              onReady={this.actionReady}
              onMoveEnd={this.processFlicking}
            >
              <div>hello</div><div>sup</div><div>how are u</div><div>cze</div><div>konnichiwa</div><div>salam</div><div>namaste</div>
            </Flicking>
        );
    }

which gives the following rendered HTML

<div class="flicking-camera">
  <div>hello</div>
  <div>sup</div>
  <div>how are u</div>
  <div>cze</div>
  <div>konnichiwa</div>
  <div>salam</div>
  <div>namaste</div>
</div>

Dynamic case

Here they are generated dynamically

render() {
        const PanelComponent = this.props.panelComponent;
        return (
            <Flicking
              style={this.getMStyle()}
              circular={false}
              align={"prev"}
              horizontal={false}
              onReady={this.actionReady}
              onMoveEnd={this.processFlicking}
            >
              { this.props.panels.map((data, index) =>
                  ( <PanelComponent key={index} data={data.content} /> )) }
            </Flicking>
        );
    }

giving the following rendered HTML (identical to previous)

<div class="flicking-camera">
  <div>hello</div>
  <div>sup</div>
  <div>how are u</div>
  <div>cze</div>
  <div>konnichiwa</div>
  <div>salam</div>
  <div>namaste</div>
</div>

for PanelComponent it was using

class EntryElement extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div>{ this.props.data }</div>
        )
    }
}

to generate each of the panels.

Problem

The static case works perfectly, the dynamic case gives the following error

Uncaught (in promise) TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.
    at getStyle (utils.ts:259:1)
    at Panel.__proto.resize (Panel.ts:323:1)
    at Renderer.ts:163:1
    at Array.forEach (<anonymous>)
    at ReactRenderer.__proto.updatePanelSize (Renderer.ts:163:1)
    at Flicking.<anonymous> (Flicking.ts:1227:1)
    at step (index.ts:1:1)
    at Object.next (index.ts:1:1)
    at fulfilled (index.ts:1:1)

and I am really puzzled why because resulting HTML is identical... Any ideas?


Solution

  • Ok I actually found it. All thanks to this GitHub discussion and here is my relevant comment.

    From https://naver.github.io/egjs-flicking/docs/quick-start I checked section Bypassing ref forwarding and added useFindDOMNode={true} to my Flicking.

    Here is the complete working source that is able to dynamically put children components in Flicking

    class Credits extends React.Component {
        constructor(props) {
            super(props);
            this.readtime = 1000;
            this.maxElements = 4;
            this.actionReady = this.actionReady.bind(this);
            this.actionReset = this.actionReset.bind(this);
            this.actionMove = this.actionMove.bind(this);
            this.processFlicking = this.processFlicking.bind(this);
            this.createPanel = this.createPanel.bind(this);
        }
    
        async actionReady(e) {
            const status = e.currentTarget.getStatus();
            setTimeout(() => {
                e.currentTarget.moveTo(status.position.panel + 1);
            }, this.readTime);
        }
    
        async actionReset(e, delay) {
            setTimeout(() => {
                e.currentTarget.moveTo(0);
            }, delay);
        }
    
        async actionMove(e) {
            const status = e.currentTarget.getStatus();
            setTimeout(() => {
                e.currentTarget.moveTo(status.position.panel + 1);
            }, this.readTime);
        }
    
        async processFlicking(e) {
            const status = e.currentTarget.getStatus();
            const remaining = status.panels.length - status.position.panel;
            if (remaining == this.maxElements) {
                this.actionReset(e, this.readtime*this.maxElements);
            } else {
                this.actionMove(e);
            }
        }
    
        getMStyle() {
            return {
                'overflow': 'hidden'
            };
        }
    
        createPanel(panel, index) {
            const PanelComponent = this.props.panelComponent;
            return <PanelComponent data={panel.content} key={index} />;
        }
    
        createPanels(panels) {
            return panels.map(this.createPanel);
        }
    
        render() {
            const panels = this.props.panels;
            return (
                <Flicking
                  useFindDOMNode={true}
                  style={this.getMStyle()}
                  circular={false}
                  align={"prev"}
                  horizontal={false}
                  onReady={this.actionReady}
                  onMoveEnd={this.processFlicking}
                >
                  { this.createPanels(panels) }
                </Flicking>
            );
        }
    }