Modeling UI States in A React Form Component Using A Finite State Machine

小杨
Mar 4 · 9min read
摄影者Garett MizunakaUnsplash

At OkCupid, we are building a new location search experience for users to set their own locations based on country, city name, and GPS.

In contrast to the earlier iteration of the location search experience, data that enables the search is no longer statically loaded from the source code. The new search experience is powered by multiple sources of data and services from the back-end and provides more sophisticated user feedback for error and success cases.

What makes the front-end of the new location search UX complex is that there are a lot of moving parts. The UI needs to manage user input while data is still being loaded. The UI also needs to display feedback to the user based on the results of the search and handle various error conditions like network connectivity and server-side errors. Under the hood, the simple form that users see when selecting a country and typing a city name has to coordinate requests to the back-end with events from the user.

This post explores an architectural design pattern called the finite state machine (FSM) that we used at OkCupid to craft a robust location search UX.

The primary use of FSM has been inprogramming embedded systemsbut in recent years, there has been an emergence of FSM in other applications likemanaging states in the browser,modeling behavior, andbuilding chatbots。这篇文章的目的不是将FSM作为更好的国家管理解决方案,而不是其替代方案Reduxbut rather, to introduce a pattern for simplifying the architecture of a system and write robust code that is understandable, extendable, anddelete-able

什么是FSM?

A FSM is an architectural design pattern that allows us to model a large system as a collection of loosely coupled components. Each component in the system changes its behavior when the internal state changes.

An FSM consists of states and state transitions. It can be represented by a state machinediagram:

FSM representing a traffic light

Formally,

  • Statesrepresent the modes of the system and drive the output of the system. Light switches have two states: ON or OFF.Traffic lights contain three states: RED, YELLOW, or GREEN. There are a finite number of states in an FSM. Every FSM has an initial state, which is the state the system is in when it first initiates.
  • State transitions是根据当前状态和系统输入从当前状态到下一个状态的规则。

如果我们反应组件作为一个FSM模型,系统puts are provided externally by the user, the server, or callers of the component. These include events, such as a button click or having received a certain response from the server. The output of the system is the JSX (i.e., what is rendered) because that is what is ultimately returned from the component.

Inputs and outputs of a React component modeled as an FSM

Transitions out of a state must be mutually exclusive. That means a given input cannot cause transition into two states. This ensures the deterministic behavior of our system.

具有几个状态的组成部分

You probably already have had experience building React components that use component states likeisLoading和likeisDisabledto determine how to render. These are examples of stateful components that can be modeled with a simple FSM with binary states. Let’s apply what we learned about FSMs so far to model a simple stateful React component as an FSM in a concrete example.

Suppose we are building the< FriendStatus >component from the反应Hooks documentation。At any given time, this component displays one of the three messages "Loading...", "Online", and "Offline".

The组件状态在线确切确定要渲染的消息并初始化为null和updated to “online” or “offline” in theuseEffect

在线allows us to stores threeUI州: LOADING, OFFLINE, ONLINE.

We can rewrite< FriendStatus >根据UI Stateinstead.

国家过渡发生在handleStatusChange, which is triggered on every render because theuseEffect没有第二个论点。

ThesetUiState(nextUiState)is responsible for causing the transition from the current uiState to the next uiState based on the system input (i.e.,statusfrom ChatAPI).

Our system output is the message we want to render, which comes directly fromUiStateMessageMap。We can do this because our system can only ever be in one of three states. When the output of the system depends solely on the current state, we have a special type of FSM called aMoore machine。摩尔机器是更确定性的,但反应于输入变化较慢Mealy Machine。在Mealy机器中,输出取决于系统的当前状态和输入。

Whether you build a Moore machine or a Mealy machine really depends on the requirements of your system. We will see in the next part of the post how we can build a system that is a hybrid Moore and Mealy machine using an architecture that delegates the implementation of state-driven output to the child components.

A Component with Many States

对于只有两个或三个状态的简单组件,FSM可能会过大。实施FSM(例如,定义所有UI状态和过渡)的开销没有太大的收益。

对于由许多具有横切问题的子系统组成的复杂系统,FSM提供了巨大的好处。

Consider you have a system composed of multiple components in which one component needs to be disabled while another component is loading and the output of the two components determines the output of the third component.

The cross-component dependencies could result in tight coupling of components and make encapsulation difficult and the system difficult to test.

