Tuesday, July 28, 2020

React Router Native AnimatedSwitch

I wanted to automate the animations in my react native apps. Since I was using React Router I read up on their blurb on animating transitions. I came up with an AnimatedSwitch functional component for animating route changes.

So you use the component something like this:

<AnimatedSwitch {...this.props} exact>
<Route exact path="/" component={Home} />
<Route path="/customer" component={CustomerPage} />
<Route path="/about" component={AboutPage} />
</AnimatedSwitch>


If going to a new route, by default it will slide in from the right.   If you are going back or popping a route or even replacing a route, by default it will slide in from the left.

You can also change to a fade animation by changing the animationType to ANIMATE_FADE instead of the default of ANIMATE_SLIDE.   

You can also make a custom animationType by matching the shape of the 2 predefined animations.  For that object:
  • prev is the animation for the outgoing route
  • new is the animation for the incoming route
  • backPrevious is the animation for the outgoing route when going back or popping off the history
  • backNew is the animation for the incoming route when going back or popping off the history

In the animationSetup variable you can pass in a custom animation object to override the values used for the animation, such as type (decay, timing, spring) and then the setup variables for that type of animation directly matching the Animated API in React Native.

I hope you find it useful.   With a bit of work, it could also be modified to work with the web version of react router by switching out the Animated.Views with divs and css animations.    I haven't done that as we use NextJS for our web apps.

This is the usePrevious hook used to keep track of location and children of the previous render.   It needs to be available to AnimatedSwitch (you may need to adjust the import if your file structure doesn't match mine).   

import {useEffect, useRef} from 'react';

/**
* Any easy way to store the last value of a variable
* (maybe one set from useState).
*
* This can replace prevProps from componentDidUpdate.
*
* @param value The value to store.
*/
function usePrevious(value) {
const ref = useRef();

// Update the value in ref AFTER the render
// ready for the next call
useEffect(() => {
ref.current = value;
});

// This returns the value before the useEffect hook
// fires, so it is still the previous value
return ref.current;
}

export default usePrevious;

This is the AnimatedSwitch component.

import React, {useState, useEffect} from 'react';
import {Animated, Dimensions, View} from 'react-native';
import {
Switch,
matchPath,
useLocation,
useHistory,
Route,
} from 'react-router-native';

import usePrevious from '../../hooks/usePrevious';

const {width} = Dimensions.get('window');

export const DEFAULT_ANIMATION_SETUP = {
type: 'timing',
init: {
fromValue: 0,
toValue: 1,
duration: 250,
useNativeDriver: true,
},
};

export const ANIMATE_SLIDE = {
previous: anim => ({
position: 'absolute',
transform: [
{
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [0, -width],
}),
},
],
}),
new: anim => ({
position: 'absolute',
transform: [
{
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [width, 0],
}),
},
],
}),
backPrevious: anim => ({
position: 'absolute',
transform: [
{
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [0, width],
}),
},
],
}),
backNew: anim => ({
position: 'absolute',
transform: [
{
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [-width, 0],
}),
},
],
}),
};

export const ANIMATE_FADE = {
previous: anim => ({
opacity: anim.interpolate({
inputRange: [0, 0.75],
outputRange: [1, 0],
}),
}),
new: anim => ({
opacity: anim.interpolate({
inputRange: [0, 0.75],
outputRange: [0, 1],
}),
}),
backPrevious: anim => ({
opacity: anim.interpolate({
inputRange: [0, 0.75],
outputRange: [1, 0],
}),
}),
backNew: anim => ({
opacity: anim.interpolate({
inputRange: [0, 0.75],
outputRange: [0, 1],
}),
}),
};

const getPreviousRoute = (exact, prevPath, routes) => {
if (!routes) {
return null;
}
return routes.find(route => {
const match = matchPath(prevPath.pathname, route.props);

if (exact) {
return match != null && match.isExact;
} else {
return match != null;
}
});
};

const renderPreviousRoute = previousRoute => {
if (previousRoute) {
return previousRoute.props.component ? (
React.createElement(previousRoute.props.component)
) : (
<Route render={previousRoute.props.render} />
);
} else {
return null;
}
};

