Looking to learn React Native? Checkout React Native Training.

I recently needed a custom Toast module for our React Native app. While there are some good ones out there, none of them fit our needs. After spending a few minutes building our own, I was happily surprised to see how easy it was to do. Here, we will go step by step, building our own from scratch.

The final repo is located here.

The demo is here on RNPlay.

Let’s go!

For out animations, we will be using the Animated API.

To set up an animated value, you first need to define the value using the Animated.Value method.

If you are using es6 classes, you can do this in the constructor:

constructor(props) {

super(props)

this.animatedValue = new Animated.Value(-70)

}

If you are using createClass, you can do it in the componentWillMount:

componentWillMount() {

this.animatedValue = new Animated.Value(-70)

}

The value ( -70 ) is going to be our distance from the top of where the animation will begin. So, because our first toast will be located at the top of our component, it will be set at -70 on the Y axis from where we place it, which will hide it until we call it to 0 (zero), which will bring it into the view!

To attach the animation to a View component, you use the Animated.View component and add the style property of transform. As you can see, transform takes an array of objects:

<Animated.View style={{ transform: [{ translateY: this.animatedValue }}></Animated.View>

We are using the translateY property to animate the Y position of the element, which will animate the property on the Y axis.

You can also animate Images and Text components:

<Animated.Image />

<Animated.Text></Animated.Text>

We will set a some styling, making sure the the component has absolute positioning as well as a few other styles. We will also add a message to the toast:

<Animated.View

style={{

transform: [{ translateY: this.animatedYValue }],

height: 70,

backgroundColor: 'green',

position: 'absolute',

left:0,

top:0,

right:0,

justifyContent: 'center'

}}>

<Text

style={{

marginLeft: 10,

color: 'white',

fontSize:16,

fontWeight: 'bold'

}}>

Hello from Toast!

</Text>

</Animated.View>

Here are the types of transforms that can be created. Here is a link to the docs for the properties.

transform [{perspective: number}, {rotate: string}, {rotateX: string}, {rotateY: string}, {rotateZ: string}, {scale: number}, {scaleX: number}, {scaleY: number}, {translateX: number}, {translateY: number}, {skewX: string}, {skewY: string}]

Animated has a few methods that are available to be called. You can see them here. We will be using the timing method.

The timing method animates a value along a timed easing curve. The Easing module has tons of pre-defined curves, or you can use your own function.

The Animated.timing function takes two arguments:

The value to be animated ( in our case, this.animatedValue ) A configuration object

Our basic timing animation will look something like this:

Animated.timing(

this.animatedValue,

{

toValue: 0,

duration: 750

}

).start()

A few things to note. We are setting the following options:

toValue: 0 — this will be the value of this.animatedValue after the animation is complete

duration: the length of the animation

The configuration object takes any of the following options:

startTime: number

fromValue: number

toValue: any

duration: number

delay: number

easing: number

onUpdate: function

animationFrame: any

timeout: any

When the animated.timing function is called, the animation will begin!

We’re going to start with something like this:

showToast() {

Animated.timing(

this.animatedValue,

{

toValue: 0,

duration: 750

}

).start()

}

Animated.timing also takes an optional callback:

Animated.timing().start(callback)

We will put this callback to use, so we can call a another function to close it:

showToast() {

Animated.timing(

this.animatedValue,

{

toValue: 0,

duration: 750

}

).start(this.hideToast())

}

Now, we need to set up a button to call the toast:

<View style={ styles.buttonContainer }>

<TouchableHighlight

onPress={ () => this.callToast() }

underlayColor="ddd"

style={{

height:60,

justifyContent: 'center',

alignItems: 'center',

backgroundColor: 'ededed',

borderWidth: 1,

borderColor: 'ddd'

}}>

<Text>Open Success Toast</Text>

</TouchableHighlight>

</View>

I’ve also added some styling around the button component to push it down:

<View style={styles.container}>

<View style={ styles.buttonContainer }>

<TouchableHighlight /> // button code we already wrote

</View>

</View> // Styles const styles = StyleSheet.create({

container: {

flex: 1,

marginTop: 70

},

buttonContainer: {

marginTop:10

}

});