At188bet金宝搏官网, we’ve built such a component () that lets you specify a country and a query term (zip code or city name) to find a location anywhere in the world. This seems pretty straight-forward until you consider all the edge cases this component has to handle.

  • 组件与API接口,该API提供了最适合国家和查询搜索词的位置对象数组。基于此数组的长度,组件可以显示成功消息,各种错误消息以及另一个选择UI,以通过从最佳匹配列表中进行选择来消除搜索。
  • A bonus feature of this component is that if it’s used on a mobile device, there’s a button that lets you locate yourself automatically using your GPS location (latitude and longitude). The same API accepts the GPS location as input and responds with the locations array.
  • 该组件需要处理客户端输入验证错误,例如无效的邮政编码以及网络错误。
  • 此外,该组件可以使用预加载location, which needs to be reflected in the country dropdown, zip code/city name input, and success message immediately when the component first mounts.

If you see this design spec in a Jira ticket, it might seem a bit overwhelming. There’s a lot of logic that needs to be implemented. But having all the functional requirements up-front is actually a blessing in disguise — it enables us to think more holistically about the design of this component and pick an architecture that can effectively manage all of the complexity of this component.

Enumeration of All UI States

与设计师一起在跨职能团队中工作的一件好事是,在开始实施之前,您将获得所有模拟。这些模拟可用于设计体系结构。

Since this is an article about UI State modeling using FSM, you may have seen it coming. We are going to assign a UI State to each mock, which represents a snapshot of the component in action.

LocationSearch output in every state

These images depict the output of the system (what the user sees) in each state. Let’s take a look at what the outputs have in common in the different states. This will give us an idea on how to split up ourcomponent into sub-systems.

  • In the SUCCESS state all the error states, a feedback message is displayed. The text color depends on the state.
  • 在成功状态,歧义状态和所有错误状态中,输入旁边显示一个图标(Checkmark和感叹号)。
  • In all the states except LOADING, the country dropdown, query input, and geo-location search button (labeled “USE CURRENT LOCATION”) are enabled and accepting input from the user.

通过将特定国家输出的实施委派给这些组件,我们能够转换into a system of loosely-coupled components that are coordinated via the UI State. We are encapsulating the FSM in, in the sense that the parent components ofdo not know anything about the implementation of the UI state in this component.

Now we’ve defined the states and the output of each state, we are going to specify the state transitions.

State Transitions

Similar to what we did in< FriendStatus >earlier, we are going to instantiateuiStateas a component state of和mutateuiStatein auseEffect。下一个评估uiState在一个helper函数吗谋求。The advantage of making the next state evaluation explicit in谋求是我们将能够分开计算的关注when从关注什么to render.

Recall FSM can be represented as a directed graph and state transitions are the edges between the nodes. This is what our FSM should look like.

FSM representing LocationSearch

待处理状态是当first mounts. As the component can mount with a preloaded location, there’s an arrow going directly from the PENDING state to the SUCCESS state. We can also reach the SUCCESS state by providing a country+query to the server or a GPS location to the server.

The ERROR_NOT_ZIP_CODE state is reached via client-side input validation and does not depend on the result of the server request. On the other hand, all the other error states would be reached only after making a request to the server.

Conclusion

In this post, we learned what an FSM is and how to model a complex React component as a system of loosely coupled sub-systems coordinated via the UI State. The FSM allowed us to build a robust system that is understandable, extendable, anddelete-able

我们已经看到了一些使用React原语的示例,例如useStateuseEffect。There exist JavaScript libraries likexStateMachina.jswhich provide a more opinionated framework for creating, interpreting, and executing finite state machines and statecharts.

While the FSM is a powerful design pattern, it is not always the right thing to use for a project. Architectural design is most effective when there is a clear product vision and requirements upfront for how the full user experience should be. Pre-mature optimization could lead to worse code maintainability. Fortunately, the location search experience project I worked on at188bet金宝搏官网拥有良好的产品计划和明确的设计目标,这使得解决问题并建立了强大的技术解决方案成为可能。

188bet金宝搏官网OkCupid技术博客

阅读Okcupid工程团队的故事,每天连接数188bet金宝搏官网百万人

小杨

写的

New York-based Software engineer. Currently at OkCupid. My website and blog: https://xiaoyunyang.github.io/

188bet金宝搏官网OkCupid技术博客

OkCupid’s Engineering team is responsible for matching millions of people daily. Read their stories on the OkCupid tech blog

小杨

写的

New York-based Software engineer. Currently at OkCupid. My website and blog: https://xiaoyunyang.github.io/

188bet金宝搏官网OkCupid技术博客

OkCupid’s Engineering team is responsible for matching millions of people daily. Read their stories on the OkCupid tech blog