Here at Industrial Logic, we have been working with React and React-Native extensively in the past few years. We have created a few tools that we found to accelerate our development, built through continuous refactoring of our code.
This small series will introduce these tools with a deep dive into how they work and why we think they are awesome for React and React-Native projects.
This article is about our package use-react-ref: a thin wrapper around React’s useRef and useState.
useRef and useState are the primary ways of controlling state in a React app. As we continued to develop with useState
, we found we wanted a different return value of a tuple.
const [value, setValue] = useState(0);
When passing these values around, we found it annoying that we had to pass two props instead of one, so we found ourselves writing the following code in a few places:
const [counterValue, setCounter] = useState(0);
const counterState: ReactState<number> = {
value: counterValue,
setCounter,
};
return <MyComponent state={counterState} />;
So we thought: why not create our custom hook that does that automatically?
const counterState: ReactState<number> = useReactState(0);
return <MyComponent state={counterState} />;
This simplified the signatures of many of our components. However, the main benefit we would soon discover is that this would allow a place to encapsulate the data in useState and useRef.
In building custom filters for a Search Screen, we wrote multiple lines like this
const typeFilterState = useReactState([]);
const contentFilterState = useReactState(ContentTypes.All);
...
const otherFilterState = useReactState('*');
function clearFilters() {
typeFilterState.setValue([]);
contentFilterState.setValue(ContentTypes.All);
...
otherFilterState.setValue('*');
}
In the clearFilters function, we were clearing the selected filters for each state back to their initialValue
. With this initial implementation, we duplicated the initialValue
for each search filter across instantiation and clearing. As we added more search filters, we would need to duplicate even more.
The initial refactoring could be to introduce a variable for each state’s initialValue
. However, we already have access to initialValue
in useReactState
! So instead, we added a reset method that sets the state back to the initialValue
.
const typeFilterState = useReactState([]);
const contentFilterState = useReactState(ContentTypes.All);
...
const otherFilterState = useReactState('*');
function clearFilters() {
typeFilterState.reset();
contentFilterState.reset();
....
otherFilterState.reset();
}
Not only did this new reset method remove the duplication, but it also improved the intention-revealing aspect of the code. When we clear the filters, we reset them back to their initial value. Win-win!
Once we added our first extension of useState
and useRef
, we noticed another code smell in our application. There were multiple instances where we checked if the state or ref was equal to the initialValue set.
function Header(props: { nameEnteredState: ReactState<string> }) {
function text() {
if (props.namedEnteredState === '') return 'Enter Name';
return `Hello ${props.namedEnteredState}`;
}
return <p>{text()}</p>;
}
We then had the idea to add .isInitialValue()
to useReactState
and useReactRef
. Since we already had access to the closure containing the initialValue, this was a simple improvement.
function Header(props: { nameEnteredState: ReactState<string> }) {
function text() {
if (props.namedEnteredState.isInitialValue()) return 'Enter Name';
return `Hello ${props.namedEnteredState}`;
}
return <p>{text()}</p>;
}
Again, this simple change removed duplication and improved the readability of the code.
These are a few examples of how we continued to extend useReactRef and useReactState to make React and React Native development easier. We found it both improved the intention-revealing aspect and removed duplication in the code.
use-react-ref is a simple drop-and-replace. Here is one first small step to get you started.
function Modal() {
const [isVisible, setIsVisible] = useState(false);
if (!isVisible) return <></>;
return <p>Modal Text</p>;
}
import { useReactState } from 'use-react-ref';
function Modal() {
const isVisibleState = useReactState(false);
const isVisible = isVisibleState.value;
const setIsVisible = isVisibleState.setValue;
if (!isVisible) return <></>;
return <p>Modal Text</p>;
}
After this, you could choose to further refactor and clean the code up.
Experiment with use-react-ref in your React and React-Native based applications and let us know what you think!