Using React, Recoil atom, selector, and hooks to re-render Large Lists optimally in React.
In this article, we are going to look at how we can use Recoil’s atom, selector, and hooks to display and update a list of employees. The employees are either manager or non-manager. We will update the isManager
field in the application state and see how only the selected column from the row is re-rendered. We will also derive a count
element from the state and see how its UI is re-renderd automatically when the application state is updated.
Recoil documentation for atom states below.
An atom represents state in Recoil. The atom() function returns a writeable RecoilState object.
Note: all atom values are frozen.
Selectors represent a function, or derived state in Recoil. You can think of them as a “pure function” without side-effects that always returns the same value for a given set of dependency values.
As per Recoil, we need to wrap our application into RecoilRoot tag so that we can use Recoil state.
Components that use recoil state need RecoilRoot to appear somewhere in the parent tree
function App() {
return (
<RecoilRoot>
<div className="App">
<EmployeeList />
<ManagerCount />
</div>
</RecoilRoot>
);
}
Our data contains four record where two employees are managers and two are not. If the isManager
field is true then the employee is manager.
[
{
"id": 1,
"firstName": "E1 Anvi",
"lastName": "E1 A",
"isManager": true
},
{
"id": 2,
"firstName": "E2 Soumya",
"lastName": "E2 A",
"isManager": true
},
{
"id": 3,
"firstName": "E3 Anvi",
"lastName": "E3 A",
"isManager": false
},
{
"id": 4,
"firstName": "E4 Soumya",
"lastName": "E4 A",
"isManager": false
}
]
Now we will create our application state using atom
. The data object contains data from above screen shot.
const employeesDataState = atom({
key: 'employeesDataState',
default: data,
});
We will be using selector
to derive the data from the employeeDataState
. Also, selector
has get
and set
methods.
- The
get
method will help us derive a piece of data from the application state. Here we will be deriving the complete state. - In the
set
method we will be getting the updated record for an employee as parameter. We will be updating the employee list with thisupdatedEmployee
record.
If only a get
function is provided, the selector is read-only and returns a RecoilValueReadOnly
object. If a set
is also provided, it returns a writeable RecoilState
object.
const employeesState = selector({
key: 'employeesState',
get: ({ get }) => {
return get(employeesDataState)
},
set: ({ set }, updatedEmployee) => {
set(employeesDataState, prevEmployees => {
const pEmployees = _.clone(prevEmployees),
employeeIndx = pEmployees.findIndex(emp => emp.id === updatedEmployee.id)
pEmployees.splice(employeeIndx, 1, updatedEmployee)
return [...pEmployees]
})
},
})
Now in our EmployeeList
we just need to glue this selector data so that when the state is updated the list is also updated. For this we will be using the hook, useRecoilState
, from the Recoil library.
useRecoilState
returns a tuple where the first element is the value of state and the second element is a setter function that will update the value of the given state when called.
When the setEmployee
is called it will make a call to the set
method of the selector that will update the employeeDataState
data. It will in return trigger the employeeState
selector. And as this selector is being used in EmployeeList
the list will be updated.
EmployeeList(setEmployee) -> employeeState(set method) -> employeeDataState -> employeeState(get method) -> EmployeeList
function EmployeeList() {
const [employees, setEmployees] = useRecoilState(employeesState);
return (
<div className="employees">
<h3>Employee list</h3>
<table>
<thead>
<tr>
{['FirstName', 'LastName', 'Manager'].map(
header => <th key={header}>{header}</th>
)}
</tr>
</thead>
<tbody>
{employees.map(employee =>
<Employee
key={employee.id}
employee={employee}
onUpdate={setEmployees}
/>
)}
</tbody>
</table>
<br />
</div>
);
}
Our Employee
component is a memoized component which receives employee
and setEmployee
function as parameter.
In our Employee
component we will be displaying a table with employee firstName
, lastName
and if employee is a manager or not. The last column will contain radio items which we can toggle. When the radio items are clicked, a call is made to the setEmployee
method which will trigger the update in our application state.
const Employee = React.memo(EmployeeComp);
function EmployeeComp({ employee, onUpdate }) {
const updateHandler = e => onUpdate({
...employee, isManager: e.target.value
})
return (
<tr>
<td>{employee.firstName}</td>
<td>{employee.lastName}</td>
<td>
<RadioGroup defaultValue={employee.isManager}
onChange={updateHandler} buttonStyle="solid">
<RadioButton value={true}>Yes</RadioButton>
<RadioButton value={false}>No</RadioButton>
</RadioGroup>
</td>
</tr>
);
}
We also have a component ManagerCount
that will just display the count of managers in the list. First we will create a selector which will return the manager count from the application state. In the managerSelector
below, we just have a get a method which filter’s and returns the employees that are managers.
const managerSelector = selector({
key: 'managerCountSelector',
get: ({ get }) => get(employeesState)
.filter(employee => employee.isManager),
});
Now in our ManagerCount
component we just want to read the value from the selector so we will use the hook useRecoilValue
.
useRecoilValue
returns the value of the given Recoil state. This hook will implicitly subscribe the component to the given state.
function ManagerCount() {
const managers = useRecoilValue(managerSelector);
return <>Manager Count: {managers.length}</>;
}
Great, our application is ready. Let’s see it in action. So here we have two employees as managers and the remaining two are not managers.
Now, let’s toggle the second employee from manager to non-manager.
Here we observe a couple of things:
- The complete list does not re-render. Only the second row is re-rendered. That’s great news. So even if we have a large list the performance of the application will be fast.
- When we update the employee record, the
ManagerCount
component also updates seamlessly and there is change in count from two to one. Here you can see how we are deriving only the manager records from the application state usingselector
and how Recoil automatically re-renders theManagerCount
component when the actual application state is changed.
Conclusion
It seems that Recoil is a great library that compliments React perfectly when it comes for application’s state management. The ease of using it and it’s effortless integration with React makes it a great choice and definite contender when it comes to application’s state management.