hugocxl / react-echarts

🐳 ECharts for React
https://hugocxl.github.io/react-echarts/
MIT License
94 stars 6 forks source link

Console complaining `use()` of unused components #19

Closed xsjcTony closed 9 months ago

xsjcTony commented 9 months ago

Description

Just a normal bar chart used, and use has been specified, however the console still reporting lots of unused use cases. Worrying about this since that in dev mode it may accidentally let something run without specifying in use, hence will break in production if those are not imported in prod. image

Link to Reproduction

https://stackblitz.com/edit/vitejs-vite-ea2tby?file=src%2FApp.tsx,src%2FECharts.tsx&terminal=dev

Steps to reproduce

Visit the reprod link

JS Framework

React TS

Version

latest

Browser

Chrome 121

Operating System

Additional Information

No response

xsjcTony commented 9 months ago

I was wondering whether this is a issue with the official echarts package, but seems like no, as I tried it with the minimum setup of a simple bar chart, and no errors are being printed in the console. https://stackblitz.com/edit/vitejs-vite-dyr5tr?file=src%2Fmain.ts,index.html&terminal=dev

hugocxl commented 9 months ago

@xsjcTony mmm definitely, I'll look into this.

brandanking-decently commented 9 months ago

@hugocxl have you had any chance to look into this. My current solution is just to add the required components but don't want to hurt performance

hugocxl commented 9 months ago

@brandanking-decently I'll try to find some time this weekend, I'm pretty busy at the time...

brandanking-decently commented 9 months ago

That would be great thanks, let me know if I can do anything to potentially assist with this

brandanking-decently commented 9 months ago

@hugocxl I've been able to figure out why this is happening. I tried creating a PR however, I was unable to due to restrictions as well as local problems with lint-staged (I don't use pnpm)

Below is the implemented code

To explain why I think this was happening, despite the modules being undefined, eChart still believed they where being used as undefined was explicitly passed in as a parameter. By removing the parameter all together if not required, then it works how it should. This also allows animations to work on first render as well.

So I believe it closes #20 as well as #19

You may want to do some additional improvements but hopefully you can get this merged in soon as it would be a great help on the project

// Dependencies
import { init, ECharts, use as echartsUse } from 'echarts/core'
import { useEffect, useRef, useState } from 'react'

// Constants
import { echartsEvents as ev } from './events'

// Types
import type { EChartEventsProps } from './events'
import type { EChartsOption, SetOptionOpts } from 'echarts'

export type UseEChartsOptions = EChartEventsProps &
  SetOptionOpts &
  EChartsOption &
  Parameters<typeof init>[2] & {
    group?: ECharts['group']
    theme?: Parameters<typeof init>[1]
    use?: Parameters<typeof echartsUse>[0]
  }

