You may notice that we're deriving two values from the same property. This works fine at first, but an issue arises with how we're doing things when we realize that our formatDate and formatReadableDate methods are only running once during the initial render.
Because of this, if we pass in an updated inputDate to the FileDate component, the values of formatDate and formatReadableDate will become out of sync from the parent's passed inputDate.
React
Angular
Vue
const File = ({ href, fileName, isSelected, onSelected, isFolder }) => { const [inputDate, setInputDate] = useState(new Date()); useEffect(() => { // Check if it's a new day every 10 minutes const timeout = setTimeout( () => { const newDate = new Date(); if (inputDate.getDate() === newDate.getDate()) return; setInputDate(newDate); }, 10 * 60 * 1000, ); return () => clearTimeout(timeout); }, [inputDate]); // JSX shortened for focus // This may not show the most up-to-date `formatDate` or `formatReadableDate` return <FileDate inputDate={inputDate} />;};
An embedded webpage:React Refreshing File Date - StackBlitz
While the above File component updates inputDate correctly, our FileDate component is never listening for the changed input value and, as such, never recomputed the formatDate or formatReadableDate value to display to the user.
How can we fix this?
Method 1: Prop Listening
The first - and arguably easiest to mentally model - method to solve this disparity between prop value and display value is to simply listen for when a property's value has been updated and re-calculate the display value.
Vue's watch logic allows you to track a given property or state value changes based on its key.
Here, we're watching the inputDate props key and, when changed, updating dateStr and labelText based off of the new property value.
While this method works, it tends to introduce duplicate developmental logic. For example, notice how we have to repeat the declaration of the dateStr and labelText values twice: Once when they're initially defined and again inside the property listener.
Luckily for us, there's an easy solution for this problem called "computed values."
Method 2: Computed Values
Our previous method of deriving a value from a property follows two steps:
Set an initial value
Update and recompute the value when its base changes
However, what if we could instead simplify this idea to a single step:
Run a function over a value and live update as it changes.
useMemo is a method for computing values based on an input or series of inputs. This works because it does the computation and regenerates the calculation whenever the second argument array's values have changed in a render.
Like useEffect, this array's values' changes are only tracked when the component is rendered. Unlike useEffect, however, there's no option to remove the second array argument entirely.
Instead, if you want to recalculate the logic in every render, you'd remove the useMemo entirely. So, for simple computations, you can take this code:
While it's technically possible to use this trick to never use useMemo, your application's performance will suffer drastically. That said, it's a bit of a science to know when and where to use useMemo. We'll touch on this more in our third book titled "Internals".
Angular is able to derive state from a signal using a function to transform the signal being read and a computed method:
Instead of using ref to construct a set of variables and then re-initializing the values once we watch a prop, we can simply tell Vue to do that same process for us using computed props.
Vue is able to ✨ magically ✨ detect what data inside the computed function is dynamic, just like watchEffect. When this dynamic data is changed, it will automatically re-initialize the variable it's assigned to with a new value returned from the inner function.
These computed props are then accessible in the same way a data property is, both from the template and from Vue's <script> alike.
Non-Prop Derived Values
While we've primarily used component inputs to demonstrate derived values today, both of the methods we've used thus far work for the internal component state and inputs.
Let's say that we have a piece of state called number in our component and want to display the doubled value of this property without passing this state to a new component:
In this component, we can see two numbers — one doubling the value of the other. We then have a button that allows us to increment the first number, and therefore, using a derived value, the second number also updates.
Challenge
While building through our continued file hosting application, let's think through how our Size can be calculated to be displayed in the UI like so:
File sizes are usually measured in how many bytes it takes to store the file. However, this isn't exactly useful information past a certain size. Let's instead use the following JavaScript to figure out how large a file size is, given the number of bytes: