Skip to content
  • Unlock Pro
  • Log in with GitHub
Solution
Submitted about 1 year ago

Tip Calculator React

react, tailwind-css, zustand, vitest
Mirror83•200
@Mirror83
A solution to the Tip calculator app challenge
View live sitePreview (opens in new tab)View codeCode (opens in new tab)

Solution retrospective


What are you most proud of, and what would you do differently next time?

I'm proud of how I've organised the state for the tip calculator. This is a code snippet to show what I'm talking about:

export type TipPercentage = {
  /** The currently selected preset value */
  presetValue: number | null;
  /** The currently entered custom value */
  customValue: number | null;
  /** The currently active mode */
  mode: "preset" | "custom";
};

type State = {
  billAmount: number | null;
  tipPercentage: TipPercentage | null;
  numberOfPeople: number | null;
};

type Actions = {
  setBillAmount: (amount: number | null) => void;
  setNumberOfPeople: (count: number | null) => void;
  setPresetTipPercentage: (percentage: number | null) => void;
  setCustomTipPercentage: (percentage: number | null) => void;
  reset: () => void;
};

const initialState: State = {
  billAmount: null,
  tipPercentage: null,
  numberOfPeople: null,
};

This covers everything that is required of the tip calculator, and (due to the null) is able to handle situations where the various inputs are empty.

I think it would work just as well if I had just used React context and useState with the state organised in the same manner instead of Zustand. However, I don't think that I would have come up with this without using a state management library.

What challenges did you encounter, and how did you overcome them?

While writing the UI tests, I came across an issue where the tests ran fine in isolation, but running the whole test suite would have all but the first test run fail.

After a bit of debugging, I found out that there were two (separate) issues causing this.

The first one was that the <App/> component was being rendered once for each test (without previous instances being removed), therefore the matchers were finding more than one instances of an element from the second test in the suite onwards. I solved this by adding beforeEach and afterEach functions to the test suite where the former rendered a new instance of the component and the latter cleaned up the DOM used during testing (using the cleanup function from the React Testing Library (RTL).

The other was that the values of inputs were being carried over from test to test in the UI test suite. I thought that this would be something concerning the userEvent from RTL, but apparently it was the Zustand state store. I solved this by resetting the state in the afterEach function. Maybe I should have mocked the store, but the state reset was simple, and it works well in this case. I did not see any need to complicate the code while the tests (in my opinion) were already close to how the application was supposed to be used.

If anyone has any input on the second issue and how I could handle it better, I would like to hear them out.

Code
Loading...

Please log in to post a comment

Log in with GitHub

Community feedback

No feedback yet. Be the first to give feedback on Mirror83's solution.

Join 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

Stay up to datewith new challenges, featured solutions, selected articles, and our latest news

Frontend Mentor

  • Unlock Pro
  • Contact us
  • FAQs
  • Become a partner
  • Use cases

Explore

  • Learning paths
  • Challenges
  • Solutions
  • Articles

Community

  • Discord
  • Guidelines

For companies

  • Hire developers
  • Train developers
© Frontend Mentor 2019 - 2025
  • Terms
  • Cookie Policy
  • Privacy Policy
  • License