why the frontend is dynamic and not boring at all
we often develop projects in the front end.
projects are often the best way to build and learn, no doubt about that.
but production-based code is not similar to projects and this story will explain why as well as give you a few more reasons not to hate frontend.
well, the above text is not aligned properly, doesn’t look good as it doesn’t meet the industry standards of tone in writing, right?
let me reframe the above content into production-based content or live content
I am sure most of you have built a form in frontend during your project development while learning frontend etc. But ever thought what is the usual difference between production-based code and project/sample code?
now see the difference between the project vs production code
Let me clear the real picture now,
often projects need a simple form to submit a user email and username and I not talking about authentication because that is a bit hard.
We will take the simplest example to explain the complexities of the front end as well as how dynamic front-end development is.
Task
Build a simple newsletter subscription form.
Now this form contains the following features and components
- Input to collect user email and username
- Button to submit this in the database This task is not at all different from a project the task is the same for production and project.
Production: where the actual game happens
But in the project, what doesn’t happen, happened in production. Need to dig deeper for the production-based form.
- 2 Inputs to collect email and username
- Button to submit email in the database
- Validate the email
- Animate the button
- Send emails to subscribers(if required, but required almost every time)
- check if the user is already a subscriber
- Animate the form if everything above is successful
- Handle error state …..etc other needs depend on the use case
This is the real difference and these required an increase in the production-based code from 10 lines to god-knows how much.
Still, the problem is not over
- Once the code goes beyond 20 lines we need to refactor it.
- Once refactoring is done add performance issues
- If API calls delayed them and so on
Real World Example
Let me show the code for our website subscription form.
const handleEmailSubmit = async (e) => {
e.preventDefault();
setStatus({
error: false,
success: false,
loading: true,
});
const { username, email } = values;
if (!emailIsValid(email)) {
setStatus({
error: "Invalid email",
success: false,
loading: false,
});
animateButton(1.1, colors.red[600]);
return;
}
const isSubscribed = await checkIfAlreadySubcribed(email);
if (isSubscribed) {
toast.warning("You are already a subscriber");
animateButton(1.1, colors.orange[400]);
setStatus({
error: false,
success: false,
loading: false,
});
return;
}
try {
animateButton(1.1, colors.green[400]);
await addNewsLetterEmail(username, email);
app.analytics().logEvent("user_subscribed");
await sendEmailToSubscriber({
userName: values.username,
email: values.email,
});
setStatus({
error: false,
success: "Thank you for subscribing",
loading: false,
});
if (higherOrderCallback) higherOrderCallback();
} catch (error) {
animateButton(1.1, colors.red[600]);
setStatus({
error: "Subscription Failed",
success: false,
loading: false,
});
}
};
Now the picture turns upside down but don’t worry this is not much.
Detailed Explanation
State Management
- setStatus({ error: false, success: false, loading: true }): Sets the initial status to indicate that the form is in a loading state.
const [status, setStatus] = useState({
error: false,
loading: false,
success: false,
});
Input Validation
- const { username, email } = values;: Destructures username and email from the values state.
- if (!emailIsValid(email)): Checks if the provided email is valid using emailIsValid function.
const emailIsValid = (email) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
- If invalid, it updates the status with an error message, animates the button, and exits the function.
Subscription Check
- const isSubscribed = await checkIfAlreadySubcribed(email);: Checks if the email is already subscribed using the checkIfAlreadySubcribed function.
- If already subscribed, it shows a warning toast message, animates the button, updates the status, and exits the function.
Email Subscription Process
- Inside a try block:
- animateButton(1.1, colors.green[400]);: Animates the button to indicate a successful action.
- await addNewsLetterEmail(username, email);: Adds the email to the newsletter subscription using the addNewsLetterEmail function.
- app.analytics().logEvent("user_subscribed");: Logs an event indicating a user subscription for analytics.
- await sendEmailToSubscriber({ userName: values.username, email: values.email });: Sends a confirmation email to the subscriber using the sendEmailToSubscriber function.
- Updates the status to indicate a successful subscription and calls higherOrderCallback if it's provided.
Error Handling
- In the catch block, if any error occurs during the process, it animates the button to indicate an error and updates the status with an error message.
Code Refactoring
We need to split the code into multiple lines or hooks as much as possible.
Splitting means dividing your code into small code or methods that can be reused as well into other methods.
Let’s divide the code into small
Validate Email
const validateEmail = (email) => {
if (!emailIsValid(email)) {
setStatus({
error: "Invalid email",
success: false,
loading: false,
});
animateButton(1.1, colors.red[600]);
return false;
}
return true;
};
Check Subscription
const checkSubscription = async (email) => {
const isSubscribed = await checkIfAlreadySubcribed(email);
if (isSubscribed) {
toast.warning("You are already a subscriber");
animateButton(1.1, colors.orange[400]);
setStatus({
error: false,
success: false,
loading: false,
});
return false;
}
return true;
};
Add Subscriber
const addSubscription = async (username, email) => {
await addNewsLetterEmail(username, email);
app.analytics().logEvent("user_subscribed");
};
Send Confirmation Email
const sendConfirmationEmail = async (username, email) => {
await sendEmailToSubscriber({
userName: username,
email: email,
});
};
Update Status
const updateStatus = (status) => {
setStatus(status);
};
Final Method
const handleEmailSubmit = async (e) => {
e.preventDefault();
updateStatus({
error: false,
success: false,
loading: true,
});
const { username, email } = values;
if (!validateEmail(email)) {
return;
}
if (!await checkSubscription(email)) {
return;
}
try {
animateButton(1.1, colors.green[400]);
await addSubscription(username, email);
await sendConfirmationEmail(username, email);
updateStatus({
error: false,
success: "Thank you for subscribing",
loading: false,
});
if (higherOrderCallback) higherOrderCallback();
} catch (error) {
animateButton(1.1, colors.red[600]);
updateStatus({
error: "Subscription Failed",
success: false,
loading: false,
});
}
};
Now we reduce the number of lines of code and split the code as well
But our task is not over yet the total number of lines of code remains the same and the performance would most probably remain the same.
Performance
A lot to handle on the performance side.
- Validate email on blur, no need to handle it when the user submits it
- API calls running async if not giving a response or taking too much time then move ahead allowing the user to submit the form
- Check if the subscription email is sent within 24 hours if not send it again
- Disable the button so that the user won’t make subsequent API calls to our backend or database
- Memoized the function wherever required
- Avoid inline functions inside the render method to spare from rerendering
The number of lines of code depends on the use cases for the form, some subscription forms need to send a confirmation email to the user email address with the confirmation token. That is altogether a different case that we might handle in other blogs.
Adding memoization using useCallback is a good start for optimising performance.
Below is the final
const handleEmailSubmit = useCallback(async (e) => {
e.preventDefault();
updateStatus({
error: false,
success: false,
loading: true,
});
const { username, email } = values;
if (!validateEmail(email)) {
return;
}
if (!(await checkSubscription(email))) {
return;
}
try {
animateButton(1.1, colors.green[400]);
await addSubscription(username, email);
await sendConfirmationEmail(username, email);
updateStatus({
error: false,
success: "Thank you for subscribing",
loading: false,
});
setValues({
email: "",
username: "",
});
if (higherOrderCallback) higherOrderCallback();
} catch (error) {
animateButton(1.1, colors.red[600]);
updateStatus({
error: "Subscription Failed",
success: false,
loading: false,
});
setValues({
email: "",
username: "",
});
}
}, [values, validateEmail, checkSubscription, animateButton, addSubscription,
sendConfirmationEmail, updateStatus, higherOrderCallback]);
This might sound disturbing but a lot more to handle in this final form method.
Analytics
Some apps need analytics as well, I still remember working on a crypto exchange mobile app in react-native where each form submit and churn rate or leave rate is tracked in analytics.
For example, you can read the above code as mentioned below
app.analytics.logEvent("user_subscribed")
This method is a Firebase analytics method to log the events for the analysis. These log events helped the product team to track the entire user journey in the app or website to create user journey pipelines.
User journey analytics data is important to understand burn rate, churn rate, traffic, daily active users and so on.
Logging Events, Why?
These log events are important for data science as well as the product team under the hood, they use these analytics every day to create and decide future designs and features as well.
Hence, analytics become an important part of frontend development.
Writing Test Cases
I am sure if I miss it a lot of senior developers reading this blog will abash me 😅. Writing test cases in the front end is mainly done using jest and this part along with logging events will be covered in another blog.
Not just boring
I hope one can understand what it’s like to put code in production.
- Creating UI components
- Handling states
- Handling errors and edge cases
- Refactoring the code
- Optimising the code
- Adding analytics in the code, logging events
- Writing test cases
I’ve already told you front end is static, it’s dynamic and does include the game of optimisation, and data structures just like backend development.
We are not here to compete but frontend itself is quite a vast domain to pursue becoming a frontend developer brings a lot of scope.
Frontend devs have so many options to pursue
- Website development
- Mobile app development
- Desktop apps
- Terminal and GUIs development
- Game development
- Web3 developer
- Open-source packages development
Not just Orthodox jobs
Apart from these orthodox developments, frontend developers can build tools such as
- SAAS products
- Editing tools
- Video editors
- Text Editors
- Chrome Extensions
- Device Interface (Tablets)
A lot of things are under the hood and once you have entered this domain you have to figure out and learn whatever you feel excited to you.
Frontend Development Roadmap
And if we have read so much about Frontend so far, why don’t you try my Frontend Development Roadmap?
Feel free to share it with anyone in need.
Conclusion
I hope you have loved the article that explains why frontend is not boring and static.
Frontend under the hood has so many states to handle along with writing production-based code with all edge cases and optimisation being handled.
That’s it for today, see you in the next one
Shrey
Top comments (0)