jxnblk / mdx-deck

♠️ React MDX-based presentation decks
https://mdx-deck.jxnblk.com
MIT License
11.33k stars 604 forks source link

Mechanism to retrieve previous index/step #419

Open springroll12 opened 5 years ago

springroll12 commented 5 years ago

Is there a way to determine which slide was last visited at render time?

It would be very helpful to the following use cases:

  1. Slide transitions: If you need to establish the "direction" you are moving in the slide deck to provide different animations (e.g. slide in/out animations similar to previous versions of mdx-deck)

  2. Customizing Appear: Determine what should happen on a step within an Appear. Similar to #193.

Maybe this can be accomplished through reach router history but I haven't found a way to do so yet.

jxnblk commented 5 years ago

I think you could use the useDeck hook to keep track of that somewhere, but not entirely sure I understand what you’re trying to do

https://github.com/jxnblk/mdx-deck/blob/master/docs/api.md#usedeck

springroll12 commented 5 years ago

I'm not sure that useDeck is enough, but maybe I'm missing something.

For the first use case, I'm trying to recreate the slide transitions from earlier versions of mdx-deck default theme. (An example here: https://simply-react.netlify.com/). It is a basic slide-in and slide-out animation.

If you are moving forwards in the slide deck, you want slides to "exit" to the left and "enter" from the right. However if you're going backwards through the deck, you want the opposite. I think this is difficult to achieve without being able to establish the direction the user is headed in the slide deck. Unless I'm missing something, the only ways to do this are:

  1. Somehow compare urls of previous slides through the history api, or

  2. Compare the current slide index to the previous slide index (or step)

As far as I know useDeck does not expose the previous slide index, only the current slide.

jxnblk commented 5 years ago

Yeah, the “direction” is something you’d need to keep track of but useDeck will give you the current slide and step

springroll12 commented 5 years ago

Still struggling with this.

In order to achieve directional transitions, I need to store the previous slide index in some sort of global state store. But so far I haven't figured out where to store a state variable that would be shared with all slides?

Since each slide needs to be wrapped in an individual layout component, you should not store state in these layouts, because even if two slides have the same layout, they will not share state since they are different instances of the same component.

Is the Provider the right place to store this? If so, how might I pass it down to a layout component? It doesn't appear that props are passed from Provider->Layout directly, and from my reading of Slide.js each slide has its own context provider.

Maybe a better idea would be for me to create a new useDeckEnhanced hook that contains the context I need?

Any guidance you could provide would be greatly appreciated.

jxnblk commented 5 years ago

Right, so to store the state that you'd need for this I think you'd want the following:

springroll12 commented 5 years ago

Thank you for pointing me in the right direction!

I think I'm almost there, but I think I'm struggling to understand how useState/useEffect should be used in the custom provider. It seems that the provider renders a few extra times with the default state values at the end of a slide transition (e.g. 1->2).

Here is my custom provider:

import React, { useState, useLayoutEffect, useEffect } from 'react'
import { useDeck } from 'mdx-deck'
import ExtraContext from '../hooks/ExtraContext'

export default props => {
    const state = useDeck();
    const [ prevIndex, setPrevIndex ] = useState(state.index);
    const [ direction, setDirection] = useState(10); // Set this to 10 to make it easier to distinguish the initial value
    useLayoutEffect(() => {
        let dir = 1;
        console.log("state.index: " + state.index);
        console.log("prevIndex: " + prevIndex);
        if (state.index !== prevIndex) {
            if (prevIndex > state.index) {
                dir = -1;
            }
            if (dir !== direction) {
                console.log("useEffect setting direction: " + dir);
                setDirection(dir);
            }
        }
    }, [state.index]);

    useEffect(() => {
        return () => {
            console.log("Setting prevIndex: " + state.index);
            setPrevIndex(state.index);
        }
    }, []);
    console.log("FINAL DIR: " + direction); // Prints the "correct" value, then the inital value on a subsequent render??
    console.log("======================\n");
    return (
        <div>
            <ExtraContext.Provider value={{direction: direction}}>
                {props.children}
            </ExtraContext.Provider>
        </div>
    )
}

Note that I am using a custom layout as well which reads the context value with ExtraContext.Consumer. No state changes are made beyond the custom provider, so hopefully reading the context doesn't impact how the provider renders.

Some console output for one slide transition with this custom provider. Note that it renders several times (beyond what seems to be needed by the state updates done in useEffect/useLayoutEffect) and finishes with the initial value for the direction state.

FINAL DIR: 10
provider.js:1 ======================

provider.js:1 state.index: 1
provider.js:1 prevIndex: 0
provider.js:1 useEffect setting direction: 1
provider.js:1 FINAL DIR: 1
provider.js:1 ======================

provider.js:1 FINAL DIR: 1
provider.js:1 ======================

provider.js:1 FINAL DIR: 10
provider.js:1 ======================

provider.js:1 Setting prevIndex: 0
provider.js:1 state.index: 1
provider.js:1 prevIndex: 1
provider.js:1 FINAL DIR: 10
provider.js:1 ======================

I have a feeling that there is something about the useState/useEffect combination that I have not figured out yet, but I thought I would ask here to ensure the problem I'm seeing is not related to how the Provider renders in mdx-deck.

springroll12 commented 5 years ago

Another quick test with a basic custom provider and basic layout seems to indicate that the provider renders multiple (3) times per slide transition:

Provider.js

import React from 'react'

export default props => {
    console.log("render provider");
    return (
        <div>
            {props.children}
        </div>
    )
}

This shouldn't be an issue since useState should use the state values from previous renders, but it appears it does not? Very strange.

I also tested with a basic useState/useEffect to see if the state changes are picked up on subsequent renders, but it seems they are not:

Provider.js

import React, { useState, useEffect } from 'react'

export default props => {
    console.log("render provider");
    let [ test, setTest ] = useState(true);
    useEffect(() => {
        setTest(false);
    }, []);
    console.log("test: " + test);
    return (
        <div>
            {props.children}
        </div>
    )
}

Console output:

render provider
test: false
render provider
test: true
render provider       << Why does this last render occur?
test: false

Maybe the Provider is not the right place to put state changes?