Chris Padilla/Blog


My passion project! Posts spanning music, art, software, books, and more. Equal parts journal, sketchbook, mixtape, dev diary, and commonplace book.


    Iwata on What's Worth Doing

    When it comes to answering the question "What's worth doing?", the internet can muddy it up a bit.

    Plenty of good to the internet: Shared information, connecting with far flung people, and finding community.

    And, it's also a utility that can deceive us into feeling infinite.

    I was surprised to see Nintendo's former president Satoru Iwata wrestle with this in an interview he gave for Hobo Nikkan Itoi Shumbun that was published in the book "Ask Iwata."

    "The internet also has a way of broadening your motivations. In the past, it was possible to live without knowing there were people out there who we might be able to help, but today, we're able to see more situations where we might be of service. But this doesn't mean we've shed the limitations on the time at our disposal.

    ...as a result, it's become more difficult than ever to determine how to spend the hours of the day without regret."

    Wholly relatable. Very warm to see Iwata put this in terms of serving people. For creative folk, this could be anything from projects to pursue, audiences to reach, and relationships to develop. There's, I'm sure, an interesting intersection with another change in history β€” the ability to reproduce art.

    "It's more about deciding where to direct your limited supply of time and energy. On a deeper level, I think this is about doing what you were born to do."

    Less is the answer, and considering your unique position is what takes the place of overwhelming choice. What you were born to do can be a heavy question unto itself, but thinking of it as what you're in the unique position to do helps.

    I'll paraphrase Miyazaki here: "I focus on only what's a few meters from me. Even more important than my films, that entertain countless children across the world, is making at least three children I see in a given day smile." Focusing on the physical space and your real, irl relationships, is likely to guide you towards what's worth doing.


    The Gist on Authentication

    Leaving notes here from a bit of a research session on the nuts-and-bolts of authentication.

    There are cases where packages or frameworks handle this sort of thing. And just like anything with tech, knowing what's going on under the hood can help with when you need to consider custom solutions.

    Sessions

    The classic way of handling authentication. This approach is popular with server-rendered sites and apps.

    Here, A user logs in with a username and password, the server cross references them in the DB, and handles the response. On success, a session is created, and a cookie is sent with a session id.

    The "state" of sessions are stored in a cache or on the DB.

    Session Cookies are the typical vehicles for this approach. They're stored on the client and automatically sent with any request to the appropriate server.

    Pros

    For this approach, it's nice that it's a passive process. Very easy to implement on the client. When state is stored in a cache of who's logged in, you have a more control if you need to remotely log a user out. Though, you have less control over the cookie that's stored in the client.

    Cons

    The lookup to your DB or cache can be timely here. You take a hit in performance on your requests.

    Cookies are also more susceptible to Cross-Site Request Forgery (XSRF).

    JWT's

    Two points of distinction here: When talking about a session here, we're talking about that stored on the server, not session storage in the client.

    Cookies hypothetically could be used to store a limited amount of data, but for JWT's typically need another method, since cookies have a small size limit.

    Well, what are JWT's? Jason Web Tokens are a popular alternative to sessions and cookie based authentication.

    On successful login, a JWT is returned with the response. It's then up to the client to store it for future requests, working in the same way as sessions here.

    The major difference, though, is that the token is verified on the server through an algorithm, not by DB lookup of a particular ID. There's a major prop of JWT's! It's a stateless way of handling authentication.

    Options for storing this on the client include local storage, indexedDB, and some would say, depending on the size of your token, cookies.

    Pros

    As mentioned, it's stateless. No need to maintain sessions in your cache or on your DB.

    More user-related information can be stored with the token. Details on authorization level is common ("admin" vs "user" permissions.)

    This approach is also flexible across platforms. You can use JWT's with mobile applications or, say, a smart TV application.

    Cons

    Because this approach is stateless, unfortunately you have limited control in logging out individual users remotely. It would require changing your entire algorithm, logging all of your users out.

    Depending on how you store the token, there are security concerns here, too. It's best to avoid local storage, in particular, as you are open to XSRF - Cross site request forgery. If you accept custom inputs from users, beware also of XSS - Cross Site Scripting, where malicious code could be ran on your site.

    Who Wins?

    Depending on your situation, you may just need the ease of setup provided by session storage. For an API spanning multiple devices, JWT's may seem appealing. There is also the option to blend the approaches: Using JWT's while also storing session logic in a cache or DB.

    Some handy libraries for implementing authentication includes Passport.js and auth0. For integrated authentication with Google, Facebook, etc., there's also OAuth2.0. A tangled conversation on it's own! And, addmitedly, one that's best implemented alongside a custom authentication feature, rather than as the only form of authentication.


    An Overview of Developing Slack Shortcuts

    For simple actions, sometimes you don't need a full on web form to accomplish something. An integration can do the trick. Slack makes it pretty easy to turn what could be a simple webform into an easy-to-use shortcut.

    It's a bit of a dance to accomplish this, so this will be more of an overview than an in depth look at the code.

    As an example, let's walk through how I'd create a Suggestion Box Shortcut.

    Slack API

    The first stop in setting any application up with Slack is at api.slack.com. Here we need to:

    1. Provide the Request URL for your API
    2. Create a New Shortcut "Suggestion Box"
    3. If loading data for select menus, provide an API URL for that as well.

    You'll create a callback ID that we'll save for later. Our's might be "suggestionbox"

    Developing your API with Bolt

    It's up to you how you do this! All slack needs is an endpoint to send a POST request. A dedicated server or serverless function works great here.

    Here are the dance steps:

    1. Instantiate your App with Slack Bolt
    2. Write methods responding to your shortcut callback ID
    3. Handle submissions.

    There are multiple steps because we'll receive multiple communications:

    Shortcut opens => Our API fires up and sends the modal "view" for the shortcut.

    User marks something on the form => Our API listens to the action and potentially update the view.

    User submits the form => Our API handles the request and logs a success / fail message.

    Bolt is used here to massively simplify this process. Without Bolt, the raw slack API uses http headers to manage the different interactions. With Bolt, it's all wrapped up neatly in an intuitive API.

    Blocks

    The UI components for slack are called blocks. There is a handy UI for creating forms and receiving the appropriate JSON in their documentation. Several great inputs are included, like multi select, drop down, date picker, and several other basic inputs that are analogous to their web counterparts.


    Redux Growing Pains and React Query

    AC: New Murder's announcement has been par for the course of a major release. Lots of good feedback and excitement, and some big bugs that can only be exposed out in the open.

    The biggest one was a bit of a doozy. It's around how we're fetching data. The short version of an already short overview is this:

    • Redux stores both Application State and Fetched Data
    • Redux Thunks are used to asynchronously fetch data from our Sanity API
    • We hope nothing goes wrong in between!

    Naturally, something went wrong in between.

    Querying Sanity

    Sanity uses a GraphQL-esque querying language, GROQ, for data fetching. A request looks something like this:

    `*[_type == 'animalImage']{
      name,
      "images": images[]{
        emotion->{emotion},
        "spriteUrl": sprite.asset->url
      }
    }`

    Similar to GraphQL, you can query specifically what you need in one request. For our purposes, we wanted to store data in different hierarchies, so a mega-long query wasn't ideal. Instead, we have several small queries by document type like the animalImage query above.

    The Issue

    On app load, roughly 5 requests are sent to Sanity. If it's a certain page with dialogue, 5 additional requests will be sent.

    The problem: Not every request returned correctly.

    This started happening with our beta testers. Unfortunately, there's not a ton of data to go off of. From what we could tell, everyone had stable internet connections, used modern browsers, and weren't using any blocking plugins.

    My theory is that some requests may not be fulfilled due to the high volume of requests at once. I doubt it's because Sanity couldn't handle our piddly 10 requests. More likely, there could be a request limit. Here, I'm still surprised it would be as low as 10 within a certain timeframe.

    Whatever the cause, we had an issue where API requests were failing, and we did not have a great way of handling it.

    Contemplating Handling Errors

    This project started 2 years ago when the trend for using Redux for all data storing was still pretty high. Things were starting to shift away as the project developed, but our architecture was already set.

    There is potentially a Redux solution. Take a look at this Reducer:

    function inventoryReducer(state = initialState, action) {
      const { type, payload } = action;
      switch (type) {
        case 'GET_INVENTORY_ITEMS/fulfilled':
          return { ...state, items: payload };
           ...

    The "/fulfilled" portion does imply that we do log actions of different states. We could handle the case if it returns a failure, or even write code if a "/pending" request hasn't returned after a certain amount of time. Maybe even, SAY, fetch three times, then error out.

    But, after doing all that, I would have essentially written React Query.

    Incorporating React Query

    It was time. A major refactor needed to take place.

    So, at the start, the app is using Redux to fetch and store API data.

    React Query can do both. But, rewiring the entire app would have been time consuming.

    So, at the risk of some redundancy, I've refactored the application to fetch data with React Query and then also store the data in Redux. I get to keep all the redux boilerplate and piping, and we get a sturdier data fetching process. Huzzah!

    Glueing React Query and Redux Together with Hooks

    To make all of this happen, we need:

    • A Redux action for storing the data
    • A query method that wraps around our Sanity GROQ request
    • A way of handling errors and missing data
    • An easy way to call multiple queries at once

    A tall order! We have to do this for 10 separate requests, after all.

    After creating my actions, migrating GROQ into query methods, we need to make the glue.

    I used a couple of hooks to make this happen.

    import React, { useEffect } from 'react';
    import { useQuery } from 'react-query';
    import { useDispatch } from 'react-redux';
    import { toast } from 'react-toastify';
    
    export default function useQueryWithSaveToRedux(name, query, reduxAction) {
      const dispatch = useDispatch();
    
      const handleSanityFetchEffect = (data, error, loading, reduxAction) => {
        if (error) {
          throw new Error('Woops! Did not receive data from inventory', {
            data,
            error,
            loading,
            reduxAction,
          });
        }
    
        if (!loading && !data) {
          // handle missing data
          toast(
            "🚨 Hey! Something didn't load right. You might want to refresh the page!"
          );
        }
    
        if (data) {
          dispatch(reduxAction(data));
        }
      };
      const { data, isLoading, error } = useQuery(name, query);
    
      useEffect(() => {
        handleSanityFetchEffect(data, error, isLoading, reduxAction);
      }, [data, isLoading, error]);
    
      return { data, isLoading, error };
    }

    useQueryWithSaveToRedux takes in the query and redux action. We write out our useQuery hook, and as the data, isLoading, and error results are updated, we pass it to our handler to save the data. If something goes awry, we have a couple of ways of notifying the user.

    These are then called within another hook - useFetchAppLevelData.

    export default function useFetchAppLevelData() {
      const snotesQuery = useQueryWithSaveToRedux('sNotes', getSNotes, saveSNotes);
      const picturesQuery = useQueryWithSaveToRedux(
        'pictures',
        getPictures,
        savePictures
      );
      const spritesQuery = useQueryWithSaveToRedux(
        'sprites',
        getSprites,
        saveSprites
    
      ...
    
      return {
        snotesQuery,
        picturesQuery,
        spritesQuery,
        ...
      };
    }

    useFetchAppLevelData is simply bringing all these hooks together so that I only need to call one hook in my component. It's mostly here to keep things tidy!

    import useFetchAppLevelData from './hooks/useFetchAppLevelData';
    
    function App() {
      const location = useLocation();
      const dispatch = useDispatch();
    
      const fetchAppLevelDataRes = useFetchAppLevelData();
    
      ...
    
    }

    A big task, but a full refactor complete!


    Writing Music

    I had a surprisingly hard time starting up the practice of writing music. Lots of false starts were involved, a ton of back and forth on if I even really enjoyed doing it, and the classic moments of cringing at some of my first tunes.

    In a lot of ways, music school _really__ helped me out with the skills and vocabulary needed to make songs.

    But then, the unspoken emphasis on theory-driven music and "correctness" in music was a really difficult funk to shake loose.

    So, this is advice for me-from-a-year-ago. Or, maybe it's for you! These are some things I've picked up wrestling in the mud. It's from the perspective of a performing musician switching gears to writing. Maybe it will help if that's you!

    Playful Mindset

    The meatiest part of getting into it is right here. It's gotta be fun!

    Gradually over the course of going through school and mastering an instrument, I assumed that what was meaningful was hard. I was fortunate to have wildly supportive instructors. Never did my music school experience come close to the movie Whiplash, is what I'm saying!

    But, still, systematically it's a competitive environment.

    On the other side of school, creative practices have to be done with much more levity.

    It helps that what I write is pretty silly! Take time to do things badly: Write the worst song ever on purpose. Accidentally write avant garde music. Write music to a silly prompt. Anything to get it moving!

    Honestly, it's a lifestyle thing. Making time for your play: Doing things just for the fun of it, feeds into this as well.

    There's a balance between finishing songs and always moving to what's most exciting. A balance between keeping a routine and letting enthusiasm guide you. That interplay is what keeps it exciting! Lean towards curiosity and interest as often as you can!

    Being a Connector

    Sometimes the ideas just come. Seemingly out of nowhere, after assimilating new techniques, sounds, and theory, it all just clicks!

    These days are a rush when they happen! And they are few and far between.

    In the meantime, I think taking the approach of a connector is really helpful.

    Say you want to write a song as if Beethoven wrote Lo-Fi hip hop chill beats to study to.

    You have two sounds to work with: Orchestral brilliance and a gentle beat.

    Like a DJ, your job is to mix them so that they work together. DJ's only have tempo and keys to adjust. You, on the other hand, probably have a lot more tools at your disposal (Swapping chords, rhythm, tempo, new melody, instrumental texture, mood, etc.)

    This is one of my favorite parts of the practice because it's SO JUICY! You get to break open and learn a little bit about what makes a certain artist, song, or style sound the way it sounds. There's some transcribing involved that's helpful here. Often times, the pieces that need connecting need some glue. Maybe even original material! So you are in fact writing something new, even if it's just a transition or a different bass line. At the end of all that learning, you have something new that's never existed before! Something complete that gave you lots of cool little tools for future-you writing future-music.

    Use References

    Expanding on the above point a bit, you shouldn't have any guilt around using refference.

    Steal like an Artist! You could read a whole little book on it. I'll tell you now: Everyone is stealing something. Even if you're Jacob Collier, you're borrowing from genres, artists, and experimental theory ideas. We're all just riffing on the major scale, at the end of the day!!

    Letting go of the weight of trying to be original helped me loosen up. Probably you're doing something original on accident, even if you're not trying. We all have such a unique collection of microscopic influences that have bent our ears and minds, it's bound to come through in what you make.

    Transcribe

    The best thing my general music classes gave me was just enough theory and ear training to transcribe. I also got a lot of weird hang ups about it, so I avoided it for a little while.

    Some myth busting on using the tool of transcription:

    Momentum is More Important Than Accuracy

    Sometimes recordings are muddy, chords are dense, or a sound just isn't sitting in the ear. Move on! Find something that kind of matches the musical/emotional intent, and get back to writing. It would be a shame to let go of learning all the other juicy things about form, harmony, melody, and instrumentation just because it's hard to hear exactly what extensions were being used in a passage.

    Know Enough Music Theory for the Major Tropes

    In jazz, you have to know about the ii V I. In classical, the dominant to tonic. Knowing enough of the reoccurring themes in a genre makes transcribing easier, and you get to focus on the building blocks around it instead of dissecting a technique you probably could have found in a blog article somewhere.

    Actually, blogs are great places to start with learning these, if it's a ubiquitous form like jazz.

    Transcribing is a Learnable Skill

    It's like anything. The more of it you do, the easier it is. Being reasonable with it at the start helps keep you moving. For example, maybe just start with the form of a song and then try to write something with the same form. Or focus on major harmonic points instead of every subtle chord shift. There's no test at the end of a transcription. So long as you're picking up a new technique and immersing yourself in a sound, you're learning what you need to from it.

    Releasing Music and Overcoming Inertia

    I have an arbitrary pacing for when I release music. It's broad enough where if I miss a day, it's no big deal, but frequent enough where it keeps my spirit magnetically pulled to always asking "What's next?"

    I've tried a few out: "Write something everyday" was impossible. "Record one album this year" meant it was never going to happen. But having a regular interval somewhere in between those two kept me going.

    Having to make it public, also, helps a lot with the accountability, even if no one is actively policing you on your schedule.

    Follow Your Energy Through the Day

    Classic productivity prescription. It clicked for me when I heard Dilbert's Scott Adams talk about it in his sort-of-autobiography. For him, writing happens in the morning, and rote-drawing happens in the evening.

    Translating to writing: Actual melody/harmony production happens in the morning, edits and tightening up the quality happens in the evening. Or, most of the time in my case, I took the evenings to practice an instrument like guitar or piano. It doesn't take design-type thinking to practice a scale or play an exercise.

    Keep a Collection of What You Like

    Likes on Spotify, bookmarks in your web browser, whatever! I personally keep a plain text file called FavoriteMusic.md where I copy in links, song titles, and notes on what I like about a song.

    I have a list for album ideas. Some may never happen. But on the days where there's simply a blank canvas, both of these lists come in handy.

    Make It Real

    This might just be helpful to me, personally. If it's not under my fingers, it doesn't always feel very real. At the very least, it becomes too cerebral if it isn't.

    Sometimes I find an idea while noodling on guitar. Or from playing sax. My favorite now is piano. Nothing beats it when it comes to visualizing harmony and getting used to thinking polyphonically.

    Largely, keeping a part of the process tactile has helped. The day I got an electronic keyboard hooked up to my laptop as a midi input, the game changed.

    Be In Motion

    Any creative thing β€” music, art, blogs β€” is cool because, in my mind, it's a still image capture of something in motion. Like those photos with Long Exposure effects and Light Painting.

    In other words - Don't worry about sitting down and not knowing what's going to come out. That's the fun part!! A dash of mystery and a pinch of romance on a day-to-day basis!

    You learn from starting. Get something on the page. Then mold it. I think very few folks know exactly how something will go before they sit down to write it. It's a process. In fact, the process is what's so rewarding anyhow! It's a journey of discovery, making something. That's the point of it all in the end. Not to have made, but to be making.

    Light Painting


    Fonts and CLS

    Fonts are a tricky space when accounting for CLS. They have the potential to not ping the CLS score too harshly. Though, if multiple element sizings are based on a web font loading, then it can add up. A nav bar plus a hero header plus breadcrumbs plus subtitle plus author name, it can all contribute to a larger score.

    Current solutions are primarily hack-y. There are a few worth experimenting with, and a few coming down the pipe

    Pure CSS Solution

    The leading idea offered up is to use font-display: optional. This will not load web fonts if they don't load in time. A great SEO solution, but not an ideal design solution.

    CSS Font API

    the CSS Font Loading API can be used to determine when the font has loaded, and then render the content to the page. document.fonts.onloadingdone will accept a call back where we can switch styles from hidden to display: block. In React, it could look something like this:

    const TextComponent = () => {
        const [fontLoaded, setFontLoaded] = useState(false)
    
        useEffect(() => {
            document.fonts.onloadingdone(() => setFontLoaded(true))
        })
    
        // fallback, render content after certain time elapsed
        useEffect(() => {
            setTimeout(() => setFontLoaded(true), 1000)
        })
        ...
    
        return (
            <StyledTextComponent $fontLoaded={fontLoaded}>
                ...
            </StyledTextComponent>
        )
    }
    
    const StyledTextComponent = styled.ul`
        display: ${props => props.$fontLoaded ? 'block' : 'none'};
    
        ...
    
    `;

    This is not an ideal solution for main page content. It wouldn't be ideal to have the content missing when SEO bots crawl your site for content. This would work great for asides, however.

    Font Optimization

    This article shares some interesting ideas on optimizing fonts so that they load before CLS is accounted for. Though, for some use cases these are heavy handed solutions. They include:

    • Hosting your fonts
    • Converting font to modern .woff2 format if not already
    • Caching fonts in a CDN
    • In the future πŸͺ: using F-mods, a method for matching the fallback font dimensions with the designed font

    Font Descriptions

    In the future πŸͺ we'll see font descriptions coming to CSS. A great overview is here on Barry Pollard's Smashing Magazine Article. The gist is that we'll have more control over adjusting the size of fonts as they're swapped out to mitigate the shifting that comes from a differently sized font.

    It's almost there, but will still take some time to fully bake.


    Aggregation in MongoDB

    Earlier I wrote on getting a quick bare-bones analytics feature running for a project.

    Now that we're recording data, I want to take a look at actually analyzing what we save.

    Knowing just enough about database aggregation goes a long way in providing insight into the data we're collecting! I'll dive into what things look like on the Mongodb side of things:

    Data Model

    My use case is pretty simple. All I need to know is how many users have played a game since it's release.

    So, our data model is similarly simple. Here's what a log for starting the game looks like:

    {
      "_id": {
        "$oid": "633eceff9b5e4de"
      },
      "date": {
        "$date": {
          "$numberLong": "1665060607623"
        }
      },
      "type": "play"
    }

    type here is the type of event that we're logging. "Play" marks the start of the game, "complete" when they finish, and a few in between.

    Aggregation

    When fetching the data, I want the Database to do the heavy lifting of sorting the documents and counting how many have played the game, finished it, and all the points in between. Mondodb's aggregation language makes this a really easy task:

     const aggregation = [
        {
            // Find documents after a certain date
          $match: {
            date: {
              $gte: new Date('Fri, 30 Sep 2022 01:15:01 GMT'),
            },
          },
        },
        // Count and group by type
        {
          $group: {
            _id: '$type',
            count: {
              $sum: 1,
            },
          },
        },
      ];

    Here's what that returns (with fake data):

    {
    "play": 100000000, // Wishful thinking!
    "start act 3": 136455,
    "complete": 8535,
    "start trial": 1364363
    }

    The $group operator is pretty flexible. With a little more elbow grease, you could also aggregate counts from month to month, and display a very slick line chart. πŸ“ˆ

    Coming back to the point from my last article, since we're measuring a game, the interaction is more important to us. This data is more reliable and so closely integrated with our application since it relies on actions that, more than likely, bots and crawlers won't engage with as often. It's still likely not a perfect representation, but it provides enough data to gauge impact and see where the bottlenecks are in the game flow.


    Navigating NPM Package Changes

    Subtitled: The Case of the Hidden API! πŸ”

    An interesting mystery came up with work recently.

    The Gist: A library we were using started behaving strangely. After doing some digging, I found out it was because the devs quietly changed their API with a minor release. Here's the detective work that was involved in sniffing this info out:

    The Problem

    We use Keen-Slider for all of our sliding needs. I wrote a bit of custom code on top of the react library. We needed the slider to render part of a slide's text if the string length exceeded a certain amount. We could then show the rest with a "Read More" button.

    One day, out of the blue, this behavior started to act wonky. And it had something to do with a set of options I passed in.

    Looking back to September 2021

    A completely separate bug came up as I was developing this "Read More" feature back in September. To make a long story short, I ended up finding a solution with this issue ticket on Keen-Slider's Github Repo. The main fix being setting a specific option:

    the autoAdjustSlidesPerView property is not described in the doc. You need to set it to false.

    autoAdjustSlidesPerView is indeed nowhere to be found in the docs, but it was in the codebase at the time and solved my problem like a charm. So I tossed it into our solution as well.

    All was well worth the world.

    Quiet Deprecation

    That is UNTIL we fast forward to where we left off in the story!

    I get word that the source is a set of options, including autoAdjustSlidesPerView.

    My first thought was "Ok, let me see if I can understand exactly what autoAdjustSlidesPerView is doing." So I look to the docs to find out.

    But they're not in the docs.

    No sweat! Let me just look at the source code.

    But there's no sign of it in Keen-Slider's source code.

    Looking back at the issue ticket, an update came in a few months after from the library's maintainer:

    This problem is gone with the new major version.

    As simple as that. No mention of the autoAdjustSlidesPerView.

    But I realized something: More than likely, that change likely included removing autoAdjustSlidesPerView from the API.

    It was never in the docs, just a secret fix for particular edge cases. It makes sense why there would be no word about it.

    The comment above states the change was with a major version update, but it was likely taken out with minor patches.

    (An aside on versioning: our NPM versioning for this package is with the carrot ^5.3.0. This means we wouldn't have automatically bumped up to the major version mentioned. Hence why I believe the change likely happened incrementally with a minor version change.)

    Informed Changes with GitHub

    Ok! Problem sourced. So we just need to take the option out, maybe write some new custom logic to handle the original problem, and be on our way.

    Our company is not on a scale that justifies writing out extensive tests, so I didn't have that to fall back on when I pulled out the autoAdjustSlidesPerView option. I had to make sure myself I wasn't undoing what ever I was trying to fix in the first place back in September!

    Thankfully, Git and GitHub make it incredibly easy to source exactly when I added this line of code. Good git hygiene on my part informed me why I made the change.

    At the time of adding the code, I was committing code frequently. My methodology: If I could explain what changed in one sentence, I have a commit. If it takes two sentences, then it's two commits and I need to commit more frequently.

    Here's the commit message for when I added autoAdjustSlidesPerView into the codebase:

    Correct forced slider view in reviews widget when slidesPerView matches number of cards.

    Great! I know exactly what I was trying to solve with this code, and I can see all the relating code around the change.

    With all of this information, I was able to ensure both the bug from September and this new bug was fixed!


    HTML Form Validation is Pretty Good!

    After spending a fair chunk of time working in forms the React way, I've gotta say β€” we already get a lot of goodies with the basic HTML inputs.

    There's a project I worked on recently that had me working in Vanilla JavaScript. No React, no libraries, just raw HTML, JS, and CSS.

    I went in mentally preparing to have to write my own library. I was prepped to recreate Formik for this project. But I didn't really have to!

    Here are some of the niceties that saved me a ton of time:

    Input Pattern Attribute

    Without needing to write any JS, you can check to ensure a text input matches a regex pattern. Here's an example from w3schools:

    <form action="/action_page.php">
      <label for="country_code">Country code:</label>
      <input type="text" id="country_code" name="country_code"
      pattern="[A-Za-z]{3}" title="Three letter country code"><br><br>
      <input type="submit">
    </form>

    The title attribute is the message that shows when there is a discrepancy between the input value and your pattern regex.

    Constrain Validation API

    That may not cover all use cases, but modern browsers come with an API for further validation customization.

    The Constrain Validation API is available on most form inputs. There are a couple of methods that are useful here: setCustomValidity() and reportValidity()

    setCustomValidity allows us to set a custom error message. reportValidity will then show the message when we call it on an element.

    When we get to handling form submission, these give us a way of still working with the browser's built in UI and form API.

    const handleSubmit = (e) => {
        // We use our custom name validation here
        const nameElm = document.querySelector('#contact-name');
        const isNameValid = validateName(nameElm.value);
    
        // And integrate with the API here
        if (isNameValid) {
            // If valid, we set the message to an empty string, meaning it passes.
            nameElm.setCustomValidity('');
        } else {
            // If invalid, setting an error message will mark the input as invalid.
            // Report Validity then shows the message.
            nameElm.setCustomValidity('Please enter a valid phone number');
            nameElm.reportValidity();
        }
    }

    Bonus: The CSS psuedo-classes are available when working with the form in this way. We can still make of use CSS such as this:

    input:invalid {
      box-shadow: 0 0 5px 1px red;
    }
    
    input:focus:invalid {
      box-shadow: none;
    }

    More details and examples are available on the MDN article for HTML form validation.


    I Made A Video Game - AC New Murder

    Overview

    Bucket list item complete β€” for the past year and a half I've had the chance to develop a game with my sister Jenn!

    Home Screen

    A massive labor of love, AC: New Murder is a web-based visual novel where you are investigating the details of the almost-murder of a villager on the island. In classic Phoenix Wright style, you are helping clear the name of Γ‘enn, the initial suspect who claims to be innocent. By chatting with the islanders and visiting a real New Horizons island on the Nintendo Switch as a companion to the web app, you'll uncover clues that will help reveal who-dun-it! There are even trials for you to put your knowledge to the test, presenting evidence, and objecting to suspicious testimonies!

    Jenn wrote the script, illustrated the characters, and handled all of the Animal Crossing customizations for the New Horizons island that players are encouraged to visit on their own copy of New Horizons. Her story and characters are dramatic, hilariously funny, and are overflowing with charm and personality!

    Ankha looking frustrated

    My part was designing the application, developing the software, and creating a custom CMS to load in assets, story, and handling any story-related game logic. Also, I wrote some music for it!

    This was a blast to work on! I had the chance to hone my web development chops, take an app from ideation to release, and collaborate on a massive project. Read on for the story of how this came to be!

    Application Features

    When we started planning this in November 2020, we had played around with different ways that this could come to life. A full on web comic would have taken ages with creating individual art assets for the story she had in mind. Software like GameMaker were a viable option, but had limitations for how much genuine control we had over the feel of the game. We also wanted an experience where there were very few barriers to entry β€” nothing to download, just a link to visit, a la the web experiences we grew up with on sites like Homestar Runner or Homestuck.

    Enter me and my skillset! Working primarily in React and web development technologies, this felt like the perfect opportunity to craft a web app for both the players as well as Jenn to be able to load her pages and pages of dialogue into, controlling also the emotions characters made with each phrase, who they were talking to, what evidence was required, and so on!

    Julian riddling you a riddle

    This project exists as two separate applications, similar to a blog: A client site and a backend portal for editing the application data.

    Our most important features for the app were:

    • Playable game that would save user data as they progressed
    • Accessible from a web browser
    • Allow for branching dialogue paths
    • Handle storing and presenting evidence
    • Showing different dialogue based on different game flags being fulfilled
    • Allow a "Free Roam" mode to ask villagers questions about any item in the inventory

    For the back end application, our CMS features:

    • Intuitive interface of storing different types of data (text, images, and preferences)
    • A way to referencing other documents
    • Flexible UI for hiding any uneccesary fields when inputing game assets
    • Ways of easily updating the schema as features were added without invalidating older documents

    The Tech Stack

    Some tools familiar to me and some new!

    Front End (React, Styled Components, and SCSS)

    My most fluent framework when I began the project. To accomplish that feeling of it being a game and not just a website, using React entirely for rendering pages, transitioning, and responding to state change was a natural choice.

    Page level styling is done in SCSS and component level styles are done in styled components. This was more a change to match the workflow that worked best for me at whichever point I was developing the app.

    Data Layer (Redux)

    I opted for another familiar friend Redux for managing application level state and data. While I investigated options like react-query for caching and making requests, it felt like an oversized solution for our use case. Each page would only need to load once and it was unlikely that players would return to those pages later, so I wasn't concerned about the built in caching. I was much more interested in having a fine-grained control over the structure of the data store and how individual requests and state updates were handled. Although it can be a more tedious solution in the short term, Redux served this end well.

    User data is simultaneously updated in Redux and Local Storage. On load, the app checks for save data in local storage, and then loads it into the Redux store for use while playing the game.

    Agent S keeping it cool

    CMS (Sanity)

    The project was complex enough to reach for a CMS instead of developing my own backend. We needed something flexible enough that would be able to handle our game logic (we weren't just building a blog, after all.) I wanted something that came with an intuitive interface out of the box for Jenn to use. And, JavaScript being my language of choice, I wanted a service that allowed me to develop in my primary language. Sanity checked all of our boxes here!

    With Sanity, Schema development was a breeze. There was plenty of flexibility to reffernce different documents, create unique document types, nest documents within other documents, the whole sha-bang! Querying the data on the client was elegent as well. Sanity uses their own language GROQ, which will feel familiar to anyone that's used GraphQL. All in all, for this project, we're pleased to have gone with Sanity!

    Hosting

    Site hosting and building is done on Netlify. The code for the site is hosted on github. Every time changes are pushed, Netlify will take in the new code, build, and publish the updated site all in one go.

    Sanity takes care of CMS hosting on their platform easily. For our scope, it was straightforward and cheap to implement.

    Collaborating β€” Left and Right Brain

    It's an oversimplification to say that our roles were focused one hemisphere of the brain β€” Code is as creative as it is literal, as is illustration and story writing.

    We were coming from different skillsets, though, and were both learning along the way how best to communicate the needs of our side of the project.

    What got us through wasn't so much a particular tool, but more of a mindset of being open to finding creative solutions to walls, accepting limitations where we had them, and creating an accepting environment for ideation and flexibility. Thankfully, we're respectful, kind, and resilient in our lives as brother and sister outside of the project, so this was a natural flow to get into for us!

    Our process for coming together involved meeting regularly to plan our next priorities together, work out what we needed to do individually, and getting feedback on recent steps.

    Not to mention celebrating our wins! My favorite meetings were ones where I could say "I was testing this part of the site, and just saw your new illustrations for Katt! SO CUTE!!!"

    Project Management

    We did end up needing a tool for staying organized! We've been using Notion at work along with their Kanban Boards to keep track of projects at work.

    Pulling back the curtain to show an internal guide written in Notion

    I created a board for our project to keep track of development tasks, and it soon expanded. At the end of development, we had a shared board, road map, and several Sanity guides that I wrote for Jenn to reference when she needed to add in some in-frequently used game logic to CMS

    Wrapping Up

    Growing up, I really wanted my sister and I to collaborate on a big creative project together! I personally envisioned Jenn and I would start a virtual band similar to the Gorillaz, me making music while she illustrated band mates. BUT! This, I think, is even more fun than what I fantasized!

    Cool animal animations!

    I had fun bringing life and interactivity to Jenn's story! It was rewarding to continue to learn and explore the technologies I was familiar with in a new way. We had ups and downs (and left and rights!) during development, but we were able to work through all of our blocks to create something really unique and special.

    I hope you check out AC: New Murder!

    If you're interested in reading more about the nitty gritty of developing the tech or how I managed the project, you can read my deep dive on each below:

    You can also follow Jenn's art and work from her site or twitter.

    View the code on GitHub.


    Developing A Game In React

    What are the differences between developing a game and making a well organized web application? Surprisingly, there aren't as many as you would think!

    Before starting development on AC: New Murder, I thought the move away from flash on the web meant the end of web games and shows like homestarrunner.

    Gameplay of Elvis getting questioned

    In listening to an interview Drew Conley gave on his game Danger Crew, my hope was sparked. Danger Crew and New Murder both are built on modern web development technology. At the end of the day, a game manages state, controls interactive elements, and renders specific views for different actions, just as a web application does.

    Here I'll be pulling back the curtain on how those similarities came through in developing New Murder.

    Rendering Dialogue

    In this Sanity article we looked at the different data types required for all of the games assets and logics. A majority of that data is dialogue text. And much of the actual game logic in the app is handling what to do with that dialogue, especially when they move towards branching paths.

    I was inspired by Twine, software I discovered way back when Ryan North of Dinosaur Comics fame wrote his first chose-your-own-adventure book. The software breaks down key moments in the narrative into individual nodes. The nodes can connect to each other by different response options and paths.

    The client app for New Murder does the same. A dialogue scene runs through the phrases and animal illustrations. At the end, the user is either prompted for evidence, or is asked to answer a multiple-choice response prompt.

    From there, the user is taken to the next dialogue scene based on their answer.

    Julian Riddling you

    My favorite example of this is Julian's Testimony in the first trial. If I were to graph out the structure, it would look like this:

    0 => 0 => 0 => 0 => Repeat or move on to next dialogue
    |    |
    V    V
    0    0
    |
    V
    0

    By checking the dialogue properties, the game knows whether to render an objection option, and then once following down the next dialogue path, the game also handles when to offer presenting evidence or offering response options. When reaching the end of an "objection" path, the user is then sent back to the main testimony path, one step further than they were before.

    State Management

    This is where I was surprised to see that I felt most at home developing that app from my experience with web development. There were certainly more pieces of state to manage with a game that stored the player's inventory, previously visited conversations, what conversation they were in, what paths they were on, and the list goes on. Managing that state, however, and the best practices for organizing it, very closely followed what goes into a web application!

    UI state, API data, and player save data were stored through separate reducers within the store. The store's structure at a top level was structured like so:

    store/
    β”œβ”€β”€ app/
    β”œβ”€β”€ dialogue/
    β”‚   β”œβ”€β”€ currentDialoguePosition
    β”‚   β”œβ”€β”€ currentdialogueName
    β”‚   β”œβ”€β”€ phrases
    β”‚   └── . . .
    β”œβ”€β”€ conversations/
    β”œβ”€β”€ inventory/
    β”œβ”€β”€ health/
    β”œβ”€β”€ sprites/
    β”œβ”€β”€ notepad/
    β”œβ”€β”€ loaded/
    β”œβ”€β”€ inquiry/
    β”œβ”€β”€ act3Scenes/
    └── specialEvents/

    State updated in three instances:

    1. On App load, user's save data was loaded in from local storage, where the game continually simultaneously saves when the user makes progress
    2. On page load, the specific conversation or menu screen requests for the relevant data from the API
    3. On interaction, UI changes are made, including progressing through the dialogue, adding to the player's inventory, or receiving an achievement

    All familiar tasks when designing any other web application, such as a user account portal. Even marking their game progress and saving their game data is not too dissimilar from managing form data, just without the inputs and validation.

    A look at Agent S's Notes

    A particularly interesting piece of state are Agent S's notes. These are tasks within the game with particular ways of completing them. An Agent S Note (SNote, for short) is stored into redux like so:

    {
        count: 4,
        description: "Get Lucky to remember yesterday by presenting evidence to him.",
        name: "luckyMemories",
    }

    There are two types of SNotes:

    1. One-offs, requiring only one action to check them off
    2. Counters, requiring multiple actions to check them off.

    In the game, Jenn is able to add points where player's achieve a SNote by adding the SNote's name to the dialogue field. The app knows that when it comes across "luckyMemories" while going through dialogue, to check off the task. If it's a one-off, it's done! If it's a counter, it increments the number of times it's seen "luckyMemories" until reaching 4, then it's complete!

    Achieving SNotes eventually leads to unlocking the final part of the game, where the player needs the most evidence and deepest understanding of the events that transpired. These were a fun way to add direction and celebrate progress through the game!

    Toast messages filling the screen in the mobile app

    Saving Player Info

    As mentioned earlier, I opted to use local storage to maintain player save data.

    I considered the option of spinning up a server, allowing users to create an account, and log in from any device to continue playing the game. For this project's particular requirements, needing a very thorough front end application and a thoughtful crafting of the CMS for Jenn, I was looking to see where I could save time and energy for those two major focuses.

    Most players will likely only play the game on a single device. And save data is not highly sensitive or personal. That considered, using local storage was a reasonable compromise for the project.

    Katt looking cool

    Animations

    The app really came to life when I started adding in animations! I wanted to give as much of a native game feeling to our app as I could. The animations are mostly small transitional touches that add a bit of life and playfulness to the game. The techniques I used were:

    • Text Animation with Typist
    • CSS Transition Animations
    • Keyframe animations
    • Framer Motion Animation Library

    Pure CSS Animations

    Built in CSS animations cover the small touches, such as menus swiping in and icons shaking or bouncing. Below is one of my favorites: Agent S's Notepad.

    Slide in and out effect for SNotebook

    One of the 12 Principles of Animation is Anticipation, a gesture preparing the viewer for an action. The pulling back before springing forward motion here is just that, and was accomplished with this simple line of css:

    .notepad_wrapper {
        transition: transform 0.8s cubic-bezier(0.47, -0.51, 0.46, 1.64);
    }

    Here's another example: keyframes for a bouncing caret icon or the error message shake.

    @keyframes bounce {
      from {
        transform: translate3d(0, 0, 0);
      }
      to {
        transform: translate3d(0, 10px, 0);
      }
    }

    JavaScript Animation

    Since it's a heavy resource, I wanted to keep animating with JavaScript to a minimum. It's worth noting that this is different from JS merely firing off animations that are then handled by CSS. That we'll explore how JavaScript is firing off css animations in the section below.

    The one case where JavaScript was the best tool for animation was with the dialogue text. Character speech was a major part of the game and would be a big contributor to making the app feel like a true native game.

    Dialogue text appearing on screen

    I initially scripted out a vanilla JS type-writer function that would lay out pieces of dialogue character by character. Eventually we went with another package for this functionality to more easily handle non-text portions of the dialogue, such as icon images and text highlighting.

    CSS Transition Group

    Animal illustrations were loaded in dynamically, making them a perfect use case for React Transition Group. Animals slide in and out, sometimes taking center stage, or being pushed off to the side when another animal hops in

    Characters sliding in and out of frame

    There are different classes dynamically set for the animals based on their positioning when they enter. Different animations handle the transition from off stage to center, center to left, and left to right.

    Framer Motion

    The true native feel came from bringing in page transitions with Framer Motion. Each page has a loading screen overlay that swipes in and out between the stages, almost like a curtain for scene transitions.

    Slick Scene Transition

    Here's how it works: Framer Motion has built in functionality for working with React Router. What needs to happen is a handshake for a really smooth transition. After a link is clicked (or an action leads to switching pages), Framer Motion intercepts the request to React Router. It delays the new page from loading until the exiting animation has occurred.

    Then, the next page loads. The next page also has an on load animation, mirroring the exiting animation of the previous page. So on the first page, the loading overlay swiped in. On the next page's load, a copy of the overlay is displayed and swipes out.

    Mobile and Desktop View

    With much of my time and energy focused on handling game logic and creating the CMS, the flexibility of the game's UI is another area where I had to eventually make a compromise time-wise.

    Our initial ambition was to create an application that looked elegant on all devices. Phones, tablets, and desktop. When working with primarily text and an endless scrolling page, there's a lot of room for flexibility and responsive design. When it came to developing a game with specifically sized assets and menus, it became more complex.

    Ultimately, I was able to create two views: Desktop and Mobile, with tablets rendering the desktop version.

    Desktop has a fixed window for viewing the game:

    Desktop view of the app

    Mobile is a full screen rendering of the game:

    Mobile view of the app

    The Mobile version was optimized for most popular dimensions, iPhones being my basis. Things got strange with tablet dimensions, however, and it became difficult to handle a very particularly staged set of character positions, menus, and fixed background size in this size range.

    That's what lead to showing the desktop version on tablet devices. We had hoped for an experience similar to mobile, but given the constraints of a position sensitive layout with less flexibility, it's another one I saw as worth taking.

    Wrapping Up

    There's much to be proud of with this application, but I'm happiest with how the front end turned out! Working through game logic was an interesting puzzle. Managing the application's state and performance was delightfully surprising! The process drew from my experience working in web development seamlessly. And the small touches of animation help make the story come alive. Even deciding on trade offs boosted my confidence in my ability to work with the resources I had to create a fun piece of software.

    Merengue questioning Agent S's Question

    I hope you check out AC: New Murder!

    If you're interested in reading more about the nitty gritty of developing the tech or how I managed the project, you can read my deep dive on each below:

    You can also follow Jenn's art and work from her site or twitter.

    View the code on GitHub.

    Sources

    Building Steam Games With React

    Drew Conley

    Danger Crew

    Twine

    Ryan North On β€˜To Be Or Not To Be,’ β€˜Adventure Time,’ And Why Books Where You Don’t Choose The Adventure Are For Babies

    To Be Or Not To Be


    Using Sanity as a Level Maker

    Something that goes under appreciated when it comes to developing anything β€” games, websites, music β€” is how much time goes into making the tools that you need to make your project.

    Musicians familiarize themselves with scales and harmonic language.

    Developers use libraries, frameworks, and even still, build their own reusable components on top of it.

    And Game Makers develop level builders.

    And so, that's what I did for my collaborative visual novel!

    I mention in my previous article the why and how of choosing Sanity as our backend CMS. Here I'll be describing the though process behind developing the Schema for the game along with how some of that translated into the game.

    Schema Development

    Sanity is a document based database. The structure is not too different from MongoDB. You have a collection of documents, which can have nested elements to them, and that can connect to other documents relationally. We have several collections for the game:

    Content
    β”œβ”€β”€ Dialogue
    β”œβ”€β”€ Inquiry
    β”œβ”€β”€ Conversation
    β”œβ”€β”€ Emotions
    β”œβ”€β”€ Animal
    β”œβ”€β”€ Asset
    β”œβ”€β”€ Animal Images
    β”œβ”€β”€ Item
    
    etc.

    Here's an overview of the schema in action:

    Conversations => Dialogue (contains phrases)

    Phrase => Text => Speaker => Emotion.

    Let's dive into a few to get a sense for the game's structure.

    Dialogue

    The heart of the game is text. So this was a jam-packed Schema.

    Dialogue objects look like this:

    {
        name: String,
        Conversation: ConversationID,
        phrases: Array,
        responseOptions: Array,
        isFinalDialogue: Bool,
        ...
    }

    There are a few other properties, but the important ones are displayed.

    Each dialogue is linked to a conversation. The conversation is the equivalent of a whole scene with an animal, say, you talking to Ankha in the first act. Conversations are broken up into separate dialogues, with player responses being the separator.

    Within each Dialogue is a list of phrases. A little More on those later, but essentially, these are the individual lines of text, about 2-3 sentences.

    Response Options are displayed at the end, giving the player the chance to branch down multiple paths, if needed.

    The last dialogue in a conversation is marked with as is final dialogue to signal to the game that it's time to transition to another page.

    Phrases

    These are a subdocument, so they don't have their own collection. But, their schema is pretty interesting! A lot is packed in here:

    {
        text: String,
        emotion: emotionID,
        speaker: animalID,
        centeredAnimal: bool,
        animalOrientation: "left" || "right",
        specialEvent: eventID,
        ...
    }

    Again, a bit of a simplification here. But these are the main components:

    Text to render, who is speaking so we can highlight their sprite and show their name in the dialogue box, and several other positioning properties.

    Achievements, mission clearing, and being given an item is handled in our special event property. When the game comes across this point in the conversation, it looks up the event and has special logic for handling it.

    Linking Dialogues

    Handling multiple branches took some doing, but stayed a lot less complicated thanks to the modular nature of our schemas!

    As mentioned above, we're able to go down branching paths depending on the dialogueID that's linked to a given response option. This is also possible when presenting evidence.

    There's a point in the game where it's a bit of a free for all. Animals can be asked about any item in the users inventory, sometimes providing different dialogue depending on what the user has already achieved in the game. Implementation here was largely similar to the above - link items to a specific dialogue document. BUT, if a prerequisite event is required to see a secondary response, make that check first.

    The nitty gritty of that implementation is too large for a single blog post. It all comes back to this main implementation of separate dialogues linked relationally by items or response options.

    Customizing the Back End Client

    Sanity Studio was great to work with! Even for contorting it to suit our needs for level building, it was flexible enough to accommodate us.

    Here's a quick peak at what a dialogue document looked like in Sanity:

    Sanity Studio

    Conditionally Rendering Input Elements

    We had a few situations of "If prompting for evidence is true, we also need these fields to show."

    Sanity also handled this really nicely! It came in handy as a reminder to Jenn what was and wasn't needed in certain game scenarios by having the back end client nudge her in the right direction.

    Here's an example of what this looks like in Sanity's schema, from their documentation

    {
      name: 'link',
      type: 'object',
      title: 'Link',
      fields: [
        {
          name: 'external',
          type: 'url',
          title: 'URL',
          hidden: ({ parent, value }) => !value && parent?.internal
        },
        {
          name: 'internal',
          type: 'reference',
          to: [{ type: 'route' }, { type: 'post' }],
          hidden: ({ parent, value }) => !value && parent?.external
        }
      ]
    }

    Wrapping Up

    There was a fairly large upfront cost of planning out how to structure the game and enable Jenn to add in assets and story. We saw it evolve pretty quickly as the game grew and grew in complexity. Taking that time upfront helped save a lot of headache down the road! I'm grateful to have used a system that was resilient enough to change as our needs inevitably pivoted.

    I hope you check out AC: New Murder!

    If you're interested in reading more about the nitty gritty of developing the tech or how I managed the project, you can read my deep dive on each below:

    You can also follow Jenn's art and work from her site or twitter.

    View the code on GitHub.


    Project Management for Game Development

    Project Managing

    Guides

    Our CMS continued to grow and grow. Often times with features that were important, but only needed using a handful of times.

    In most cases, the CMS had descriptions and self-explanatory field names that would guide Jenn's work. And then there were a few that needed a bit more explaining. I realized we needed Documentation.

    I wrote a handful of guides with images and gifs to serve as reference for those less used features. I would write them out as I finished a feature, then hop on a call with Jenn to talk walk down the guide. I would screen share my own computer working through an example so there was a clear picture of how it worked in Sanity, and how it would end up looking in our main application.

    Screenshot of a Notion Guide on Positioning and Phrases

    Communicating Trade Offs and Keeping an Eye on the Big Picture

    Towards the end of the project, feature creep continued. We had to continue discussing what an end product looked like.

    At the start of the project, I was here to serve. Anything we could dream up, I wanted to implement. I didn't mind diving deep into small bugs and rise with excellent solutions.

    Nearing the end of the project, I was excited to release, and saw the beginnings of perfection robbing us of a "good enough" application.

    My role towards the end of the project shifted towards a more informed leader in the technical domain. I saw myself switch from "I think we can do all of that!" to "Ok, let's talk about what value that feature would add to the game in conjunction with the time and trade offs adding that in presents."

    I was able to help us come to some hard calls, asking for creativity on Jenn's side to find story solutions to issues that were not so easily solved through the tech.

    Technical Debt

    In many ways, I'm happy with how the code has stayed modular and organized. Additions were easy to add in many portions of the code, including the schemas, inventory menus, and at a page level.

    And still, the project acquired it's fair share of technical debt.

    My time and resources for the project began to shrink as we neared the end, and I became less and less able to maintain it in the way that I would like. Getting the application done and out in the world took precedence over a pristine, DRY codebase.

    This has been a big lesson for me. Ultimately, I stand by the choice to focus on implementation over maintenance for this project β€” neither of us are planning to return to it. I also don't intend on releasing the code as any sort of basis for other people to use this as a game engine anytime soon.

    At the same time, I now deeply understand what technical debt looks and feels like, how it can come about gradually, and that for a project that's meant to be sustained and continually iterated on, it's invaluable to take the time to maintain, refactor, and plan for the future.

    Wrapping Up

    This was my greatest area of growth. I picked up some great technical skills, certainly. Actually managing the project, triaging feature requests, sticking to an MVP, and communicating to a non-technical collaborator β€” that's a whole suite of juicy soft skills.

    My experience with teaching music came in handy here! Lots of overlap from planning out the next step in a beginner saxophonist's education. And, some interesting new ways of adapting when it came to shipping an entire application!

    I hope you check out AC: New Murder!

    If you're interested in reading more about the nitty gritty of developing the tech or how I managed the project, you can read my deep dive on each below:

    You can also follow Jenn's art and work from her site or twitter.

    View the code on GitHub.


    Iwata on Creative People

    Cover Image for Iwata on Creative People

    I grew up with a lot of the games and consoles developed by the late Satoru Iwata, Nintendo's former President. "Ask Iwata" is a sort of auto-biography, a collection of interviews Iwata gave for Shigesato Itoi's company / site with a few excerpts from the official Nintendo "Iwata Asks" interview series.

    The book came across as surprisingly personal for someone that's a sort of legend in the space. Some characters garner mysticism through the distance between them and their audience, but Iwata was a proponent of transparency and open communication, as is evident by all that's shared in these interviews.

    The war stories are impressive. His early experiences of being a tech wiz, saving a couple of games like Earthbound and Pokemon Gold and Silver from development hell, are a testament to him being a master developer.

    But it was his view on people that struck me.

    It's not what I expected going in, though he did spent more of his life in management. Iwata transferred much of his logical thinking and desire to delight others over into his leadership.

    Here's one example of this is his insight onto why creative folks can come into conflict on a shared project:

    Unless they have the self-confidence to announce, "I'm the best," engineers and artists never make any headway. Most programmers, too, believe their way of doing things to be superior. When these kinds of people join forces on a project, some conflict is unavoidable. Creativity, after all, is an expression of the ego.

    Really insightful to frame creativity as a means of putting a piece of ourselves out there.. Maybe not all forms of creativity, but certainly design centric, engineering type roles, take a bit more of that characteristic to it.

    Here's another one on respecting the talents of others:

    "There will always be people who see things differently than you. Perhaps to an unreasonable degree. Still, these people surely have their own reasons, their own history and values. Moreover, they're bound to be able to do things you can't do, and know things you don't know. This doesn't mean accepting everything they suggest, but respecting the fact that they have skills you lack, and are doing things that you can't do yourself. Whether or not you can maintain this respect will vastly influence how much fun and fulfillment you get out of a job."

    I plead guilty to the artistic trope of wanting to be good at it all! In an age (and, for me, a stage of life) that glorifies remarkable individuals, this was a refreshing passage to come across. It's that gradual opening up of accepting yourself for your limitations and celebrating the differences with others that brings buoyancy to work and opens up possibilities.


    Customizing Field State in React Final Form

    React Final Form is a delightful library. So much comes out of the box, and it's extensible enough to handle custom solutions.

    I've been working with it a fair amount recently. Here's what adding custom logic looks like:

    Scenario

    Say that you have data that needs to update two fields within your form. An example might be that you have an event form with the fields:

    • Name: Skate-a-thon 2022
    • Start Date: August 3rd
    • End Date: August 5th

    Let's also say that Skate-a-thon got rescheduled to another weekend.

    When going in to push back the start date, we want our form logic to automatically update the end date as well - to also go back 7 days.

    Simple Set up In Final Form

    I'll leave the initial set up to the React Final Form docs.

    Let's pick up with a component that looks like this within our form:

    <h2>Render Function as Children</h2>
    <Field name="name">
      {({ input, meta }) => (
        <div>
          <label>Name</label>
          <input type="text" {...input} placeholder="Name" />
          {meta.touched && meta.error && <span>{meta.error}</span>}
        </div>
      )}
    </Field>
    
    <Field name="startDate">
      {({ input, meta }) => (
        <div>
          <label>Start Date</label>
          <input type="date" {...input} placeholder="Start Date" />
          {meta.touched && meta.error && <span>{meta.error}</span>}
        </div>
      )}
    </Field>
    
    <Field name="endDate">
      {({ input, meta }) => (
        <div>
          <label>End Date</label>
          <input type="date" {...input} placeholder="End Date" />
          {meta.touched && meta.error && <span>{meta.error}</span>}
        </div>
      )}
    </Field>

    We're assuming we already have our <Form> wrapper and submit button elsewhere.

    So far with this code, all of our fields will update independently.

    To tie together our endDate field with the startDate, we'll need a custom onChange method and a way to access the form API ourselves.

    Form Hook

    Two hooks come in handy here:

    • userForm(): Gives us all access to utility methods for our form. It can be called in any component within the <Form> wrapper.
    • useFormState(): As the name implies, gives us access to the current state, including values and meta data such as what fields have been "touched"

    We'll open up our component with these hooks:

    import React from 'react';
    import {Field, useForm, useFormState} from 'react-final-form';
    
    const FormComponent = () => {
      const formApi = useForm();
      const {values} = useFormState();
    
      return(
        ...
      )
    };

    And then use these in a custom onChange handler on our field

    <Field name="startDate">
      {({ input, meta }) => (
        <div>
          <label>Start Date</label>
          <input
            type="date"
            {...input}
            placeholder="Start Date"
            // Custom onChange below
            onChange={(e) => {
              const newStartDate = e.currentTarget.valueAsDate;
    
              // Lets assume I've written a COOL function that takes in the
              // Initial values for startDate and endDate, and calculates a
              // new endDate based on that
              const newValue = calculateNewDateBasedOnNewStartDate(newStartDate, values);
    
              // Update both values through the formApi
              formApi.change('startDate', newStartDate)
              formApi.change('endDate', newValue)
            }}
          />
          {meta.touched && meta.error && <span>{meta.error}</span>}
        </div>
      )}
    </Field>

    There you go! The endDate will update alongside the start date from here!