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.