When we run this, we should see the following when clicking on our button:

Now the code for our component should look like this.

Now, let’s make the button dynamic and add some more features.

One thing you will notice, is that if you keep clicking on the button, it will keep firing the toast to open. To fix that, let’s create a state variable to check if the toast is open:

constructor(props) {

super(props)

this.animatedValue = new Animated.Value(-70)

this.state = {

modalShown: false

}

}

Then, in our callToast function we check to see if the modal is shown, if it is we return. If it is not, we set the modalShown property to true:

callToast() {

if(this.state.modalShown) return

this.setState({ modalShown: true })

Animated.timing(

this.animatedValue,

{

toValue: 0,

duration: 350

}).start(this.closeToast())

}

Then, in the callback, we set the state of modalShown to false:

closeToast() {

setTimeout(() => {

this.setState({ modalShown: false })

Animated.timing(

this.animatedValue,

{

toValue: -70,

duration: 350

}).start()

}, 2000)

}

Next, let’s change the background color and the message of our toast to be dynamic with the values toastColor and message:

constructor(props) {

super(props)

this.animatedValue = new Animated.Value(-70)

this.state = {

modalShown: false,

toastColor: 'green',

message: 'Success!'

}

}

Then, we can use the values in the view:

<Animated.View style={{ ... backgroundColor: this.state.toastColor ... }}>

<Text style={{...}}>

{ this.state.message }

</Text>

</Animated.View>b

Our new code should look like this.

Now, we want to dynamically change the color and text of our toast. To do this, let’s create a function that will return the configuration we will use for the toast. Place this function under closeToast:

setToastType(message='Success!', type='success') {

let color

if (type == 'error') color = 'red'

if (type == 'primary') color = '#2487DB'

if (type == 'warning') color = '#ec971f'

if (type == 'success') color = 'green'

this.setState({ toastColor: color, message: message })

}

As you can see we have set default parameters which are new in es6. That way, if we do not specify any arguments, the defaults will be passed.

To use this function, we need to set up some arguments to our callToast function, then call the setToastType function after the callToast function :

callToast(message, type) {

if(this.state.modalShown) return

this.setToastType(type, message)

this.setState({ modalShown: true })

Animated.timing(

this.animatedValue,

{

toValue: 0,

duration: 350

}).start(this.closeToast())

}

To use the new function, just call callToast and pass in a type and a message:

this.callToast('Something went wrong...', 'warning')

this.callToast('Error toast called!', 'error')

this.callToast('Primary toast called!', 'primary')

You can also call the toast with no arguments and you will get the success toast:

this.callToast()

Or, with one argument to change the message:

this.callToast('YOYOYO')

With the added buttons, the code should look like this.

Now, when you run the app with the above arguments, you should get the desired color and message!

Before we go any further, let’s refactor the buttons into reusable components:

class Button extends Component {

render() {

let { callToast, type } = this.props

return (

<View style={ styles.buttonContainer }>

<TouchableHighlight

onPress={ callToast }

underlayColor="ddd"

style={{

height:60,

justifyContent: 'center',

alignItems: 'center',

backgroundColor: 'ededed',

borderWidth: 1,

borderColor: 'ddd' }}>

<Text>Call { type } toast.</Text>

</TouchableHighlight>

</View>

)

}

}

Now, we can use the button like this:

<Button type="success" callToast={ () => this.callToast('YOYOYO') } />

Next, we’ll add another type of toast, one that slides in from the bottom left.

To get this set up, let’s create another animated value. To set the value, we first need to calculate the width of the screen using Dimensions. We will also need to calculate the height of the view to position it at the bottom because we are using absolute positioning. To do so, first make sure Dimensions is required:

var React = require('react-native')

var {

...

Dimensions

...

} = React

or

import {

...

Dimensions

...

} from 'react-native'

Then, calculate the width and height, and store the values in a variable:

let windowWidth = Dimensions.get(‘window’).width

let windowHeight = Dimensions.get('window').height

Now, we’re ready to use the windowWidth variable to set our next animated value:

