React (with MobX)
Why do we need MobX?
When using MVVM, we need a way to push updates to view models from our model and the UI from our view models when something changes. MobX is a library that does just this, allows us to set up observable or computed properties that trigger other things (like re rendering or re computing) to happen elsewhere in our code. MobX is inspired by reactive programming principles so it's a simple and lightweight library that we use to trigger other areas of our app. MobX does not define how we set up our stores, view models, or anything else in our app, it just helps us implement the observable pattern with reactive programming.
If you have another library that does this or your own way of setting up reactive programming, feel free to use that instead! MobX is just my preference when implementing MVVM. MobX specifically is not required.
How do I use this information?
The page is written in a way to start simply, and then add layers of abstraction where it starts to make sense, so some of the things we do at first, will not ultimately be how they end up. We do this gradually to be able to explain why we'd do it at all!
The Weather App
We are building an app that has the following requirements
- Search for the current temperature by zip code and display the temperature in Celsius and Fahrenheit, as well as the city name. This does not need to be remembered for the duration of the session.
- Search for the current weather using the current location and display the temperature in Celsius and Fahrenheit, as well as the city name. Remember this for the duration of the session.
We can keep it simple by using
create-react-app with TypeScript and add MobX.
Because this is a real world app that is going to hit the OpenWeather API, we can get that ready first. If you already have a way of setting up your networking layer, feel free to use that, otherwise check out the Networking documentation in the Architecture section on how to do so, and why we abstract it (as opposed to making networking calls directly from our view models). You can also copy the networking layer from my MVVM Web Github. Generate a free API key by creating an account and navigating to the API keys section to generate a key The API docs can be read through here if you're curious, but you don't need to do that for this exercise.
Creating a View and its ViewModel
Let's start with the zip code requirement. We know we need an input, a search button, and a way to display the temperature and city, so let's start with setting up each file, and then move onto the simplest thing which is the temperature label.
If you ever aren't sure how to get started (or on anything really), start by getting it working the way you know how to do it and then refactor it. If the idea of creating the view and view model at the same time is daunting or you simply don't know what goes where, first build the component that does everything, then work to separate the logic from the UI after. We do exactly that in The Counter Example in the introduction. With practice, it'll become more intuitive and obvious how to start.
I've created a directory called
ZipCodeWeatherView which will match the component name and all of the following files will be defined in that directory.
We know we'll need a
ViewModel.ts, so let's create that
Then we will need a
View.tsx that will eventually it
And finally, the provider or binder or "glue" that connects the two and is what all of our other components will use directly (as opposed to the
ViewModel we just created).
Why do we need three files? Technically these could all be defined in the same file but I prefer to separate by responsibility (the view, the view model, and the binder for the two). Specifically separating the view model and the view helps me mentally and physically separate UI and logic and then for consistency and clarity, I separate the binder as well. Every view that needs to do some logic will follow this pattern. Would it be more convenient to do them all together? Sure, but what's "more convenient" right now can often be in conflict with what's best for the testing or scalability of a codebase.
We will have these three files for all of our components, with the only difference being the name of the component exported from the
If it helps you to name them differently (for example
absolutely feel free to do that as long as you're consistent with naming throughout your app.
It's common to need to hold some data that we fetch from an API in a place that's accessible to multiple screens or views. It's also common to only need some data for a specific page and we can disregard it once the user navigates away. We will cover how to handle both of these situations.
Because our store will be injected into our view models, we'll keep it abstracted with an interface.
We'll create a concrete implementation called
WeatherStore because it'll store our weather data.
It will also take an implementation of our network layer as a dependency in order to be able to access OpenWeather.
I'll name the interface
WeatherStorable because any implementation is able to store weather data, but feel free to use
whatever naming conventions you like (the
-ing suffixing I use comes from
the Swift naming guidelines if you were curious).
This is the basics of our store with dependencies injected and abstractions ready to go.
makeAutoObservable from mobx will make all of our properties observable by default. You can read more about what it
does here but if you're unfamiliar with reactive programming, don't worry about it for now.
Let's add the functionality for getting weather by location by zipcode by having a method we can call on the store that will make the API call, unpack the networking request, and return an object we'd like to use. So first, let's define what we'd like to use in our UI. I know OpenWeather returns the temperature in Kelvin, and I'd like to convert the Kelvin to both Celsius and Fahrenheit, and I'd also like to display the city name for the zip code. Let's create a type for that.
Now let's put it all together! First we can define the method in our interface to do this. It can be handy to think
about the interfaces first since we don't need to get bogged down with implementation yet. We can just think about it
like a black box. We know we need to pass in zip, we know we want a
Weather object in return. We can define our interface like so