Search code examples
reactjsreact-hooksvegavega-lite

Failed to set state for react functional component


What is expected: button clicked -> variable value changed -> re-render

What happened: button clicked -> error

I am looking for the most straightforward way to fix this issue. I believe redux may help.

I created the project from yarn create react

import React, { useState } from 'react';
import { VegaLite } from 'react-vega'

var specDefault = {
  width: 400,
  height: 200,
  mark: 'bar',
  encoding: {
    x: { field: 'a', type: 'ordinal' },
    y: { field: 'b', type: 'quantitative' },
  },
  data: { name: 'table' },
}

const barData = {
  table: [
    { a: 'A', b: 28 },
    { a: 'B', b: 55 },
    { a: 'C', b: 43 },
    { a: 'D', b: 91 },
    { a: 'E', b: 81 },
    { a: 'F', b: 53 },
    { a: 'G', b: 19 },
    { a: 'H', b: 87 },
    { a: 'I', b: 52 },
  ],
}

function UpdateSpec(props) {
  const [spec, setSpec] = useState(specDefault);

  return (
    <div>
      <button onClick={() => setSpec(spec.mark = 'line')}>
        Change to Line Chart
      </button>
      <VegaLite spec={spec} data={barData} />
    </div>
  );
}

function App() {
  return (
    <>
      {UpdateSpec()}
    </>

  );
}

export default App;

Error:

[Error] TypeError: newSpec is not an Object. (evaluating ''width' in newSpec')
    computeSpecChanges (0.chunk.js:65101)
    componentDidUpdate (0.chunk.js:64784:101)
    commitLifeCycles (0.chunk.js:57118)
    commitLayoutEffects (0.chunk.js:60070)
    callCallback (0.chunk.js:37600)
    dispatchEvent
    invokeGuardedCallbackDev (0.chunk.js:37649)
    invokeGuardedCallback (0.chunk.js:37702)
    commitRootImpl (0.chunk.js:59812)
    commitRootImpl
    unstable_runWithPriority (0.chunk.js:68183)
    commitRoot (0.chunk.js:59654)
    finishSyncRender (0.chunk.js:59071)
    performSyncWorkOnRoot (0.chunk.js:59057)
    performSyncWorkOnRoot
    (anonymous function) (0.chunk.js:48541)
    unstable_runWithPriority (0.chunk.js:68183)
    flushSyncCallbackQueueImpl (0.chunk.js:48536)
    flushSyncCallbackQueue (0.chunk.js:48524)
    discreteUpdates$1 (0.chunk.js:59160)
    discreteUpdates (0.chunk.js:38222)
    dispatchDiscreteEvent (0.chunk.js:41621)
    dispatchDiscreteEvent

Solution

  • state updated with hooks do not merged the values, instead just overrides them.

    From the official docs:

    Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:

    You need to merge the values yourself while updating. In your case you just replaced the entire state with just line by running setSpec(spec.mark = 'line'). You can use functional state update like below

    return (
        <div>
          <button onClick={() => setSpec(prev => ({...prev, mark:'line'}))}>
            Change to Line Chart
          </button>
          <VegaLite spec={spec} data={barData} />
        </div>
      );