We will use the three new libraries we've just learnt about in the course - Linear Gradient, Masked View and Reanimated 2 - to add an animation effect to our skeleton loader.
Linear Gradient Expo | Pure React Native
Masked View Expo | Pure React Native
Reanimated 2 Expo | Pure React Native
Finished code is available on Expo Snack.
Instructor: [0:00] Here we have an app that renders a list of fruit. You can see that we're emulating a two-second loading time. If we reload the app, we'll see a little skeleton placeholder before the actual data gets loaded in.
[0:15] Now while working on this, let's comment out this if statement, and let's just show the loader. Now our loader is static, so it doesn't move at all. Most skeleton loaders have some kind of animation to make the user feel like something is happening behind the scenes.
[0:30] To do this, let's create a new file and let's call it skeleton loader. In this file, let's import React and let's export const skeletal loader. From the props, let's destructure the children. Let's also import view from React Native.
[0:48] From our skeleton loader, let's just return a view with the children inside. This component will return whatever is being passed into it. In our item list loader, let's import skeleton loader.
[1:03] Let's wrap the whole of item list loader inside the skeleton loader. The idea here is in the future, you could pass any type of content into the skeleton loader, and you will handle doing the animation.
[1:15] Next up, we'll need to know the size of this view once it's been rendered and have this work with any content. Let's use a useState. We'll do a layout and set layout. We'll do a React useState with no initial value. Now we can do if not layout, so layout is undefined.
[1:36] We will return this view with the children inside, and we'll use the on layout property on the view which gets called once the view is rendered. This returns a event. We will call set layout with the event, Native event, and layout.
[1:58] This will give us the layout rectangle with the width and the height of the rendered item. Otherwise, let's return the children. Next, let's import masked view from React Native masked view. Let's return a masked view at the bottom.
[2:16] We can use the children as the masked element. We want our masked view to be the size of whatever our children is. We'll do style = width layout.width, and height layout.height.
[2:35] This will set the width and height of this masked view to be whatever the width and height of the rendered children would be.
[2:43] In order for this masked view to actually display something, we'll need to also pass in something for it to mask. Let's pass in a view. Let's import style sheet from React Native. Let's create our styles object at the bottom. Const styles = stylesheet crates.
[3:01] We'll do a background and let's give this a flex grow of one overflow hidden and a background color of pink. Let's pass this into our view. We'll do style = styles.background. Saving this, we will see that we have a pink version of our item list loader.
[3:26] One thing to note is when I reload this, obviously, originally it's going to return the children inside the view and then inside a masked element.
[3:34] The background color of your background should be the same as the background color of your loading items here, so you wouldn't see the flicker. Let's pass in the background color as a property, so we could configure it.
[3:46] Let's convert this style into an array and we will do a background color so we can remove it from the style object. In our item list loader, let's do a const background color = pink, and let's pass this into our skeleton loader.
[4:06] We can also use the same background color here instead of light gray. Now you can change this background color to, for example, teal, or gray, or any color you want. Next, let's import a linear gradient from React Native linear gradient.
[4:25] Now inside our masked view, so underneath the view that we already have, let's do another masked view. Let's position it absolutely. We can do style = stylesheet absolute fill. For the mask element, we can use a linear gradient. Let's do a linear gradient.
[4:44] Let's do a start from X0, Y0, and an end of X1, Y0. This will go left to right. For the style, let's also use absolute fill. Finally, for the colors, let's do an array of transparent, black, and transparent.
[5:06] Inside our second masked view, so where the mask element is linear gradient, let's convert this into a masked view that can take in children. Let's pass in a view. For the style, let's pass in an array. We'll do a style sheet absolute fill again, and we'll pass in the background color. Let's do a background color of purple.
[5:34] We see that our existing skeleton loader has a purple linear gradient applied on top of it. You can see that the color of the mask element doesn't matter apart from the transparency because the color comes from here instead.
[5:46] Let's move this highlight color to the properties as well. In our item list loader file, let's do const highlight color = purple. Let's pass the highlight color into the skeleton loader. In our skeleton loader, let's destructure it from the properties.
[6:06] Down here, we can do background color is the highlight color. To animate this gradient, let's import Reanimated from React Native Reanimated and let's add a Reanimated view around the second masked view.
[6:21] Let's do a reanimated.view and let's put it around the second masked view. We'll also need to pass in a style of stylesheet absolute fill. Next, let's import use animated style from React Native Reanimated, and let's do a const animated style = use animated style.
[6:45] We'll pass in an arrow function that just returns an empty object for now. Let's take this animated style and go down to where our reanimated view is, convert this style into an array, and pass in the animated style, as well as the absolute fill.
[7:02] Now let's import useShared value from React Native Reanimated and let's create a shared value, so useShared value. Let's start with zero. Let's do a React useEffect, which will get run on Mount.
[7:19] Let's also import with repeats, and with timing from React Native Reanimated. In this useEffect, let's do shared.value. Let's do with repeats and with timing. Let's go from to 1, and for the config object list to duration one second, so 1,000 milliseconds.
[7:45] For the second argument for with repeats, we need to pass in how many times you want to repeat it. In this case, I'm going to pass in infinity. Now when the skeleton loader component gets mounted, we will have a shared value that starts from zero.
[7:59] Then we'll run this useEffect once. This sets the shared value with repeats to go from to 1 taking one second, so 1,000 milliseconds to do it and do it infinitely. Next, to use this shared value in our animators styles, let's do a transform effect.
[8:18] We'll do a transform and this takes an array. We'll do a translate X and let's import interpolate from React Native Reanimated. Let's do interpolate. The first argument to this will be the shared value. The second argument will be the values that the shared value can vary between.
[8:40] We know it's between and 1, and the second array will be what we want to map to. We'll do - layout.width, and + layout.width. We'll also need to account for the initial state, where layout will be undefined. Let's do if layout, then layout.width, otherwise, zero.
[9:03] The same for the second value. If layout, then layout.width, otherwise, zero. After saving this, you might initially get a strange animation effect. I see this a lot when working with animator styles. This only happens when you have fast refresh turned on.
[9:19] If you reload the app, the animation gets reset, and it looks good again. Now I've used really bright colors here just to illustrate what's going on. I'm going to change this back to a background color of light gray. For the highlight color, I'm going to use white.
[9:34] Let's save this and reload, and we'll have a nice skeleton loader. The last thing you want to do is in our app.js, we can uncomment the if-statement we commented out earlier. We got our fruit back and now when we reload the app, we will have a nice animated skeleton loader.