Creating a Useless Project using NaitveBase and NextJS

Creating a Useless Project using NaitveBase and NextJS

Ā·

13 min read

Hmm šŸ¤”, so you want to know what made me write this title right? Haha šŸ˜‚, let me clear that first, Have you ever been in a situation where you were needed to calculate your Age at a particular year. I came across such situations a lot of times so finally, finally, I was like, Iā€™m done with calculating all this again and again and just started to create an App https://how-old-was-i-in.vercel.app/.

So what it does is, it asks you for a year at which you want to know your age at and then your Date of Birth and then tells you how old were you in that year. Simple right, and if you are not crazy like me then you also must be thinking what a useless project this is, and I totally agree with you itā€™s stupid and itā€™s useless but after making it I thought wait this might be a really good project to share how you can use NativeBase with NextJS, so without wasting any more time letā€™s waste some more time and build this useless Age Calculator sort of thing.

First Step . . .

Getting everything running smoothly:

You can simply follow the steps listed down here https://docs.nativebase.io/install-next or follow me.

Basically, all we need to do is create a new NextJS app,

yarn create next-app fun-experiment

of course, if you donā€™t have yarn in your system just use npm instead.

Then once it gets all installed, itā€™s time to install and setup NativeBase ( our superstar that will help us make our project look decent with no effort for styling )

yarn add react-native-web native-base react-native-svg react-native-safe-area-context

also, we need to install some of its dev dependencies.

yarn add next-compose-plugins next-transpile-modules next-fonts -D

Note: We donā€™t need @expo/next-adapter for this project so I have removed it from the dev dependencies.

Now next.config.js,

const withPlugins = require("next-compose-plugins");
const withFonts = require("next-fonts");
const withTM = require("next-transpile-modules")([
  "native-base",
  "react-native-web",
  "react-native-svg",
  "react-native-safe-area-context",
  "@react-aria/visually-hidden",
  "@react-native-aria/button",
  "@react-native-aria/checkbox",
  "@react-native-aria/combobox",
  "@react-native-aria/focus",
  "@react-native-aria/interactions",
  "@react-native-aria/listbox",
  "@react-native-aria/overlays",
  "@react-native-aria/radio",
  "@react-native-aria/slider",
  "@react-native-aria/tabs",
  "@react-native-aria/utils",
  "@react-stately/combobox",
  "@react-stately/radio",
]);

module.exports = withPlugins(
  [
    withTM,
    [withFonts, { projectRoot: __dirname }],
    // your plugins go here.
  ],
  {
    webpack: (config, options) => {
      config.resolve.alias = {
        ...(config.resolve.alias || {}),
        // Transform all direct `react-native` imports to `react-native-web`
        "react-native$": "react-native-web",
      };
      config.resolve.extensions = [
        ".web.js",
        ".web.ts",
        ".web.tsx",
        ...config.resolve.extensions,
      ];
      return config;
    },
  }
);

Now update your pages/_document.js to preload the css so that your app doesnā€™t flicker when the JS gets loaded.

import { Children } from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { AppRegistry } from "react-native";
import config from "../app.json";
// Force Next-generated DOM elements to fill their parent's height
const normalizeNextElements = `
  #__next {
    display: flex;
    flex-direction: column;
    height: 100%;
  }
`;

export default class MyDocument extends Document {
  static async getInitialProps({ renderPage }) {
    AppRegistry.registerComponent(config.name, () => Main);
    const { getStyleElement } = AppRegistry.getApplication(config.name);
    const page = await renderPage();
    const styles = [
      // eslint-disable-next-line react/jsx-key
      <style dangerouslySetInnerHTML={{ __html: normalizeNextElements }} />,
      getStyleElement(),
    ];
    return { ...page, styles: Children.toArray(styles) };
  }

