Since early in my coding career as a passionate entrepreneur I’ve wanted to construct a web application in the form of a basic membership site, an application that solves a matching problem, ie sellers to buyers, owners to guests, passengers to drivers. When I discovered the concept for Embryo Match I felt inspired to build this application that solves the issue of matching embryo donors with recipients through a basic membership site model. In order not to bury myself in complicated features I decided that the MVP would focus on a three specific things, primarily the ability to sign up and create a donor or recipient profile, the ability to search and filter for matches, and basic messaging capability. The application would be include a React/Redux front end and a rails api backend. Here are some of the challenges and lesson I encountered while constructing this application.
Idiosyncrasies of JSX
is quick review of JSX deviations and syntactic requirements to remember:
- • All JSX expressions must have only ONE outermost element.
- • Multi-line JSX expressions must be wrapped in parentheses
- • JSX uses the keyword className as opposed to class in JS
- • JSX does not support if/else statements
- • Conditional are expressed in one of three ways: using ternary operators, an if statement outside of the JSX expression, of the && operator to create a boolean expression that will execute or return noting at all.
- • Event listener names are always camelCased
- • Event listener values are a function that can be declared inline or as variable with an optional event parameter.
- • Key attributes are used to uniquely identify individual elements, ie. as in lists of similar elements that need to be identified by the DOM for updating
Class Based Components vs Function Based Components
Advantage of Class Based Components
Although function components are light weight, simpler and require less boilerplate code, we do loose the advantages of having access the to React Component library that class based components inherit from. Additionally React references aka 'refs' can not be accessed from function components, and the timing of a React hooks execution maybe slightly different than what we expecting from a lifecycle methods. And finally, it's important to note that class based components use 'strict-mode' which may help us identify silent errors.
Converting a Class Based Component to Function Component
I believe one of the best ways to visualize and understand the differences between class-based components and function components is convert one to the other. Let's walk through the steps required to do that conversion.
To convert a class based component to a function component:
- 1. Change the class declaration to a function declaration
- 2. Remove the render keyword from the final code block, keep the return statement and ensure that it is the last method
- 3. Change all methods to arrow functions
- 4. Delete all instances of the THIS keyword
- 5. Remove the constructor method and replace it with a useState() hook
- 6. Remove any event handler bindings
- 7. Replace lifecycle methods with useEffect() hooks
Once you have completed the steps above your component will have transformed from this class based component:
To this function component:
LifeCycle Methods vs React Hooks
When function components were originally introduced they did not work with lifecycle methods such as componentDidMount(), or componentDidUpdate() and there was no way to hook our component functions into the lifecycle of a component. React hooks were introduced in React 16.8 to give developers the same functionality and ease of that class-based components provide.
What are React Hooks?
As state above and in the React Documentation: "hooks are functions that let you hook into React and lifecycle features from a function component". Because class-based components are dependant on life cycle methods with stateful logic they can be hard to break up into separation of concerns. Now with React hooks we have the ability to create smaller stateful components broken down by separation of concerns. The two most essential and commonly used hooks are useState and useEffect.
The useState function is how we add state to our function components. To use this function we import it from the React library without import statement. Then a state variable and set and initial value. When we call the useState function it will return the current state value and the function to update it.
The useEffect function was created to allow sideEffects within a component. Prior to the creation of useEffect, React developers relied on lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount(). componentDidMount() was called as soon as the component was mounted and this is where React developers would place initiate their API calls. componentDidUpdate() was called when any state changes where made and used to update the DOM to reflect these changes. componentWillUnmount(), another commonly used lifecycle method in class based components is called right before a component is destroyed and the DOM is cleared or reset, it is used to remove any event listeners, cancel subscriptions and clean up any tasks that may need to be resolved before a component is destroyed. Now lets see how we accomplish the same results by using useEffect().
To mimick the effects of the lifecycle method componentDidMount() in our function component and execute useEffect() only after our component has mounted, we make our useEffect() function call with an empty array for the second argument. To use useEffect() similarly to componentDidUpdate() we chain a useEffect() call with an array, and a second useEffect() call with the dependency you want to use as a field hook. To make use of useEffect() as componentWillUnmount(), return a useEffect() with an empty dependency array. Don’t forget, when using useEffect() to add an empty function call for cleanup. This will prevent setting state on unmounted components, and clear stale state values when the user changes.
Differences Between useEffect() and Lifecycle Methods
It is important to understand when transitioning from lifecycle methods to the useEffect hook that the conversion is not absolute. There are small variances to be aware of when using one to replace the other. Unlike componentDidMount(), useEffect() executes after the DOM has been rendered which could lead to visible content flicker if you need to read from the DOM and synchronously set state to create the new UI. The work around in this case is to use the layoutEffect() function which is executes with the same timing and the previous lifecycle method componentDidMount().
Another potential oversight, to be aware of, is the fact that useEffect() will 'capture' values in the effect function through the use of closure, while the ComponentDidMount does not create closure and will just read the current value. If your effect depends on a value, make sure that it is included in your useEffect() dependency array to keep these captured values current and accurate. Don't forget to include a clean up function to prevent stale values, or setting set on unmounted components.
Debugging Nested JSON objects
One of the most frustrating challenges I encountered was an issue with the JSON objects I was returning from my API. No matter what I did I could not seem to access nested object values. It was pretty clear to me what syntax combination of array indexes and object keys should have returned that values I was trying to retrieve but I still wasn't able to access them. After a few frustrating days of returning to the problem I discovered that the issue was I had double encoded nested objects within my api JSON objects, but using a combination of the to_json method in my controllers and active_model serializers. I was able to resolve this by removing some unnecessary serializers, and parsing nested objects a second time.
I found very little documentation regarding active model serializers, and observed Fast JSON seems to have become a popular replacement.
Application architecture, and design patterns is another area I would like to explore further in React/Redux. While I'm very comfortable with MVC architecture for the back end api, and I used traditional React design patterns such and parent container components passing props to child presentational components, I feel like the end result was a multitude of components that could possible be reduced to simplify and condense the finished product.
As with the construction of every new application I build from scratch there were to many lessons to include here but I’ve included some of the notable concepts.atxrenegade/Harleigh Abel