This post is originally from bradcypert.com, but has been reformatted for your viewing pleasure on Dev.to!
React hooks have really changed the game for me when it comes to building react components. However, I've found that writing autosaving functionality is a little less obvious via hooks. Thankfully, it is still possible (and arguably a lot cleaner) when using hooks.
We can accomplish autosaving functionality through use of useEffect
. You'll also need a way to post the data to your server. In my case, I'm using Apollo's useMutation hook as well. This allows me to post a graphql mutation from a hook-like interface.
The useEffect
hook
The useEffect
hook effectively replaces componentDidMount
, componentWillUpdate
, and componentWillUnmount
. Here's how I remember the API for useEffect
:
useEffect(() => {
doWhateverIsHereOnMountandUpdate();
return () => {
doWhateverIsHereOnWillUnmount();
}
}, [skipUntilThisStateOrPropHaschanged])
It's worth mentioning that the skipUntilThisStateOrPropHasChanged
is optional, and leaving it out will cause it to process the hook on every render.
Implementing Autosave
Now that we understand our hook, the autosave functionality becomes fairly trivial. We'll use a couple of state hooks as well to help us manage the text that a user types in as well as the last value we saved (we can skip network requests if they're the same).
const [lastText, setLastText] = React.useState('');
const [text, setText] = React.useState('');
You'll see how we use lastText
in our useEffect
hook below, but for now, you just need to know that text
represents the state of what the user has typed in. If you'd like more information on how this works, React's documentation for Controlled Components is a great place to start.
Now, we need a function to trigger to persist our data to the server. In my case, I'll use an Apollo mutation since the server API processes graphql.
const [updateContent] = useMutation(UPDATE_CHAPTER_CONTENT.MUTATION);
Finally, we can write our useEffect
hook:
const AUTOSAVE_INTERVAL = 3000;
React.useEffect(() => {
const timer = setTimeout(() => {
if (lastText != text) {
updateContent({ variables: { content: text, id: chapterId } });
setLastText(text);
}
}, AUTOSAVE_INTERVAL);
return () => clearTimeout(timer);
}, [text]);
We're doing a couple of neat things here. First, we're setting up our useEffect
hook. It creates a timer via setTimeout
, and when that hook unmounts, it removes that timer. That's the "meat-and-potatoes" behind it. You'll notice that our setTimeout function checks to see if the text has changed before posting our data, and then sets the last text if it has changed.
We're also only triggering this useEffect
when text
has changed (as indicated by [text]
as the second parameter. We probably could clean this up a bit by removing the if
in the timeout function body, and updating the useEffect
dependency array to be [text != lastText]
.
And that should do it! Hopefully, this helps if you're trying to build autosave functionality into React project.
If you'd like to learn more about React, you can find my other post on Facebook's awesome framework here.
Top comments (5)
You should probably store the "lastText" value in a Ref instead since doing useState re-renders the component. If you update lastText and text at the same time you will get 2 re-renders every time you really just wanted one.
Could you explain the theory behind using a Ref in this instance?
Doesn't React batch updates?
Since
lastText
is never used in render/UI, wouldn’t using a ref instead of state for storing prev state save extra renders?it looks like
lastText != text
is doing the same thing as[text]
, so everythinglastText
could probably be removed