I've been running into some issues with my React + Typescript project.
This happens whenever I have to type a variable that starts of as null and will receive async data of some type.
For example: I have an AdminBlogPostPage that the admin uses to create and edit posts.
And I have a variable that will be used to keep the blogPost data.
But when that page 1st renders, there still won't be any data available, 'cause the post will be loaded async.
Currently, I'm typing it like this (I'm using --strictNullChecks):
type ADMIN_BLOGPOST_STATE = {
blogPost: null | BLOGPOST
}
The blogPost state starts off as null and once it's loaded, it becomes BLOGPOST.
It works fine. But from this point on, everything that the admin will do to the blogPost object becomes kind of a pain, because Typescript will always be "worried" that blogPost might be null.
I'm usually using Redux in those situations, so in my reducer I'm constantly having to do type assertions make it clear to Typescript that when that action happens, blogPost will be a BLOGPOST and not null.
For example:
/* ############################# */
/* #### BLOGPOST PROPERTIES #### */
/* ############################# */
UPDATE_CATEGORY(state, action:UPDATE_CATEGORY) {
const blogPost = state.blogPost as TYPES.BLOGPOST; // TYPE ASSERTION HERE
const { value } = action.payload;
blogPost.category = value;
},
UPDATE_BOOLEAN_PROPERTY(state, action: UPDATE_BOOLEAN_PROPERTY) {
const blogPost = state.blogPost as TYPES.BLOGPOST; // TYPE ASSERTION HERE
const { name, value } = action.payload;
blogPost[name] = value;
},
UPDATE_STRING_PROPERTY(state, action: UPDATE_STRING_PROPERTY) {
const blogPost = state.blogPost as TYPES.BLOGPOST; // TYPE ASSERTION HERE
const { name, value } = action.payload;
blogPost[name] = value;
},
UPDATE_PRODUCT_CATEGORY(state, action: UPDATE_PRODUCT_CATEGORY) {
const blogPost = state.blogPost as TYPES.BLOGPOST; // TYPE ASSERTION HERE
const { value } = action.payload;
blogPost.productCategory = value;
},
How do people usually handle the types for async data? Is it worth it to start with null before the data arrives? Or is it best to start with a valid blogPost: BLOGPOST stub as the initial state so Typescript will know that blogPost will always be a BLOGPOST at all times and I won't have to deal with this repetitive type assertions anymore?
Note: Even if I decide to start off with the valid blogPost: BLOGPOST, I'll still need to load data from DB. The initial state will not be used. It will be there just to allow me to use blogPost: BLOGPOST instead of null | BLOGPOST.
Rather than using
null, I'd create a separate type to represent the state where data hasn't yet been loaded from the backend/database, along the lines ofThis would let you explicitly represent the UI's state before the blog post has loaded, which might be useful if you wanted to add a spinner or other loading indicator. Your reducers would still need to check for whether the blog post is loaded or not, but it'd be pretty simple to just early-exit if the blog post hasn't loaded:
As a side note, when using Redux, you generally want your reducers to be pure functions that return a new state object instead of mutating the existing state. See the docs.