export function useECharts<T extends HTMLElement>({
  // Init
  devicePixelRatio,
  height,
  locale,
  pointerSize,
  renderer,
  theme,
  use,
  useCoarsePointer,
  useDirtyRect,
  width,

  // eChartsInstance
  group,

  // SetOption
  lazyUpdate,
  notMerge,
  replaceMerge,
  silent,
  transition,
  darkMode,
  media,
  options,
  stateAnimation,

  // Option
  angleAxis,
  animation,
  animationDelay,
  animationDelayUpdate,
  animationDuration,
  animationDurationUpdate,
  animationEasing,
  animationEasingUpdate,
  animationThreshold,
  aria,
  axisPointer,
  backgroundColor,
  blendMode,
  brush,
  calendar,
  color,
  dataZoom,
  dataset,
  geo,
  graphic,
  grid,
  hoverLayerThreshold,
  legend,
  parallel,
  parallelAxis,
  polar,
  progressive,
  progressiveThreshold,
  radar,
  radiusAxis,
  series,
  singleAxis,
  textStyle,
  timeline,
  title,
  toolbox,
  tooltip,
  useUTC,
  visualMap,
  xAxis,
  yAxis,

  // Events
  onAxisAreaSelected,
  onBrush,
  onBrushEnd,
  onBrushSelected,
  onClick,
  onContextMenu,
  onDataRangeSelected,
  onDataViewChanged,
  onDataZoom,
  onDoubleClick,
  onDownplay,
  onFinished,
  onGeoSelectChanged,
  onGeoSelected,
  onGeoUnselected,
  onGlobalCursorTaken,
  onGlobalOut,
  onHighlight,
  onLegendInverseSelect,
  onLegendScroll,
  onLegendSelectChanged,
  onLegendSelected,
  onLegendUnselected,
  onMagicTypeChanged,
  onMouseDown,
  onMouseMove,
  onMouseOut,
  onMouseOver,
  onRendered,
  onRestore,
  onSelectChanged,
  onTimelineChanged,
  onTimelinePlayChanged
}: UseEChartsOptions): [(node: T) => void, ECharts | undefined] {
  const containerRef = useRef<T>()
  const echartsRef = useRef<ECharts>()
  const resizeObserverRef = useRef<ResizeObserver>()
  const [started, setStarted] = useState(false)
  const echartsInstance = echartsRef.current

  async function setContainerRef(node: T) {
    if (containerRef.current && echartsRef.current) return

    containerRef.current = node
    echartsRef.current = await startEcharts()
    resizeObserverRef.current = startResizeObserver()

    setStarted(true)
  }

  async function startEcharts() {
    if (!containerRef.current) return

    const useOpts = use || (await getGlobalUse())

    echartsUse(useOpts)

    return init(containerRef.current, theme, {
      devicePixelRatio,
      height,
      locale,
      pointerSize,
      renderer,
      useCoarsePointer,
      useDirtyRect,
      width
    })
  }

  function startResizeObserver() {
    const resizeObserver = new ResizeObserver(() => {
      echartsRef.current?.resize()
    })

    if (containerRef.current) resizeObserver.observe(containerRef.current)
    return resizeObserver
  }

  useEffect(() => {
    if (!echartsInstance) return

    if (group) echartsInstance.group = group
  }, [group, started, echartsInstance])

  useEffect(() => {
    if (!echartsInstance) return

    echartsInstance.clear()
    echartsInstance.setOption(
      {
        series,
        useUTC,
        xAxis,
        yAxis,
        progressive,
        blendMode,
        hoverLayerThreshold,
        progressiveThreshold,
        ...(angleAxis && { angleAxis }),
        ...(animation && { animation }),
        ...(animationDelay && { animationDelay }),
        ...(animationDelayUpdate && { animationDelayUpdate }),
        ...(animationDuration && { animationDuration }),
        ...(animationDurationUpdate && { animationDurationUpdate }),
        ...(animationEasing && { animationEasing }),
        ...(animationEasingUpdate && { animationEasingUpdate }),
        ...(animationThreshold && { animationThreshold }),
        ...(aria && { aria }),
        ...(axisPointer && { axisPointer }),
        ...(backgroundColor && { backgroundColor }),
        ...(brush && { brush }),
        ...(calendar && { calendar }),
        ...(color && { color }),
        ...(darkMode && { darkMode }),
        ...(dataset && { dataset }),
        ...(dataZoom && { dataZoom }),
        ...(geo && { geo }),
        ...(graphic && { graphic }),
        ...(grid && { grid }),
        ...(legend && { legend }),
        ...(media && { media }),
        ...(options && { options }),
        ...(parallel && { parallel }),
        ...(parallelAxis && { parallelAxis }),
        ...(polar && { polar }),
        ...(radar && { radar }),
        ...(radiusAxis && { radiusAxis }),
        ...(series && { series }),
        ...(singleAxis && { singleAxis }),
        ...(stateAnimation && { stateAnimation }),
        ...(textStyle && { textStyle }),
        ...(timeline && { timeline }),
        ...(title && { title }),
        ...(toolbox && { toolbox }),
        ...(tooltip && { tooltip }),
        ...(useUTC && { useUTC }),
        ...(visualMap && { visualMap }),
        ...(xAxis && { xAxis }),
        ...(yAxis && { yAxis })
      },
      {
        lazyUpdate,
        notMerge,
        replaceMerge,
        silent,
        transition
      }
    )
  }, [
    angleAxis,
    animation,
    animationDelay,
    animationDelayUpdate,
    animationDuration,
    animationDurationUpdate,
    animationEasing,
    animationEasingUpdate,
    animationThreshold,
    aria,
    axisPointer,
    backgroundColor,
    blendMode,
    brush,
    calendar,
    color,
    darkMode,
    dataset,
    dataZoom,
    geo,
    graphic,
    grid,
    hoverLayerThreshold,
    legend,
    media,
    options,
    parallel,
    parallelAxis,
    polar,
    progressive,
    progressiveThreshold,
    radar,
    radiusAxis,
    series,
    singleAxis,
    stateAnimation,
    textStyle,
    timeline,
    title,
    toolbox,
    tooltip,
    useUTC,
    visualMap,
    xAxis,
    yAxis,

    //
    lazyUpdate,
    notMerge,
    replaceMerge,
    silent,
    transition,

    //
    started,
    echartsInstance
  ])

  useEffect(() => {
    if (!echartsInstance) return

    if (onAxisAreaSelected) {
      echartsInstance.on(ev.onAxisAreaSelected, onAxisAreaSelected)
    }
    if (onBrush) {
      echartsInstance.on(ev.onBrush, onBrush)
    }
    if (onBrushEnd) {
      echartsInstance.on(ev.onBrushEnd, onBrushEnd)
    }
    if (onBrushSelected) {
      echartsInstance.on(ev.onBrushSelected, onBrushSelected)
    }
    if (onClick) {
      echartsInstance.on(ev.onClick, onClick)
    }
    if (onContextMenu) {
      echartsInstance.on(ev.onContextMenu, onContextMenu)
    }
    if (onDataRangeSelected) {
      echartsInstance.on(ev.onDataRangeSelected, onDataRangeSelected)
    }
    if (onDataViewChanged) {
      echartsInstance.on(ev.onDataViewChanged, onDataViewChanged)
    }
    if (onDataZoom) {
      echartsInstance.on(ev.onDataZoom, onDataZoom)
    }
    if (onDoubleClick) {
      echartsInstance.on(ev.onDoubleClick, onDoubleClick)
    }
    if (onDownplay) {
      echartsInstance.on(ev.onDownplay, onDownplay)
    }
    if (onFinished) {
      echartsInstance.on(ev.onFinished, onFinished)
    }
    if (onGeoSelectChanged) {
      echartsInstance.on(ev.onGeoSelectChanged, onGeoSelectChanged)
    }
    if (onGeoSelected) {
      echartsInstance.on(ev.onGeoSelected, onGeoSelected)
    }
    if (onGeoUnselected) {
      echartsInstance.on(ev.onGeoUnselected, onGeoUnselected)
    }
    if (onGlobalCursorTaken) {
      echartsInstance.on(ev.onGlobalCursorTaken, onGlobalCursorTaken)
    }
    if (onGlobalOut) {
      echartsInstance.on(ev.onGlobalOut, onGlobalOut)
    }
    if (onHighlight) {
      echartsInstance.on(ev.onHighlight, onHighlight)
    }
    if (onLegendInverseSelect) {
      echartsInstance.on(ev.onLegendInverseSelect, onLegendInverseSelect)
    }
    if (onLegendScroll) {
      echartsInstance.on(ev.onLegendScroll, onLegendScroll)
    }
    if (onLegendScroll) {
      echartsInstance.on(ev.onLegendScroll, onLegendScroll)
    }
    if (onLegendSelectChanged) {
      echartsInstance.on(ev.onLegendSelectChanged, onLegendSelectChanged)
    }
    if (onLegendSelected) {
      echartsInstance.on(ev.onLegendSelected, onLegendSelected)
    }
    if (onLegendUnselected) {
      echartsInstance.on(ev.onLegendUnselected, onLegendUnselected)
    }
    if (onMagicTypeChanged) {
      echartsInstance.on(ev.onMagicTypeChanged, onMagicTypeChanged)
    }
    if (onMouseDown) {
      echartsInstance.on(ev.onMouseDown, onMouseDown)
    }
    if (onMouseMove) {
      echartsInstance.on(ev.onMouseMove, onMouseMove)
    }
    if (onMouseOut) {
      echartsInstance.on(ev.onMouseOut, onMouseOut)
    }
    if (onMouseOver) {
      echartsInstance.on(ev.onMouseOver, onMouseOver)
    }
    if (onRendered) {
      echartsInstance.on(ev.onRendered, onRendered)
    }
    if (onRestore) {
      echartsInstance.on(ev.onRestore, onRestore)
    }
    if (onSelectChanged) {
      echartsInstance.on(ev.onSelectChanged, onSelectChanged)
    }
    if (onTimelineChanged) {
      echartsInstance.on(ev.onTimelineChanged, onTimelineChanged)
    }
    if (onTimelinePlayChanged) {
      echartsInstance.on(ev.onTimelinePlayChanged, onTimelinePlayChanged)
    }
  }, [
    onAxisAreaSelected,
    onBrush,
    onBrushEnd,
    onBrushSelected,
    onClick,
    onContextMenu,
    onDataRangeSelected,
    onDataViewChanged,
    onDataZoom,
    onDoubleClick,
    onDownplay,
    onFinished,
    onGeoSelectChanged,
    onGeoSelected,
    onGeoUnselected,
    onGlobalCursorTaken,
    onGlobalOut,
    onHighlight,
    onLegendInverseSelect,
    onLegendScroll,
    onLegendSelectChanged,
    onLegendSelected,
    onLegendUnselected,
    onMagicTypeChanged,
    onMouseDown,
    onMouseMove,
    onMouseOut,
    onMouseOver,
    onRendered,
    onRestore,
    onSelectChanged,
    onTimelineChanged,
    onTimelinePlayChanged,

    //
    started,
    echartsInstance
  ])

  return [setContainerRef, echartsRef.current]
}

async function getGlobalUse() {
  const all = [
    import('echarts/features'),
    import('echarts/charts'),
    import('echarts/components'),
    import('echarts/renderers')
  ]

  const promise = await Promise.all(all.map(m => m.then(m => Object.values(m))))

  return promise.flat()
}
hugocxl commented 9 months ago

@brandanking-decently incredible contribution! I tested it and it solves all mentioned issues. Already published under version v1.0.4.

Thank you so much for taking care of it, really appreciate it 🙏