function AnimatedSwitch({
children,
exact,
animationType = ANIMATE_SLIDE,
animationSetup = DEFAULT_ANIMATION_SETUP,
}) {
const [animating, setAnimating] = useState(false);
const [anim] = useState(new Animated.Value(0));
const [previousRoute, setPreviousRoute] = useState();

const location = useLocation();
const history = useHistory();

// we're going to save the previous matching route so we can render
// it when it doesn't actually match the location anymore
const previousLocation = usePrevious(location);
const previousChildren = usePrevious(children);

// now save the animation type for both previous and new views
const [newAnimationStyle, setNewAnimationStyle] = useState();
const [prevAnimationStyle, setPrevAnimationStyle] = useState();

const finishAnimating = () => {
setAnimating(false);
setPreviousRoute();
};

const needsAnimation = previousLocation !== location;

useEffect(() => {
const newPreviousRoute = getPreviousRoute(
exact,
previousLocation,
previousChildren,
);

if (needsAnimation && newPreviousRoute) {
// we were rendering, but now we're heading back up to the parent,
// so we need to save the newPreviousRoute so we can render it
// while the animation is playing

if (history.action === 'POP' || history.action === 'REPLACE') {
setNewAnimationStyle(animationType.backNew(anim));
setPrevAnimationStyle(animationType.backPrevious(anim));
} else {
setNewAnimationStyle(animationType.new(anim));
setPrevAnimationStyle(animationType.previous(anim));
}

setPreviousRoute(newPreviousRoute);

setAnimating(true);
}
}, [
location,
needsAnimation,
animationType,
previousChildren,
previousLocation,
history.action,
anim,
exact,
]);

useEffect(() => {
if (animating) {
switch (animationSetup.type) {
case 'decay':
Animated.decay(anim, animationSetup.init).start(finishAnimating);
break;
case 'timing':
Animated.timing(anim, animationSetup.init).start(finishAnimating);
break;
case 'spring':
default:
Animated.spring(anim, animationSetup.init).start(finishAnimating);
}
}
}, [animating, anim, animationSetup]);

// Need to render the previous route for the time between not animating and animating
if (needsAnimation && !animating) {
const tempPreviousRoute = getPreviousRoute(
exact,
previousLocation,
previousChildren,
);

if (tempPreviousRoute) {
const prevRouteComp = renderPreviousRoute(tempPreviousRoute);
return prevRouteComp;
} else {
return null;
}
}

if (animating) {
// Animate both the previous route and the new route at the same time,
// then change them to the new route based on the animation type, so either slide or fade or spring
const prevRouteComp = renderPreviousRoute(previousRoute);
return (
<View>
<Animated.View key="newView" style={newAnimationStyle}>
<Switch>{children}</Switch>
</Animated.View>

<Animated.View key="prevView" style={prevAnimationStyle}>
<Switch>{prevRouteComp}</Switch>
</Animated.View>
</View>
);
} else {
// Just animate the actual route from the location
return (
<View>
<Animated.View key="newView">
<Switch>{children}</Switch>
</Animated.View>
</View>
);
}
}

export default AnimatedSwitch;

Tuesday, March 27, 2018

Running Expo offline

Offline Expo:

I wanted to continue working on my expo project on my laptop while no wifi was present.   It was very hard to find information on how to do get it running, or if it was even possible.

Finally found the 2 magic commands!

This command will start the offline server.   No login required.
exp start --offline

This command will start the app in a running android emulator.
exp android --offline


