So I just realized, while Frontity is built with React, itās pretty different than a website made with react like ustwo.com because Frontity is server side rendered. Static websites etc make for really fast load times, but does that mean itās not possible to do any preloading in the background or have it transition between pages?
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
What we did:
- We used the
useTransition
hook of react-spring and usedstate.router.link
to trigger the new animations:
const transitions = useTransition(state.router.link, link => link, {
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
});
- We put everything in
<Body>
inside a thetransitions
map returned byreact-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.
-
We passed that
data
to both<List>
and<Post>
components. They are not āhardcodedā tostate.router.link
anymore, they can render what you pass them in thedata
prop. This is needed because during the animation, both the new and the previous<Body>
need to coexist, but render different links. -
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
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.
Absolutely
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!
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).
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.
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
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!
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.
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 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
@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
@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
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.