Possible to do page transitions?

Sure, you can do page transitions with Frontity.

@David and I did a little experiment with react-spring. Itā€™s probably not perfect, but itā€™s a start :slight_smile:

What we did:

  1. We used the useTransition hook of react-spring and used state.router.link to trigger the new animations:
const transitions = useTransition(state.router.link, link => link, {
    from: { opacity: 0 },
    enter: { opacity: 1 },
    leave: { opacity: 0 }
  });
  1. We put everything in <Body> inside a the transitions map returned by react-spring:
{transitions.map(({ item, props, key }) => {
        const data = state.source.get(item);
        return (
          <animated.div key={key} style={props}>
            <Absolute>
              <Body>
                {data.isFetching && <Loading />}
                {data.isArchive && <List data={data} />}
                {data.isPostType && <Post data={data} />}
                {data.is404 && <Page404 />}
                {data.content}
              </Body>
            </Absolute>
          </animated.div>
        );
      })}

Instead of getting our data from the state.router.link variable, we use the link (called item) that transitions gives us. react-spring takes care of storing the previous value for us.

  1. We passed that data to both <List> and <Post> components. They are not ā€œhardcodedā€ to state.router.link anymore, they can render what you pass them in the data prop. This is needed because during the animation, both the new and the previous <Body> need to coexist, but render different links.

  2. We added a new styled component called <Absolute> that makes everything under <Body> absolute. This is needed because during the animation, both the new and the previous <Body> need to coexist and be on top of each other.

const Absolute = styled.div`
  position: absolute;
  width: 100%;
`;

As I said, itā€™s probably not perfect, but itā€™s a start :slight_smile:

3 Likes

Awesome, Iā€™m going to take a crack at this. I donā€™t know react super well yet, but I think I can follow this gist of what youā€™ve got here, considering I converted a wordpress theme to use transitions using a vanilla javascript plugin previously and I have some sense of whatā€™s going on here.

This seems to me something that might be good to introduce into the core functionality of frontity. For me the main reason for wanting a React frontend is because it can feel snappier due to the virtual dom and the ability to preload assets in the background. Page transitions are a big part of enhancing that snappiness and making it feel clean and not jarring.

1 Like

Absolutely :slight_smile:

It took us a while to understand how react-spring works but, once we got it, it was super simple.

Hey started trying to implement the transitions according to your codesandbox example today and Iā€™m getting a fade in on stuff but Iā€™m getting errors when I click a link.

