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.
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 acomponents
directory and addindex.js
,DatePickerModal.js
, andShowAgeModal.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.
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.
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]);
And VoilĆ ! if you have done everything properly you can try running your app, and it should work as expected.
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.