Data overriding in components

Hi there! I am creating an app that shows different charts depending on a previous loaded info about each chart.

So I first load the charts info and pass it to a component called ChartLoader that will fetch the chart data and show it on screen. Each chart data is stored inside an array that I call chartsArray. After load the data I just loop through chartsArray to show each chart:

{{#if this.isLoading}}
  ... loading
{{else}}
  {{#each this.chartsArray as |chart|}}
      <LineChart @firstDataSet={{chart.carteiraDataSet}} @secondDataSet={{chart.benchmarkDataSet}} />
  {{/each}}
{{/if}}

The problem is that inside LineChart data is being overrode and when the loop ends only the last entry of chartsData is showed. I end up with six equal charts. Why is it overriding? I know that tracked properties will update but I am looping through an array and sending each entry to LineChart to render the chart. I thought that it wouldn’t be overrode. What is the best way to design this components and prevent it to be overridden?

Thanks in advance!

:thinking: This template code looks fine to me. Have you verified that chartsArray is definitely what you would expect? If not I would try checking (with a debugger statement or breakpoint or {{log this.chartsArray}} invocation) that the chartsArray looks as you expect.

If that’s not the case perhaps LineChart is inadvertently leaking state. Is it a glimmer component or an ember classic component? And what does the javascript part look like?

Hey! Thank you!

Yes, chartsArray looks like this (I am omitting the data part):

[{"chartLabel": "DIV01", "carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]},{"chartLabel":"DIV02", "carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]},{"chartLabel":"DIV03", "carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]},{"chartLabel":"DIV04","carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]},{"chartLabel":"DIV05","carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]}]

And LineChart is just a glimmer component that uses D3.js to plot the charts like this:

//line-chart.js
export default class LineChartComponent extends Component {

  @tracked scaleY;
  @tracked scaleX;
  @tracked margin;
  @tracked width;
  @tracked height;
  @tracked axisPad;
  @tracked charteiraDataSet;
  @tracked benchmarkDataSet;
  @tracked chartWidth;
  @tracked chartHeight;
  @tracked showAxis = false;

  constructor(owner, args) {
    super(owner, args);
    this.carteiraDataSet = this.args.carteiraDataSet;
    this.benchmarkDataSet = this.args.benchmarkDataSet;
    this.chartWidth = this.args.chartWidth;
    this.chartHeight = this.args.chartHeight;
    this.showAxis = this.args.showAxis;
  }

  @action
  buildChart() {
  //builds chart whith D3.js using carteiraDataSet and benchmarkDataSet
 }
}
//line-chart.hbs
<div class="justify-center">
  {{#if this.carteiraDataSet}}
    <div class="start-chart" 
      {{did-insert this.buildChart}}>
    </div>
  <div class="justify-center" id={{@chartId}}></div>
  {{else}}
    ... loading
  {{/if}}
</div>

I am using the Ember version 3.25.2.

I was confused about this because I used to write components like this lots of times and it always worked. Am I missing something on this? :thinking:

Hmmm yeah that looks pretty straightforward. With classic components it was easier to accidentally leak state into other components but with Glimmer components that’s a lot harder to I think you’re fine on that count. This seems like an obvious question but just in case, I assume the empty div in the line-chart.hbs is where the chart gets attached? Are you definitely passing a unique @chartId to each chart? I could see that “injecting” the same chart regardless of what data is passed to the owning component.

I guess where I would look next is putting a breakpoint in the constructor of line-chart.js and inspect the args passed to each chart, make sure the data sets are unique and expected, and make sure this.args.uniqueId is unique in each chart.

Thank you again! Yes, I’ve checked and each div has a unique ID to render the charts. I’ve spent some time investigating it and I think I am getting closer to the problem. I have this:

//chartsArray
[{"chartLabel": "DIV01", "chartInfo": [{}], "carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]},{"chartLabel":"DIV02", "chartInfo": [{}], "carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]},{"chartLabel":"DIV03", "chartInfo": [{}], "carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]},{"chartLabel":"DIV04", "chartInfo": [{}], "carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]},{"chartLabel":"DIV05", "chartInfo": [{}], "carteiraDataSet": [{...}], "benchmarkDataSet": [{...}]}]
//chart-loader.hbs - It fetches the charts data and put it inside chartsArray


{{#each this.chartsArray as |chart|}}
      <LineChart @carteiraDataSet={{chart.carteiraDataSet}} @benchmarkDataSet={{chart.benchmarkDataSet}} @chartId={{chart.chartLabel}} />
{{/each}}

Even though the chartsArray has different data set to each chart, I end up with 5 equal charts when the rendering finishes.

LineChart component renders the chart like this:

//line-chart-hbs
<div class="justify-center">
  {{#if this.isLoading}}
    ... loading
  {{else}}
    <div class="start-chart" 
      {{did-insert this.buildChart}}>
    </div>
  <div class="justify-center" id={{@chartId}}></div>
  {{/if}}
</div>

Every time it is calling buildChart to build the chart. I am suspecting that when I call did-insert it overrides everything, it builds each chart again with the current data on the loop. I am right now thinking how to write the same logic without using did-insert to see if it works.

What do you think? Does that make sense? :thinking:

But it doesn’t explain why the chart’s info (that is just some strings) is also being overwritten. Inside the chartArray there is also some info about the chart that is inside chartInfo. And then, inside the loop I am trying to render it:

//chart-loader.hbs
{{#each this.chartsArray as |chart|}}
      <LineChart @carteiraDataSet={{chart.carteiraDataSet}} @benchmarkDataSet={{chart.benchmarkDataSet}} @chartId={{chart.chartLabel}} />

      <ChartInfo @info={{chart.chartInfo}}/>
{{/each}}
//chart-info.hbs
<span class="inline-block text-sm font-bold">{{@info.chartLabel}}</span>
<p>{{@info.description}}</p>
<p><b>Data de início:</b> {{moment-format @info.startDate "DD/MM/YYYY"}}</p>

But at the end all charts info is also the same (the last one on the loop).

Hmmm I still would have thought it would be something to do with the D3 <=> Ember integration, like D3 not finding the right unique chart element and rendering hte last chart on all of them. But if it’s also duplicating ChartInfo, and that isn’t based on D3 (or is it?) that wouldn’t make sense…

If I were debugging something like this I’d probably throw a breakpoint in the buildChart method and step through it slowly looking at the data on the component/args and exactly what buildChart is doing. Then either you’d know the data on the component class is off (and therefore the problem is further “up” the component hierarchy or call stack) or it’s something to do with the chart construction/D3 code (so further “down” the component hierarchy/call stack).

Just managed to get it working! I checked every chart data and worked on some asynchronous calls. After all I’ve learned a lot about this asynchronous calls and how they work with EmberJS. Thanks for your help @dknutsen !

1 Like

Woohoo! Glad you got it figured out!