constructor(props) {

super(props)

this.animatedValue = new Animated.Value(-70)

this.animatedXValue = new Animated.Value(-windowWidth)

this.state = {

modalShown: false,

toastColor: 'green',

message: 'Success!'

}

}

We set the value to -windowWidth because we will be using the windowWidth variable to set the width of our toast. This value will place our toast component out of the view until we animate animatedXValue to 0 (zero) which will bring it into the full view.

Now, we create a new Animated.View. We use the windowHeight variable and subtract the height of the toast to set the top margin:

<Animated.View

style={{

transform: [{ translateX: this.animatedXValue }],

height: 70,

marginTop: windowHeight — 70,

backgroundColor: ‘green’,

position: ‘absolute’,

left:0,

top:0,

width:

windowWidth,

justifyContent: ‘center’

}}>

<Text

style={{

marginLeft: 10,

color: ‘white’,

fontSize:16,

fontWeight: ‘bold’,

textAlign: ‘center’

}}>

Success!

</Text>

</Animated.View>

Now, we need to create the function to call the toast. Like before, we will call a callback function as well:

callXToast() {

Animated.timing(

this.animatedXValue,

{

toValue: 0,

duration: 350

}).start(this.closeXToast())

} closeXToast() {

setTimeout(() => {

Animated.timing(

this.animatedXValue,

{

toValue: -windowWidth,

duration: 350

}).start()

}, 2000)

}

The code should now look like this.

To use the new toast, call the callToast function:

<Button type="bottom" callToast={ () => this.callXToast() } />

To enhance or modify the animation, let’s use Animated.Value’s interpolate method. interpolate will basically let you map animated values to a time in the transition.

To set it up, we will first need to create a variable to store our interpolated value. This goes in the render function before the return statement:

let animation = this.animatedValue.interpolate({

inputRange: [0, .3, 1],

outputRange: [-70, -10, 0]

})

As you can see, we are taking this.animatedValue and calling the interpolate method, and passing in some configuration.

inputRange: this range maps directly to the outputRange, and is usually on a numerical scale of either 0 (zero) to 10 or 0 (zero) to 1. For example, when input range reaches .3, it will transform the outputRange to -10. It can take two or more values. The number of values in inputRange has to equal the number of values in outputRange.

outputRange: this is the actual output of the animation.

For this to work, we need to first change the initial animatedValue value to be 0 (zero):

constructor(props) {

super(props)

this.animatedValue = new Animated.Value(0)

this.animatedXValue = new Animated.Value(-windowWidth)

this.state = {

modalShown: false,

toastColor: 'green',

message: 'Success!'

}

}

Next, we need to change the toValue in our callToast and closeToast functions to match the max and min values of our interpolate inputRange property:

callToast(message, type) {

if(this.state.modalShown) return

this.setToastType(message, type)

this.setState({ modalShown: true })

Animated.timing(

this.animatedValue,

{

toValue: 1,

duration: 350

}).start(this.closeToast())

}



closeToast() {

setTimeout(() => {

this.setState({ modalShown: false })

Animated.timing(

this.animatedValue,

{

toValue: 0,

duration: 350

}).start()

}, 2000)

}

As you can see, we changed the toValue to go to 1 when the toast is called, and to 0 (zero) when the toast is closed. The toValue maps directly to the inputRange value we defined earlier in the interpolate method.

Finally, we need to change the animatedValue in our view to the animation variable we declared earlier:

<Animated.View

style={{

transform: [{ translateY: animation }],

height: 70,

backgroundColor: this.state.toastColor,

position: 'absolute',

left:0,

top:0,

right:0,

justifyContent: 'center'

}}>

<Text

style={{

marginLeft: 10,

color: 'white',

fontSize:16,

fontWeight: 'bold'

}}>

{ this.state.message }

</Text>

</Animated.View>

Our code should now look like this.

Now, when we call the toast, it should come in fast then slow down as it reaches the final position.

There is a lot more that can be done from this point, but I’ll leave that up to you!

The final repo is located here.

The working demo on RNPlay is here.