function DagGraph() {
const steps = useState<Jobs[]>([]);
const selectedJobIdx = useState<number | null>(null);
return ...;
}
In elm, I lift this up (I will keep using React syntax for the sake of wider-audience readability) function DagGraph({ steps, selectedJobIdx } : IDagGraphModel) {
return ...;
}
This allows for better testability of the graph view. The parent then injects the model function PipelinePage({ graphMode, selectedPipeline }: IPipelineModel) {
return (
graphMode === GraphMode.DagView
? <DagGraph steps={selectedPipeline.steps} selectedJobIdx={selectedPipeline.selectedStep} />
: <TableView steps={selectedPipeline.steps} selectedJobIdx={selectedPipeline.selectedStep} />
);
}
Now -- we need to bubble up the selected pipeline index based on when the user clicks a node in the DAG view. I personally prefer to do this completely orthogonally to the view rendering pipeline (which remains mostly pure). I create a new model, and define the message relationships to it: namespace PipelineState {
interface Model {
steps: Step[];
selectedStep: number | null;
};
interface ISelectStepMessage { stepIdx: number };
interface IUnselectStepMessage;
type Message = ISelectStepMessage | IUnselectStepMessage;
function update(m: Model, msg: Message): Model { return...; }
}
// main model
interface Model {
pipeline: PipelineState.Model,
}
type Message = PipelineState.Message | SomeOtherMessage | ...;
function update(m: Model, msg: Message): Model {
switch (msg) {
case PipelineState.Message as msg:
return update(m, msg);
...
}
}
And this seems to work quite well. Personally, I really like this architecture, because it enables me to think about state and UI separately. One of the really big gripes I have with React is how most components end up with `useEffect` and `useState`, and then immediately become untestable. Elm literally doesn't allow for that.