  render() {
    return (
      <Html style={{ height: "100%" }}>
        <Head />
        <body style={{ height: "100%", overflow: "hidden" }}>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Create an app.json in the root of your project directory, and add this code in it.

{
  "name": "age-calculator",
  "displayName": "age-calculator"
}

We are almost done with the setup one last thing, Setting the NativeBaseProvider is left. Let's quickly add that and start making our project.

Replace your pages/_app.js with this code.

import "../styles/globals.css";
import { NativeBaseProvider } from "native-base";

function MyApp({ Component, pageProps }) {
  return (
    <NativeBaseProvider>
      <Component {...pageProps} />
    </NativeBaseProvider>
  );
}

export default MyApp;

Now lets test if everything is working or not,

yarn dev

Once your development server has started, replace your pages/index.js with the below code.

import React from "react";
import { Box } from "native-base";

export default function App() {
  return <Box>Hello world</Box>;
}

If you can see Hello world on your screen congrats you are done with 40% of the project, not joking seriously 40%, because it's that easy with NativeBase to create your UI, and the rest is all React ā¤ļø.

Btw, if you were wondering how I got these steps, so donā€™t worry it's the same steps from NativeBase docs NextJS installation page that is present in adding NativeBase to the existing Project.

Note: There might be some changes in the next.config.js if compared to docs but trust me this will work flawlessly, as currently the NativeBase Docs are in active development and there might be some change in the content. Also Iā€™ll be updating the steps once it gets stable setup steps on the Docs.

Fun fact the setup that we have is the same that the new NativeBase docs are using which are also made using NativeBase.

Second Step . . .

Creating our app state/store,

Now letā€™s see what are we going to need:

  • dobDate (User Data)

  • dobMonth (User Data)

  • dobYear (User Data)

  • yearToBeChecked (User Data)

  • showDatePickerModal (App Control State)

  • showAgeModal (App Control State)

Donā€™t worry we will discuss in detail when we are going to use these.

Letā€™s add them first in our pages/index.js,

import React from "react";
import { Box } from "native-base";

export default function App() {
  const [showDatePicker, setShowDatePicker] = React.useState(false);
  const [showAge, setShowAge] = React.useState(false);
  const [yearToBeChecked, setYearToBeChecked] = React.useState("");
  const [dobDate, setDobDate] = React.useState("");
  const [dobMonth, setDobMonth] = React.useState("");
  const [dobYear, setDobYear] = React.useState("");
    return <Box>Hello world</Box>;
}

Once our data store is sorted now we will create our required components, here is a workflow that our app will have.

How Old was I in app workflow

So basically we need 2 components for this workflow, I also like to have a Dark mode toggle switch in my apps so we will in the end, add that as well.

1st Component DatePickerModal -

  • Create an src directory in your root.

  • Inside src create a components directory and add index.js, DatePickerModal.js, and ShowAgeModal.js.

  • In your DatePickerModal.js add this code.

      import React from "react";
      import {
        Modal,
        Button,
        Input,
        HStack,
        FormControl,
        Text,
        ChevronLeftIcon,
      } from "native-base";
    
      export const DatePickerModal = (props) => {
        const {
          showDatePicker,
          setShowDatePicker,
          setDobDate,
          setDobMonth,
          setDobYear,
          setShowAge,
        } = props;
    
        return (
          <Modal isOpen={showDatePicker} onClose={() => setShowDatePicker(false)}>
            <Modal.Content maxWidth="400px">
              <Modal.CloseButton />
              <Modal.Header>Just Wanted to know...</Modal.Header>
              <Modal.Body>
                <FormControl>
                  <FormControl.Label>What is your Date of Birth?</FormControl.Label>
                  <HStack space="3" alignItems="center" divider={<Text>-</Text>}>
                    <Input onChangeText={setDobDate} flex="1" placeholder="DD" />
                    <Input onChangeText={setDobMonth} flex="1" placeholder="MM" />
                    <Input onChangeText={setDobYear} flex="2" placeholder="YYYY" />
                  </HStack>
                </FormControl>
              </Modal.Body>
              <Modal.Footer>
                <Button.Group space={2}>
                  <Button
                    leftIcon={<ChevronLeftIcon size="4" />}
                    variant="ghost"
                    colorScheme="blueGray"
                    onPress={() => {
                      setShowDatePicker(false);
                    }}
                  >
                    Go Back
                  </Button>
                  <Button
                    onPress={() => {
                      setShowDatePicker(false);
                      setShowAge(true);
                    }}
                  >
                    Done
                  </Button>
                </Button.Group>
              </Modal.Footer>
            </Modal.Content>
          </Modal>
        );
      };
    

    What this code does is it Renders a Modal with 3 input fields horizontally aligned which takes in Input from the user about his/her DoB. And when the user clicks on the Done Button closes the current Modal and opens our next component, so letā€™s move on and create our next component.

    Dob Date Picker Modal preview

2nd Component ShowAgeModal.js

  • This is where all our logic goes in.

  • What I want is that our final result i.e. the Age should come inside a pretty Circle with a Gradient background that is random from a set of Gradients.

    • Here we create 2 array of arrays that contains colors for light mode and dark mode, we will use these colors for our gradient.

        import React, { useEffect } from "react";
        import {
          Modal,
          Button,
          Center,
          Circle,
          Text,
          Heading,
          ChevronLeftIcon,
        } from "native-base";
      
        export const ShowAgeModal = (props) => {
          const DarkGradients = [
            ["#D946EF", "#024FC7"],
            ["#F87171", "#3730A3"],
            ["#38BDF8", "#1D4ED8", "#4C1D95"],
            ["#FB923C", "#C026D3"],
            ["#5EEAD4", "#0284C7", "#5B21B6"],
            ["#8B5CF6", "#A21CAF"],
            ["#9333EA", "#4338CA"],
            ["#9333EA", "#EA580C"],
          ];
          const LightGradients = [
            ["#FBCFE8", "#818CF8"],
            ["#BAE6FD", "#60A5FA"],
            ["#FDBA74", "#E879F9"],
            ["#FEF3C7", "#67E8F9"],
          ];
          return (
            <></>
          );
        };
      
    • Now we need 2 functions to choose random colors from these arrays based on our color mode, so let's create these helper functions.

        function generateRandomDarkGradient() {
          const index = Math.floor(Math.random() * DarkGradients.length);
          return DarkGradients[index].join(",");
        }
        function generateRandomLightGradient() {
          const index = Math.floor(Math.random() * LightGradients.length);
          return LightGradients[index].join(",");
        }
      

      What these do is generate a random number between 0 to the length of Gradient Array and picks the array that was at that index and join it to create a string of colors separated by commas.

    • Now we need to set these colors in a state when the component is mounted, so we will call these functions inside a useEffect hook and set it in a local state.

        const [lightGradientsArray, setlightGradientsArray] = React.useState([""]);
        const [darkGradientsArray, setDarkGradientsArray] = React.useState([""]);
        useEffect(() => {
          setlightGradientsArray(generateRandomLightGradient());
          setDarkGradientsArray(generateRandomDarkGradient());
        }, []);
      
    • One last helper function we need is that actually calculates the personā€™s age at a particular year. So letā€™s add it.

        const getAge = (dateOfBirth, currentDate) => {
          var ageInMilliseconds = currentDate - dateOfBirth;
          return Math.floor(ageInMilliseconds / 1000 / 60 / 60 / 24 / 365); // convert to years.
        };
      
    • Now all that is left is to design the UI

        <Modal isOpen={showAge} onClose={() => setShowAge(false)}>
          <Modal.Content maxWidth="400px">
            <Modal.Body>
              <Center>
                <Circle
                  _light={{
                    _web: {
                      style: {
                        backgroundImage:
                          "linear-gradient(135deg," + lightGradientsArray + ")",
                      },
                    },
                  }}
                  _dark={{
                    _web: {
                      style: {
                        backgroundImage:
                          "linear-gradient(135deg," + darkGradientsArray + ")",
                      },
                    },
                  }}
                  size="56"
                >
                  <Text>As of July 2, {yearToBeChecked}. You were</Text>
                  <Heading>
                    {getAge(
                      new Date(dobYear, dobMonth - 1, dobDate),
                      new Date(yearToBeChecked, 6, 2)
                    )}{" "}
                    yrs Old
                  </Heading>
                </Circle>
              </Center>
            </Modal.Body>
            <Modal.Footer justifyContent="center">
              <Button.Group space={2}>
                <Button
                  leftIcon={<ChevronLeftIcon size="4" />}
                  variant="ghost"
                  colorScheme="blueGray"
                  onPress={() => {
                    setShowAge(false);
                  }}
                >
                  Check another Year
                </Button>
              </Button.Group>
            </Modal.Footer>
          </Modal.Content>
        </Modal>
      

      If you donā€™t understand what this code means you might be missing what NativeBase can do, give a read to this article 90% of your doubts will be gone after this.

Show Age Modal Design Preview

  • So let's continue, you might have noticed that we forgot to destructure the props that will give a lot of errors, so let's fix it. Add this to the very start of your component.

      const { setShowAge, showAge, dobDate, dobMonth, dobYear, yearToBeChecked } = props;
    

3rd Component ColorSwitcher.js

You can totally skip this, itā€™s just for us Color Mode enthusiast that always wants the freedom to toggle the color mode in an App.

This one is pretty straight forward so Iā€™m just adding the Code with some explanation.

import React from "react";
import { Fab, SunIcon, MoonIcon, useColorMode } from "native-base";
export const ColorSwitcher = () => {
  const { colorMode, toggleColorMode } = useColorMode();
  return (
    <Fab
      placement="top-right"
      size="12"
      p="0"
      variant="unstyled"
      _dark={{ bg: "coolGray.900" }}
      _light={{ bg: "coolGray.50" }}
      shadow="none"
      onPress={toggleColorMode}
      icon={colorMode === "dark" ? <SunIcon /> : <MoonIcon />}
    />
  );
};

So all we are doing here is to use the useColorMode hook from NativeBase and a Fab, Passed the toggleColorMode function to onPress of Fab and done now you color mode switches. Now after this all we did is to make our design compatible to color mode changes and thatā€™s it.

Now export all these components using your src/components/index.js

export * from "./ColorSwitcher";
export * from "./DatePickerModal";
export * from "./ShowAgeModal";

All that is now is left is to wrap all this up together and create our root page, but wait I also have a bonus section for you guys, but letā€™s finish this first.

Building our Root Page

Letā€™s get back to our pages/index.js file, We previously added our states here but we were yet to send it to our components so lets do some clean up for that first.

I always like to keep my code clean and easily readable so lets separate those props that will be passed to DatePickerModal and ShowAgeModal.

const datePickerModalProps = {
  showDatePicker,
  setShowDatePicker,
  setShowAge,
  dobDate,
  setDobDate,
  dobMonth,
  setDobMonth,
  dobYear,
  setDobYear,
  yearToBeChecked,
};
const showAgeModalProps = {
  showAge,
  setShowAge,
  dobDate,
  setDobDate,
  dobMonth,
  setDobMonth,
  dobYear,
  setDobYear,
  yearToBeChecked,
};

So if you check what all was getting used by these components that we previously used , you will come up with this list. Now when this is done lets also start creating our UI for Root page and add our components too.

return (
  <>
    <Head>
      <title>How old was I in {yearToBeChecked}?</title>
    </Head>
    <Center
      _light={{ bg: "coolGray.50" }}
      _dark={{ bg: "coolGray.900" }}
      h="100vh"
    >
      <HStack alignItems="center">
        <Heading size="2xl">How old was I in </Heading>
        <Input
          onChangeText={setYearToBeChecked}
          w="90px"
          pb="1"
          fontSize="3xl"
          placeholder="YYYY"
          variant="underlined"
        />
        <Heading size="2xl"> ?</Heading>
      </HStack>
      <ColorSwitcher />
      <DatePickerModal {...datePickerModalProps} />
      <ShowAgeModal {...showAgeModalProps} />
    </Center>
  </>
);

Also update your imports,

import Head from "next/head";
import { Heading, Input, HStack, Center } from "native-base";
import {
  DatePickerModal,
  ShowAgeModal,
  ColorSwitcher,
} from "../src/components";

Now if you closely look we are updating our state with the user input of the year, but we are not doing anything that will open our DatePickerModal, so letā€™s fix it

What we want is to open the DatePickerModal whenever the user type 4 characters in the input and if that is a valid year between 1900 and this year. To achieve this we will create a helper function and call it in useEffect that has yearToBeChecked in its dependencies array. So whenever the yearToBeChecked gets updated we check if its a valid year and then open DatePickerModal.

function openModalWhenYearToBeCheckedIsValid(year) {
  if (year > 1900 && year < new Date().getFullYear()) {
    setShowDatePicker(true);
  } else {
    setShowDatePicker(false);
  }
}
React.useEffect(() => {
  if (yearToBeChecked.length === 4) {
    openModalWhenYearToBeCheckedIsValid(yearToBeChecked);
  }
}, [yearToBeChecked]);

Root Page Design

And VoilĆ ! if you have done everything properly you can try running your app, and it should work as expected.

voila gif

Donā€™t worry here is the Github Repo for easy and fast access. Also that bonus content, let start with that now.

Bonus . . .

Congrats If you have made it till here! Kudos to you all. I always like adding easter eggs in my projects when Iā€™m allowed. Those are really fun, so letā€™s add something of that sort in our Project as well. What we are going to do is create a dynamic route that will help us process url params, so if you write http://localhost:3000/2009 then our app will directly add that in our yearToBeChecked state and move on to DatePickerModal.

And this is really easy once you have done all the previous things.

You do need to have some understanding of how NextJS dynamic routes work so if you have never heard of it please! please! please! check this out.

Now assuming you know about NextJS Dynamic Routes, Lets add some masala spice to our dish.

  • Create a new file in pages directory named [year].js

  • Copy all your code from index.js in here.

  • And now the amazing part. All you need to make this work is just add 5-6 lines.

    • Import useRouter

        import { useRouter } from "next/router";
      
    • init router

        const router = useRouter();
        const { year } = router.query; // get the year from url params
      
    • Just add this in your useEffect now

        if (year && year.length === 4) {
          setYearToBeChecked(year);
          openModalWhenYearToBeCheckedIsValid(yearToBeChecked);
        }
      

      Also do add year to the useEffect Dependency array.

    • If you have followed everything properly this would have started working as expected.

Uff. . . this has been a long article, thatā€™s the reason there might be some places where I couldnā€™t explain in depth and I want to apologize for that but canā€™t write a hour long blog so please cooperate, more over Iā€™m all open to any doubts and queries that you may have so sendā€™em in the comments below, will try to replay ASAP.

I hope that you will try it out and also fall in love with NativeBase and how easy it makes, creating beautiful UIs, being a co-author of a library its very easy to get a little partial but judge it yourself do give it a try.

Ā