To make this even easier (and so I didn't have to remember the commands) I just added them to my scripts in package.json

"scripts": {
"server": "exp start --offline",
"emulator": "exp android --offline"
}


Then I just run on one terminal:
npm run emulator


And in another terminal:
npm run server


Documentation:
To get the full offline experience, I also downloaded react-native, react-native-elements and expo from github so I can access the docs offline!   The gh-pages branch in react-native and react-native-elements contain the doc files, though the links won't work, so you have to browse to them manually.   Still better than no docs at all!

Thursday, March 22, 2018

Expo Icon Fonts with React Native and React Native Elements!

I was trying to use the Avatar and Icon objects from react-native-elements I kept getting the following error:
fontFamily 'MaterialIcons' is not a system font and has not been loaded through Expo.Font.loadAsync.

- If you intended to use a system font, make sure you typed the name correctly and that it is supported by your device operating system.

- If this is a custom font, be sure to load it with Expo.Font.loadAsync.

It was driving me nuts.   Googling for the answer just brought up snippets of information about what to do.   So finally I pieced together the parts to get it working.

You have to load the fonts before they are used.   It seems that if you ever blow away your node_modules and then do npm install again, you lose the built in loading.  So you have to do it manually.  Here is how!

I made sure the @expo/vector icons are loaded:

npm install --save @expo/vector-icons


Then I changed the App to load them directly:

import React from 'react';
import { View } from 'react-native';
import { Avatar } from 'react-native-elements';
import { AppLoading, Font } from 'expo';

import FontAwesome  
from './node_modules/@expo/vector-icons/fonts/FontAwesome.ttf';
import MaterialIcons  
from './node_modules/@expo/vector-icons/fonts/MaterialIcons.ttf';
export default class App extends React.Component {
state = {
fontLoaded: false
};

async componentWillMount() {
try {
await Font.loadAsync({
FontAwesome,
MaterialIcons
});
this.setState({ fontLoaded: true });
} catch (error) {
console.log('error loading icon fonts', error);
}
}
render() {
if (!this.state.fontLoaded) {
return <AppLoading />;
}

return (
<View>
<Text>My App</Text>
<Avatar
small
rounded
icon={{ name: 'add' }}
/>
</View>
);
}
}

So now the fonts load before the app is shown.   While they are loading, the AppLoading continues to render the loading screen before showing any of the app.   The fonts get loaded, then the state is set so the AppLoading component no longer renders and it continues to your app.

But why throw all that into the main App.js?    It's messy.   So I made an AppFontLoader utility that looks like this:


import React from 'react';
import { AppLoading, Font } from 'expo';

import FontAwesome 
from '../../node_modules/@expo/vector-icons/fonts/FontAwesome.ttf';
import MaterialIcons  
from '../../node_modules/@expo/vector-icons/fonts/MaterialIcons.ttf';

class AppFontLoader extends React.Component {
state = {
fontLoaded: false
};

async componentWillMount() {
try {
await Font.loadAsync({
FontAwesome,
MaterialIcons
});
this.setState({ fontLoaded: true });
} catch (error) {
console.log('error loading icon fonts', error);
}
}

render() {
if (!this.state.fontLoaded) {
return <AppLoading />;
}

return this.props.children;
}
}

export { AppFontLoader };

Now the App.js gets simplified!

import React from 'react';
import { View } from 'react-native';
import { Avatar } from 'react-native-elements';
import { AppFontLoader } from './src/utils';
export default class App extends React.Component { render() {
return ( 
<AppFontLoader>
 <View>
<Text>My App</Text>
<Avatar
small
rounded
icon={{ name: 'add' }}
/>
</View>
</AppFontLoader>
);
}
}

I hope this helps you.   It shouldn't take 4 hours to figure this out!

Friday, September 29, 2017

Javascript/React project template

After doing our first big React SPA (Single Page Application) I decided to take all the project setup out and make a template project from it.   So for future projects we just unzip the project into our repository and we can start programming.

Github React Template

Here's the synopsis from the README.md

react-redux-template

Base template for enterprise react-redux projects with feature based layout. Includes setup for the following:
  • React
  • Redux with sagas
  • React-Router v4
  • SASS CSS processing
    • Global variables
    • File per component layout
  • Feature based layout - directory for each feature
    • Container
    • Styles
    • Actions & Constants
    • Reducer
    • Saga
  • Express Server
    • Public folder for security
    • Route controllers
    • HTTPS with default key
    • Async/Await syntax
    • Winston logging
      • Log level modification service
      • Logging setup parameters including automatic file rotation/deletion
    • Multi-threading
    • Hot reloading client code in dev mode
    • Hot reloading server code in dev mode
  • Production ready webpack with compression and latest javascript syntax

Wednesday, April 12, 2017

AWS config getCredentials as a promise

Getting AWS to work with async/await took some doing.   Finally have a nice little library using 
aws-sdk and aws-api-gateway-client.


// AWS API Gateway Setup - Test Inventory Callconst AWS = require('aws-sdk');
const apigClientFactory = require('aws-api-gateway-client');

const { INVENTORYLOOKUP } = appSettings.service_endpoints;

async function getAwsConfig() {
    return new Promise(function (resolve, reject) {
        AWS.config.getCredentials(async function(err) {
            if (err) {
                console.log('Error getting credentials', err);
                return reject(err);
            } else {
                resolve({
                    accessKey: AWS.config.credentials.accessKeyId,
                    secretKey: AWS.config.credentials.secretAccessKey,
                    sessionToken: AWS.config.credentials.sessionToken,
                    region: 'us-west-2'                });
            }
        });
    });
}

async function invokeAWS(targetURL, body) {

        let config = await getAwsConfig();
        config.invokeUrl = targetURL;

        console.log('config=', config);

        const apigClient = apigClientFactory.newClient(config);

        // config, url, method, header, body        let prom = apigClient.invokeApi({}, '/', 'POST', {
            'Content-Type': 'application/json',
            'Accept': 'application/json'        }, body);

        return prom;
    }
}

Monday, April 3, 2017

To the promised land, with async/await and Node 7

So you want to synchronously call asynchronous commands?   What?!?!?   But there are times when you do need to, such as stringing together dependent calls.   Request-Promise did make this a bit cleaner, but the code still gets messy.  This is where Node 7 and async/await come to save the day.

