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.
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 propertiesInstead, @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 propertyAfter 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>
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
Most helpful comment
V2 now has a
useStepshook that you can use in custom components. I'll be adding docs soon, but peek at the source code forAppearin the meantime