Mdx-deck: Advanced step control

Created on 8 Oct 2018  路  5Comments  路  Source: jxnblk/mdx-deck

Following the discussion on #177, a feature I would love to have is an advanced form of Appear that would give great control on what happens at each step (i.e. not necessarily only making a component appear, but anything really).

My main use case is the control of D3.js updates, like filtering some data on a step, highlighting one particular bar, etc.

Original idea

My original idea was a Higher Order Component:

import withSteps from 'mdx-deck/with-steps'

withSteps(numberOfSteps, ({ step }) => {
  // Render something using step.
  return <span>Current step: {step}</span>
})

The above is just a functional component, but withSteps would also allow relying on componentDidUpdate to perform some low-level DOM operations, like updating a D3 chart:

import withSteps from 'mdx-deck/with-steps'

withSteps(numberOfSteps, class Chart extends React.Component {
  constructor(props) {
    super(props)
    this.svgNode = React.createRef()
  }
  componentDidUpdate() {
    const { step, data } = this.props
    updateChart(this.svgNode.current, data, step)
  }
  render() {
    return <svg ref={this.svgNode} />
  }
})

However, as mentioned by @zcei, the issue with this approach is that it is not supported in mdx. As a result, it would require to create the component in a separate file (https://github.com/jxnblk/mdx-deck/issues/177#issuecomment-427588751).

step and setStep as properties

Instead, @zcei suggests a simple component that would provide step and setStep to its child (https://github.com/jxnblk/mdx-deck/issues/177#issuecomment-427579253):

<Steps>
  {({ step, setSteps }) => {
     // Do something with step and setSteps
  }}
</Steps>

However, I believe passing setSteps as a property is a bit weird. With functional components, it makes them intrinsically impure. Also, when should setSteps be called? If the child is a functional component, that means it can only be called at render time 馃樀... And what happens if one calls setSteps with a value lower than step?

totalSteps on the parent, and setStep as a property

After some thoughts, an alternative could be:

<Steps totalSteps={4}>
  {({ step }) => {
     // Do something with step
  }}
</Steps>

Note that I really wish <Steps> would not only accept functional components but also class component. This is necessary to support low level DOM operations. Maybe:

<Steps totalSteps={4}>
  {class SomeThing extends React.Component { /* */ }}
</Steps>

Most helpful comment

V2 now has a useSteps hook that you can use in custom components. I'll be adding docs soon, but peek at the source code for Appear in the meantime

All 5 comments

As your main concern about the <Step> component seems to be the lifecycle methods, I'm just dropping two links in here:

"Answers to common questions about render props" by Kent C. Dodds which covers lifecycle methods as well and

render-props a library as a result of above's article.

I think with this we could easily support functional components and your desired class components as well, without the need to change how MDX works to enable HoC.

I'm still not too sure about the setSteps, especially about calling it in the constructor, but I basically used this pattern as it was already in mdx-deck.

Yes absolutely. The point is to make lifecycle hooks available.

I don't think it is hard to do either. I don't think a new dependency is required for this. I emphasized it just to make sure it does not get lost.

Thanks, will add more thoughts to this later, but I don鈥檛 think this proposed component should have anything to do with lifecycle hooks. That could be part of how you construct your D3 Component internally if needed, but this library鈥檚 components should only expose props for steps

I agree. I am not saying it should be managed by the library. I think it would be a terrible idea to support different properties for every lifecycle hooks for example. But I wish the library would allow someone to add these hooks to their own component and integrate it easily with mdx-deck. For example, If mdx-deck only supports render functions, one is forced to add an indirection, and develop the component in a separate file:

<Step totalSteps={3}>
  {({ step }) => <Component step={step} />}
</Step>

But if it also supports component injection (which means it supports both ways of specifying React component, functional and class), the above indirection is not needed anymore:

<Step totalSteps={3}>
  {Component}
</Step>

and one may even put the code of the component class directly inside <Step /> (as shown previously).

I believe this is very little more work, but would make my use case much more easy.

V2 now has a useSteps hook that you can use in custom components. I'll be adding docs soon, but peek at the source code for Appear in the meantime

Was this page helpful?
0 / 5 - 0 ratings

Related issues

busypeoples picture busypeoples  路  4Comments

trevordmiller picture trevordmiller  路  4Comments

DSchau picture DSchau  路  5Comments

wesbos picture wesbos  路  6Comments

sibelius picture sibelius  路  3Comments