It sounds like you are using mutation, and in particular mutation across cells. This is considered an antipattern in Observable because it doesnât work well with dataflow (as youâve discovered).
If you want to split the work across multiple cells, instead of mutating the values in place, you should return a transformed copy of the data. For example, say you have data as an array of strings:
data = [
"2022-01-01",
"2022-01-02",
"2022-01-03",
"2022-01-04",
"2022-01-05",
âŚ
]
Using mutation, you might say:
{
for (let i = 0; i < data.length; ++i) {
data[i] = new Date(data[i]);
}
}
After the above cell runs, data
will be an array of dates instead of an array of strings. But the problem is that any other cell that references data
, the behavior is undefined as to whether that other cell sees data
as an array of strings or an array of dates. And furthermore when the cell runs that mutates data
, it wonât cause any other cell that references data
to run again automatically. (See How Observable Runs.)
So instead, create a transformed copy of the data, and make the dependencies in your code explicit.
strings = [
"2022-01-01",
"2022-01-02",
"2022-01-03",
"2022-01-04",
"2022-01-05",
âŚ
]
dates = strings.map(s => new Date(s))
Now any cell that references dates
will see an array of dates. And if you change your transformation logic by editing the dates
cell, or if you change your data by editing the strings
cell, anything downstream will run again automatically.
I mention this topic in Learn D3: Data:
A subtle consideration when working with data in Observable is whether to put code in a single cell or separate cells. A good rule of thumb is that a cell should either define a named value to be referenced by other cells (such as data above), or it should display something informative to the reader (such as this prose, the chart above, or cells which inspect the data).
A critical implication of this rule is that you should avoid implicit dependencies between cells: only depend on what you can name.
Why? A notebookâs dataflow is built from its cell references. If a cell doesnât have a name, it cannot be referenced and cells that implicitly depend on its effect may run before it. (Cells run in topological order, not top-to-bottom.) Implicit dependencies lead to nondeterministic behavior during development, and possible errors when the notebook is reloaded!
For example, if one cell defines an array and a second cell modifies it in-place, other cells may see the array before or after the mutation. To avoid this nondeterminism, make the dependency explicit by giving the second cell a name and copying the array using Array.from or array.map. Or combine the two cells so that only the already-modified array is visible to other cells.
If you do want to use mutation, you can also limit the effect to within a single cell, even if your code is written in multiple cells, by using functions. For example:
function coerceToDates(data) {
for (let i = 0; i < data.length; ++i) {
data[i] = new Date(data[i]);
}
return data;
}
data = coerceToDates([
"2022-01-01",
"2022-01-02",
"2022-01-03",
"2022-01-04",
"2022-01-05",
âŚ
])
You wonât be able to inspect the value of data
prior to coercion, but you can call your function in another cell to test and debug its behavior. Writing unit tests for such functions is often effective at finding bugs.