First, say we have 2 calls to request data, then 2 calls that need that data and need to be called in order.   I was able to solve this problem with request promise, but it is not ideal.   First, the very straightforward RP solution.

rp(restCall1)
    .then(function(response1) {
        // processCall1        })
    .catch(function(error) {
        // handle error from Call1    })

This is fine for a single call, but now make 2 calls, with the 2nd dependent on the 1st call.
rp(restCall1)
    .then(function(response1) {
        // processCall1        rp(restCall2)
            .then(function(response2) {
                // processCall2            })
            .catch(function(error2) {

            })
    })
    .catch(function(error1) {
        // handle error from Call1    })

That's managable, but getting messy.   Make 3 or 4 calls and it becomes an indentation nightmare.  You can unwind the nightmare a bit by using callback functions instead of putting them inline.

function processCall1(response1) {
    rp(restCall2).then(processCall2).catch(processError)
}

function processCall2(response2) {
    rp(restCall3).then(processCall3).catch(processError)
}

function processCall3(response2) {
    rp(restCall4).then(processCall4).catch(processError)
}

function processCall4(response4) {
    // Do final processing.}
    
function processError(error) {
    
}

rp(restCall1).then(processCall1).catch(processError1);

But now to show the process, you need to document carefully, name functions carefully and still when you go back and look at it, you have to really trace through each call to figure out what happens and in what order.   It's confusing and takes time if you wrote the code.   It's even harder if someone else is looking at the code.

Enter async and await.  You can wait for any promise.   You just need to make sure it's returned from the method you are calling.    The rp call returns the actual promise with rp().promise().   So now the code gets much simpler and more readable.

async function doComplicatedProcess(input) {
    try {
        let response1 = await processCall1(input);
        let response2 = await processCall2(response1);
        let response3 = await processCall3(response2);
        let response4 = await processCall4(response3);
    } catch (error) {
        console.log(error);
    }
}

async function processCall1(input) {
    return rp(restCall1).promise();
}

async function processCall2(response1) {
    return rp(restCall2).promise();
}

async function processCall3(response2) {
    return rp(restCall3).promise();
}

async function processCall4(response3) {
    return rp(restCall4).promise();
}
Now it reads like it is supposed to.   Do call 1, then 2, then 3, then 4.   Simple and easy.   Any function that returns a promise you can wait for with await.   Easier to understand also means easier to maintain.

Now what if process1 and process2 aren't needed until process4?  And they do take some time to execute?   Now we have the power to make our call even faster.
async function doComplicatedProcess(input) {
    try {
        let promise1 =  processCall1(input);
        let promise2 =  processCall2(input);
        let response3 = await processCall3(input);
        let [response1, response2] = await Promise.all([promise1, promise2]);
        let response4 = await processCall4(response1, response2);
    } catch (error) {
        console.log(error);
    }
}

async function processCall1(input) {
    return rp(restCall1).promise();
}

async function processCall2(input) {
    return rp(restCall2).promise();
}

async function processCall3(input) {
    return rp(restCall3).promise();
}

async function processCall4(response1, response2) {
    return rp(restCall4).promise();
}

Now call 1 and call 2 are processing while call 3 is being processed.   Then the await Promise.all makes sure call1 and call2 are done before going on to call 4.   It's a beautiful thing.   I reduced the average call time by 30% in my app which improved customer response time.   It also made it so that co-workers understand the code and what it's doing by looking, rather than having to trace through.

Thursday, July 21, 2016

OpenUI5 DataBinding and setModel(model) vs setModel(model, "myModel")

The data binding has been driving me crazy and I wasn't able to find an explanation of the different syntaxes used in many of the examples I've seen.

var model = new JSONModel({
   "isValid" : true
});

If I do this.getView().setModel(model), how is that different from this.getView().setModel(model, "myModel")?   How do I reference them from the xml view definition?

The setModel(model) version

When you do this.getView().setModel(model) it actually sets the data into the model in an "undefined" area of the model.  (Check in the browser debugger).   So when you reference it in the xml view, you use a system like:

<Text text="{isValid}"/>

It appears if you try and set 2 models this way, one will not be set and will be unavailable.

The setModel(model, "myModel") version

When you do this.getView().setModel(model, "myModel) you are now seting the data into the model with the "myModel" name.   So if you look in the browser debugger it will be nicely nested inside the view's model data structure.  So now to reference it you use the ">" notation to tell it to grab the named model instead of the generic one.  So it looks like this:

<Text text = "{myModel>/isValid}"/>

If you are referencing array data like in a list or table, then you drop the / after the > to make it a relative reference:

<Text text = "{myModel>isValid}"/>