This is probably because Iā€™ve changed a lot of things in the them Iā€™m working on (such as changing the name of ā€œlist.jsā€ to ā€œarchiveā€, and some refactoring of the component structure in index.js according to what seemed simpler (such as turning HeadContainer>Header into just Header).

Hereā€™s the errors Iā€™m getting from the dev tools console:

Uncaught TypeError: Cannot read property 'map' of undefined
    at Archive (archive.js?e9fa:10)
    at runAsReaction (reactionRunner.js?30ae:54)
    at reaction (observer.js?724e:8)
    at eval (connect.js?84b5:70)
    at renderWithHooks (react-dom.development.js?61bb:16235)
    at updateFunctionComponent (react-dom.development.js?61bb:18310)
    at updateSimpleMemoComponent (react-dom.development.js?61bb:18257)
    at beginWork$1 (react-dom.development.js?61bb:20217)
    at HTMLUnknownElement.callCallback (react-dom.development.js?61bb:332)
    at eval (scheduler.js?f1c4:8)
Archive @ archive.js?e9fa:10
runAsReaction @ reactionRunner.js?30ae:54
reaction @ observer.js?724e:8
eval @ connect.js?84b5:70
renderWithHooks @ react-dom.development.js?61bb:16235
updateFunctionComponent @ react-dom.development.js?61bb:18310
updateSimpleMemoComponent @ react-dom.development.js?61bb:18257
beginWork$1 @ react-dom.development.js?61bb:20217
callCallback @ react-dom.development.js?61bb:332
eval @ scheduler.js?f1c4:8
batchedUpdates$1 @ react-dom.development.js?61bb:24344
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
invokeGuardedCallbackDev @ react-dom.development.js?61bb:384
invokeGuardedCallback @ react-dom.development.js?61bb:437
beginWork$$1 @ react-dom.development.js?61bb:25738
performUnitOfWork @ react-dom.development.js?61bb:24655
workLoopSync @ react-dom.development.js?61bb:24637
performSyncWorkOnRoot @ react-dom.development.js?61bb:24228
eval @ react-dom.development.js?61bb:12169
unstable_runWithPriority @ scheduler.development.js?3069:788
runWithPriority$2 @ react-dom.development.js?61bb:12071
flushSyncCallbackQueueImpl @ react-dom.development.js?61bb:12169
flushSyncCallbackQueue @ react-dom.development.js?61bb:12146
batchedUpdates$1 @ react-dom.development.js?61bb:24359
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
backend.js:6 The above error occurred in one of your React components:
    in Unknown (created by InnerLoadable)
    in InnerLoadable (created by Context.Consumer)
    in Unknown (created by ForwardRef)
    in ForwardRef
    in div (created by Context.Consumer)
    in Styled(div)
    in div (created by Context.Consumer)
    in Styled(div)
    in div (created by ForwardRef)
    in ForwardRef
    in Unknown (created by App)
    in HelmetProvider (created by App)
    in App

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.
r @ backend.js:6
logCapturedError @ react-dom.development.js?61bb:21810
logError @ react-dom.development.js?61bb:21810
update.callback @ react-dom.development.js?61bb:23192
callCallback @ react-dom.development.js?61bb:13794
commitUpdateEffects @ react-dom.development.js?61bb:13836
commitUpdateQueue @ react-dom.development.js?61bb:13836
commitLifeCycles @ react-dom.development.js?61bb:22102
commitLayoutEffects @ react-dom.development.js?61bb:25295
callCallback @ react-dom.development.js?61bb:332
eval @ scheduler.js?f1c4:8
batchedUpdates$1 @ react-dom.development.js?61bb:24344
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
invokeGuardedCallbackDev @ react-dom.development.js?61bb:384
invokeGuardedCallback @ react-dom.development.js?61bb:437
commitRootImpl @ react-dom.development.js?61bb:25035
unstable_runWithPriority @ scheduler.development.js?3069:788
runWithPriority$2 @ react-dom.development.js?61bb:12071
commitRoot @ react-dom.development.js?61bb:24840
finishSyncRender @ react-dom.development.js?61bb:24288
performSyncWorkOnRoot @ react-dom.development.js?61bb:24271
eval @ react-dom.development.js?61bb:12169
unstable_runWithPriority @ scheduler.development.js?3069:788
runWithPriority$2 @ react-dom.development.js?61bb:12071
flushSyncCallbackQueueImpl @ react-dom.development.js?61bb:12169
flushSyncCallbackQueue @ react-dom.development.js?61bb:12146
batchedUpdates$1 @ react-dom.development.js?61bb:24359
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
react-dom.development.js?61bb:12192 Uncaught TypeError: Cannot read property 'map' of undefined
    at Archive (archive.js?e9fa:10)
    at runAsReaction (reactionRunner.js?30ae:54)
    at reaction (observer.js?724e:8)
    at eval (connect.js?84b5:70)
    at renderWithHooks (react-dom.development.js?61bb:16235)
    at updateFunctionComponent (react-dom.development.js?61bb:18310)
    at updateSimpleMemoComponent (react-dom.development.js?61bb:18257)
    at beginWork$1 (react-dom.development.js?61bb:20217)
    at HTMLUnknownElement.callCallback (react-dom.development.js?61bb:332)
    at eval (scheduler.js?f1c4:8)
Archive @ archive.js?e9fa:10
runAsReaction @ reactionRunner.js?30ae:54
reaction @ observer.js?724e:8
eval @ connect.js?84b5:70
renderWithHooks @ react-dom.development.js?61bb:16235
updateFunctionComponent @ react-dom.development.js?61bb:18310
updateSimpleMemoComponent @ react-dom.development.js?61bb:18257
beginWork$1 @ react-dom.development.js?61bb:20217
callCallback @ react-dom.development.js?61bb:332
eval @ scheduler.js?f1c4:8
batchedUpdates$1 @ react-dom.development.js?61bb:24344
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
invokeGuardedCallbackDev @ react-dom.development.js?61bb:384
invokeGuardedCallback @ react-dom.development.js?61bb:437
beginWork$$1 @ react-dom.development.js?61bb:25738
performUnitOfWork @ react-dom.development.js?61bb:24655
workLoopSync @ react-dom.development.js?61bb:24637
performSyncWorkOnRoot @ react-dom.development.js?61bb:24228
eval @ react-dom.development.js?61bb:12169
unstable_runWithPriority @ scheduler.development.js?3069:788
runWithPriority$2 @ react-dom.development.js?61bb:12071
flushSyncCallbackQueueImpl @ react-dom.development.js?61bb:12169
flushSyncCallbackQueue @ react-dom.development.js?61bb:12146
batchedUpdates$1 @ react-dom.development.js?61bb:24359
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
backend.js:6 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    in Unknown
    in div (created by Context.Consumer)
    in Styled(div)
    in div (created by Context.Consumer)
    in Styled(div)
    in div (created by ForwardRef)
    in ForwardRef
    in Unknown (created by App)
    in HelmetProvider (created by App)
    in App
r @ backend.js:6
warningWithoutStack @ react-dom.development.js?61bb:530
warnAboutUpdateOnUnmountedFiberInDEV @ react-dom.development.js?61bb:25684
scheduleUpdateOnFiber @ react-dom.development.js?61bb:23635
dispatchAction @ react-dom.development.js?61bb:17051
scheduler @ connect.js?84b5:52
queueReaction @ reactionRunner.js?30ae:25
queueReactionsForOperation @ reactionRunner.js?30ae:75
set @ handlers.js?c9ee:84
eval @ actions.ts?f1c8:48
asyncGeneratorStep @ actions.ts?f1c8:2
_next @ actions.ts?f1c8:2
eval @ scheduler.js?f1c4:8
batchedUpdates$1 @ react-dom.development.js?61bb:24344
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
Promise.then (async)
apply @ scheduler.js?f1c4:33
asyncGeneratorStep @ actions.ts?f1c8:2
_next @ actions.ts?f1c8:2
eval @ actions.ts?f1c8:2
eval @ actions.ts?f1c8:2
eval @ actions.ts?f1c8:58
eval @ create-store.js?81b0:1
eval @ actions.ts?fe69:12
eval @ create-store.js?81b0:1
onClick @ link.js?564e:11
callCallback @ react-dom.development.js?61bb:332
eval @ scheduler.js?f1c4:8
batchedUpdates$1 @ react-dom.development.js?61bb:24344
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
invokeGuardedCallbackDev @ react-dom.development.js?61bb:384
invokeGuardedCallback @ react-dom.development.js?61bb:437
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js?61bb:453
executeDispatch @ react-dom.development.js?61bb:581
executeDispatchesInOrder @ react-dom.development.js?61bb:606
executeDispatchesAndRelease @ react-dom.development.js?61bb:711
executeDispatchesAndReleaseTopLevel @ react-dom.development.js?61bb:711
forEachAccumulated @ react-dom.development.js?61bb:690
runEventsInBatch @ react-dom.development.js?61bb:732
runExtractedPluginEventsInBatch @ react-dom.development.js?61bb:864
handleTopLevel @ react-dom.development.js?61bb:5778
batchedEventUpdates$1 @ react-dom.development.js?61bb:24359
batchedEventUpdates @ react-dom.development.js?61bb:1413
dispatchEventForPluginEventSystem @ react-dom.development.js?61bb:5898
attemptToDispatchEvent @ react-dom.development.js?61bb:6011
dispatchEvent @ react-dom.development.js?61bb:5914
unstable_runWithPriority @ scheduler.development.js?3069:788
runWithPriority$2 @ react-dom.development.js?61bb:12071
discreteUpdates$1 @ react-dom.development.js?61bb:24384
discreteUpdates @ react-dom.development.js?61bb:1426
dispatchDiscreteEvent @ react-dom.development.js?61bb:5846
eval @ scheduler.js?f1c4:8
batchedUpdates$1 @ react-dom.development.js?61bb:24344
batch @ scheduler.js?f1c4:8
batched @ scheduler.js?f1c4:17
Show 26 more frames

I donā€™t want to seem like Iā€™m dumping a bunch of trash on yaā€™ll btw. Iā€™m not super experienced with react, and my javascript skills are still pretty rudimentary, but Iā€™m doing my best to figure it out on my own as much as I can, doing a bunch of googling and continuing to try isolate the cause of the problem. If thereā€™s more info yaā€™ll need let me know.

EDIT 1:

If I click one of the posts and it bugs out, I then can refresh the page and the page will load correct. But I do get the following error in the console:

react-dom.development.js?61bb:530 Warning: React does not recognize the `allowTransparency` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `allowtransparency` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
    in iframe
    in div
    in figure
    in Unknown
    in article (created by Context.Consumer)
    in Styled(article)
    in Unknown
    in div (created by Context.Consumer)
    in Styled(div)
    in div (created by Context.Consumer)
    in Styled(div)
    in div (created by ForwardRef)
    in ForwardRef
    in Unknown (created by App)
    in HelmetProvider (created by App)
    in App

EDIT2:

Okay so Iā€™m diffing the current version of mars-theme I have downloaded against the codesandbox example you gave so that I can double check to see if you made any changes to files other than just index.js.

I found that one change was that post.js has const Post = ({ state, actions, libraries, data }) => { Since data is one of the things that weā€™re dealing with in the index I added that to my post.js. Now the app does something different! It still errors out when I try to go from the frontpage to a post, but when Iā€™m at a frontpage and I click the home button it does correctly load it! IDK whyā€¦ I feel kind of silly, like Iā€™m cargo cult coding where Iā€™m just copying and pasting things according to forms created by other people without any understanding but itā€™s the best Iā€™ve got right now. I hope that by doing this I can at lest internalize a little more of react. Iā€™m still such a novice at it.

EDIT 3:

WOOO!!! I got it working! It now transitions properly between posts and the archive page.

It seems the important thing is just that I delete the const data = state.source.get(state.router.link); and add data to ({ state, data }) .

Why? IDKā€¦

But let me reason this outloud and maybe you can confirm.

Well before the component Post was getting itā€™s const data defined by the state.source.get(state.router.link).

Which broken down means:

state.source
source refers to wp-source right? So the component state contains the Wordpress data in the source object right?

.get
so this is a method of source to get:

state.router.link
I havenā€™t actually learned what routing is yet in react. I hear about it all the time but Iā€™m not really clear on how it works or what it means.

But I can summazie that state is knowing what information to get via the stateā€™s routing link object. yeah?

Thatā€™s how stuff USED to work.

But with the page transition stuff the way it works is that data is passed along with state. Where does data get defined?

It gets defined in state.source.get(item).

Where does state.source.get(item) get defined? it gets defined as a parameter of transitions.map? ā€¦ I guess? Iā€™m not quite sure how transitions.map knows where to get the argument to fill the item parameter. This is an aspect of a lot of javascript code that Iā€™m still kind of iffy on understand overall tbh. Like I understand passing arguments in paramters of functions etc, in the abstract but how this works at a library/depency level in between stuff is still unclear to me.

Iā€™m glad you finally got it working! :blush:

As you say, how Router and Source work is not clear right now. Itā€™s one aspect we want to work on and we hope to release new documentation about this soon.

In the meantime, Iā€™ll try to explain better your questions. When you create a project in Frontity, the routing is handled by the Frontity package @frontity/tiny-router, which basically let you set the url with actions.router.set() and detects where are you and stores it at state.router.link.

State is an object where we store all the data necessary, and in state.source we are storing all the data related to the API where you are getting data from, in this case wp-source (although you could create a new one if you want).

Screenshot%20from%202019-11-13%2013-06-40

You can take a deeper look at it going to your website (or mars.frontity.org), opening the dev tools and writing in the console frontity.state.source.

There you could find all the data about the posts, categories, authors, etcā€¦ you fetch from WordPress. And also another important object named data.

In order to make it easier and preventing you from learning WP REST API, we decided to manage this data using URLs instead of the common endpoints. This means that in source.data we store the important data in the urls: if it has been fetched, if itā€™s home, if itā€™s a post, a category, the id, etc.
Screenshot%20from%202019-11-13%2013-13-59
From there, you can look for the rest of the info at the state when needed.

If you want to access the data of one url, you can use the method get with the url as an argument. For example, if we want to get the data from "/category/nature/" we just have to use: state.source.get("/category/nature/") and weā€™ll be accessing state.source.data["/category/nature/"].

Note that in order to have the data available in the state, is has to be fetched before with actions.source.fetch() . By default Frontity does the fetch once you access one url, but if you change that behavior you have to make sure to do the fetch before getting the data.

So letā€™s try to explain your example too: const data = state.source.get(state.router.link)
We are basically getting the data of the current url. state.route.link, as explained before, gives you the current url, and state.source.get() gets the data from the state.

I hope this info is enough to give you a better understanding about how does it work and help you with the page transitions. Weā€™ll inform you when we have better documentation for this.

Please let us know if you have more questions :slightly_smiling_face:

Thanks this was helpful. Iā€™m also continuing my classes in react. I just learned some basic routing using reactā€™s built in router component. So Iā€™m getting there!

2 Likes

Can anyone help with implementing this on the twentytwenty theme which uses Switch?

Hi @John_Jordan,

What exactly do you want to do? It would be great if you can provide a repo to be able to take a look at your idea. If you canā€™t share your whole project, please create a CodeSandbox (you can start with this template) or a GitHub repository with the minimal amount of code to reproduce your idea.

In the meantime, @thedonquixotic just shared his custom Frontity theme that implements animations. It may serve as inspiration for your project

Looking forward to see what youā€™re building with Frontity

Hi there,

Thanks for getting back to me. Will have a look at the example you sent me. I was going to pull out the parts I can share and get an idea of what im trying to do in to a repo so others can also learn from what I am doing.

Best,

John

My main questions was about using the component and when it renders a different part of the site have that animate in.

Thanks

I have updated this repository with the lastet version of mars-theme, which is using Switch.

Demo: https://mars-theme-wth-transitions.now.sh/

Would it be possible to get an update on this? Reat-spring no longer has the .map function on transitions. I tried using the same code from your github link, and I receive the following error:

Cannot destructure property ā€˜resetā€™ of ā€˜(intermediate value)(intermediate value)(intermediate value)ā€™ as it is undefined.

I think this may have something to do with the (link) => link parameter you use in useTransition (I canā€™t find any such usage in the react-spring docs, so maybe that is an old syntax pattern you could explain?

I tried creating an example with the current react-spring model as follows:

const data = state.source.get(state.router.link)
  const location = state.router.link
  const transitions = useTransition(location, {
    from: { opacity: 0,},
    enter: { opacity: 1, },
    leave: { opacity: 0, },
    config: { duration: 1000 },
  });

{transitions((props, item) => (
                <animated.div style={props}>
                  <Switch location={item}>
                    <RegistrationPage when={data.isRegistrationPage}/>
                    <Page when={data.isPage}/>
                  </Switch>
                  <Footer />
                </animated.div>
            ))}

As you might have guessed, the frontity page switches before the transition takes effect so I see a lovely fade-in and fade-out of the page I just navigated to. I have tried several different combinations to try to get this to work, and I thought you might have some insight.

@gideonjb Youā€™re correct, it looks like react-spring has updated their API :slight_smile: However, I would expect it to work quite similarly looking at this example: react-spring

Could you post a link to a codesandbox / stackblitz with your issue? What do you expect to happen and what is happening if you use the snippet that youā€™ve posted?

I believe that you are also missing a container element which should be adding position: absolute to your page as well as you should probably get the data for the current link inside of the transition callback.

whilst the code above should be self-explanatory, if anybody prefers to watch video walkthroughā€¦ Web Dev Simplified goes through it here https://www.youtube.com/watch?v=whsYQ9fb64k&t=643s

1 Like

@codemonkeynorth this is what I was referring to above. He uses transition.map in that video which is old syntax for react-spring, and therefore no longer works if you are updated to the current version

1 Like

@gideonjb Does adding display: none to the leave event not help? (as he explains on the video). It stops both transitions happening at the same time

Iā€™ll have to fire up a demo and check

Thanks @mmczaplinski, I donā€™t have a sandbox to share as of yet, but for the moment, I can post a screenshot of whatā€™s happening. I also thought that it would make sense to have the current link inside the transition callback. That requires you to put a closure and a return statement around it, which I tried like so:

{transitions((props, item) => {
     const dataitem = state.source.get(state.router.link)
     return(
          <animated.div style={props}>
               <Switch location={item}>
                    <RegistrationPage when={dataitem.isRegistrationPage}/>
                    <Page when={dataitem.isPage}/>
                </Switch>
                <Footer />
           </animated.div>
)})}

I left out the position: ā€œabsoluteā€ on purpose so you could see what was happening. As you will see in the screenshot, both sections of the page have navigated to the link I used. Itā€™s as if react-spring is not correctly storing the old data during transition, which I assumed was a result of miscoding, but perhaps it is something else.

@gideonjb Does using a unique key in the useTransition function help? See the codesandbox example at the bottom of here Replicating v8 useTransition behaviour without 'unique' prop Ā· Discussion #1027 Ā· pmndrs/react-spring Ā· GitHub

@codemonkeynorth

I thought you had found the right solution when I was looking at the code, and in principle I think the idea behind whatā€™s happening in that codesandbox is what I need.

However, I tried using the id generated from state.router.link and placing it as the key for useTransition. This appears to break the whole thing. Oddly, when I reload a page, it will fade in, but when I switch routes, it is as if react-spring does not exist.

Looking at your code it seems like youā€™re not using the item from the transitions mapping to display the page. Youā€™re getting the page data from the router in your transition. What you should instead do is getting the page data from the transition item.

Here is a working example of how Iā€™m doing page transitions in my index.js using the latest react-spring API.

[...]
    // Page transitions
    const transitions = useTransition(state.router.link, {
        from: { opacity: 0 },
        enter: { opacity: 1 },
        leave: { opacity: 0 },
        config: { duration: 200 }
    });

  return (
        <>
            <Styles/>
            <HtmlHead/>
            <Sidebar isNavVisible={state.theme.isNavVisible} />
            {transitions((style, route) => {
                const data = state.source.get(route)
                return (
                    <TransitionWrapper style={style}>
                        <Switch>
                            <Loader when={data.isFetching} />
                            <List when={data.isArchive} route={route} />
                            <Post when={data.isPost} route={route} />
                            <Page when={data.isPage} route={route} />
                            <Error when={data.isError} />
                        </Switch>
                    </TransitionWrapper>
                )}
            )}
        </>
    )
[...]

You can then fetch your page/post data in the page/post component.

[...]

const Page = ({ state, libraries, route }) => {
    const data = state.source.get(route)
    const page = state.source[data.type][data.id]
[...]

Hope that helps! :slight_smile:

3 Likes