Invoice App - ReactJS, Styled-Components, Framer Motion, Webpack
Design comparison
Solution retrospective
Helloπ!
This is an invoicing application build with ReactJS and styled-components. The application is used to manage invoices and allows the user to create, read, update, filter by status and delete invoices. There is an option in the app to switch between a dark and a light theme. All transitions are smoothly displayed by using Framer Motion library to create animations. It was by far the largest and most comprehensive project I have done so far. It showed me how important it is to plan so that you don't have to change things that previously worked well in the middle of the project. A valuable lesson!
- The first time I used
useReducer
hook to manage application state. I noticed that my state logic getting more complex as the few elements of my state relies on the value of another element of my state. Read More - Together with useReducer, the
useContext
hook turned out to be handy. It allowed me to create common data that can be accessed throughout the component hierarchy without passing the props down manually to each level which in turn allowed me to avoid Prop drilling (also called "threading") that is the process you have to go through to get data to parts of the React Component tree. Read More(1). Read More(2) - In order to create a theme switcher and provide colors for components I used styled-components
<ThemeProvider>
wrapper component. By leveraging the theme provider I only need to write my styles in one single object and invoke them in any component that is a descendant of that provider. Read More - When creating the form I learned what Controlled Component is. In HTML, form elements such as
<input>
,<textarea>
, and<select>
typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the state property of components, and only updated withsetState()
. Then the React component that renders a form also controls what happens in that form on subsequent user input. Read More - To make application more
ADA compliant
(which means the website should be entirely accessible using just keyboard) I prevent focus go outside the modal once the modal is opened. In this case, the focus trap turns on when the form or modal with invoice deletion/status change is opened. In order to create an accessible modal I followed this great tutorial that follow the WAI-ARIA Practices. - To animate the pages transitions and modals I used Framer Motion API. Framer Motion is an open source, production-ready library that's designed for creating creative animations. In order to support users who have enabled their deviceβs Reduced Motion setting and make accessible animations I used
useReducedMotion
hook. Based on whetheruseReducedMotion
returnstrue
or not we're passing different values toanimate
. That replace position transitions with opacity. Read More(1). Read More(2) - Handle 404 routes in React Router and provide a fallback component for displaying an imfamous
404 Page Not Found
error to the user. Try to enter a page that doesn't exist - like 'invoice-tediko.netlify.app/gotcha'
You can find more about the things I used in the project in the README on github. I just wanted to point out most important things here.
Questions:
- As I am changing the theme and many colors are changing, I wasn't sure how to make the color system. I end up with object for dark/light theme with colors of each element and a few colors that are common. How would you approach that?
- I didn't want to create a special component for my headings since they have no logic. I created helper utility called typographyStyles where I put all styles that I use across application. Not sure about that tho.
- I am curious what method you use to name your components. I personally stick to option #2 because it has the most pros for me and a little longer import doesn't bother me.
Bugs:
- Clicking twice quickly on header logo (navigate to home page, which render Invoices component) causes component not to render. I am convinced that this is related to Framer Motion and
AnimatePresence
that allows components to animate out when they're removed from the React tree. I think build in isPresent state when clicking twice quickly didn't change the state. I overcame this problem with simpleevent.preventDefault()
with asetTimeout
function but..
Additional feedback or a criticism will be appreciated Thanks! π!
Community feedback
- @alex-kim-devPosted about 3 years ago
Awesome job, Tediko! I see you put a lot of effort into this project. I like that everything is well organized, files, code, commits, it's really easy to navigate through and find out how things are connected to each other. And great job on writing readme! Here is some feedback:
- animation feels slow, I would adjust duration to be around 200-300 ms
- accessing local storage might throw errors in some cases, for example if a user turned off all cookies in Chrome, some older browsers might throw even if only third-party cookies are blocked. So It's a good practice to wrap it in a try-catch block
- there are a few places with a lot of nested if-else statements, like in
formValidation.js
. That's increasing cyclomatic complexity and makes code hard to understand, especially for people not familiar with the codebase. Try to avoid nesting conditions. Ideally it should look like this:
() => { if (conditionA) return value1; // the following code will be executed only if(!conditionA) if (conditionB) return value2; // the following code will be executed only if(!conditionA && !conditionB) return value4; }
- would be nice to work a bit on loading performance. I had DOMContentLoaded: 1.99s on fast connection and 5.32s on simulated "fast 3G".
Good luck and until the next awesome project!
Marked as helpful3@tedikoPosted about 3 years ago@Alex-K1m Thanks! Indeed, the animations are too long and therefore use of application isn't as smooth as it should be. I like web animations and I found myself using them far too much π .
Oh, I didn't know that! Now I've take a quick look and in fact using
localStorage
directly can be a bad idea. I will definitely take a proper look on the topic.Aw Jeez! Believe me, I was about to say at the top of the file that I am not responsible for the consequences of reading this validation code π Validation itself was easy but pushing all error messages to object was harder part. It iterates through an object that has a string, array, or object property and within arrays and objects there are many more to check. I'm not happy with this code and know that it shouldn't look like this.
I never got into the subject of performance, but definitely I have to.
Thanks for great feedback Alex! Good luck too!
1 - @mattstuddertPosted about 3 years ago
Another awesome solution, tediko. Nice work!! π
It's so clear you pay close attention to the details, and your write-ups are extremely helpful for others to read through. To answer your questions:
- Using an object inside your
theme.js
file is fine and is typically the way this would be done using Styled Components. You can still use CSS variables if you prefer, though. One thing I would say is that you could use broader colour names. At the moment, you're duplicating a lot of the colour values. We use names likeprimaryBlue
,accentGreen
, etc., in our code. - Different people will have different approaches to this. We have a single
Heading/index.js
file that handles all possible heading styles. I'll send you the code for that file in Slack so you can take a look. It might give you some ideas about alternative approaches. - As you can see from above, we use the
index.js
pattern. We'd also have the Storybook stories and tests in the main component folder, e.g./Heading
. We like that usingindex.js
saves a few characters here and there π
I hope this helps! Keep up the awesome work! π
Marked as helpful2@tedikoPosted about 3 years ago@mattstuddert Thank you for such kind words, Matt! π I'll have to work on the colors in my next projects. I think I'll stick to using object in
theme.js
but I'll try to use broader color names as you said. If an application had only one theme, it would be much easier. Indeed I did duplicate the colors, that is because several elements use the same color in light theme, but on the other theme they have completely different colors and I couldn't come up with a different solution.I look through what you sent me on Slack and it does look way better than my approach. Since our headings usually doesn't have many styling except some base font styling it would be much better to have this as a reusable component and optionally upload style by passing the props. It will reduce boilerplate code for me. π€©
Good to know! I'll stick to the component name for now. This is just a personal preference and it is very easy to switch to another method so.. π But! I think I'll start to using
index.js
for my utilities where I keep many different functions. So that I could import multiple utilities in one go like this:import { dateToString, generateUID, } from '../../../../utils';
instead of:
import dateToString from '../../../../utils/dateToString'; import generateUID from '../../../../utils/generateUID';
Anyway! Thanks for valuable feedback, as always! π
2@mattstuddertPosted about 3 years ago@tediko, that's a good point about your colour naming and a good reason for why you went the route you have π
I'm happy to hear my feedback helped! π
1 - Using an object inside your
- @arkharman12Posted about 3 years ago
Great work!!! I was kinda surprised by the amount of commits in the repo. Do you like to commit every change you make?
1@tedikoPosted about 3 years ago@arkharman12 Thanks! Commit often, perfect later, publish once. I've read that this is a good practice. Usually, when I create a component or some functionality I commit only when the job is done and from that point every change in that component / function is a separate commit. For sure there were some unnecessary commits, e.g. when I needed to check something on netlify and I needed a push for it and it creates some unnecessary commits.
Committing frequently gives the advantage of troubleshooting commits that have broken something in the build process and being able to revert back without losing too much work. Seeing changes is easier without going through a file with 1000 line additions.
4@arkharman12Posted about 3 years ago@tediko Good to hear your perspective for doing that :)
0
Please log in to post a comment
Log in with GitHubJoin our Discord community
Join thousands of Frontend Mentor community members taking the challenges, sharing resources, helping each other, and chatting about all things front-end!
Join our Discord