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
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>
);