Building a Data Table with Optimistic Updates: A Modern React Guide

Enyel Sequeira
4 min readNov 3, 2024

--

Introduction

Today, I’ll share how I built a polished food database management system using modern React patterns. We’ll focus on creating a responsive data table with seamless optimistic updates, combining the power of TanStack Query (formerly React Query) with Mantine’s component library.

Project Overview

Requirements

  • Display food items in a data table
  • Add new items with immediate feedback
  • Handle loading and error states gracefully
  • Provide smooth optimistic updates

Tech Stack

  • TanStack Query: Server state management
  • Mantine UI: Component library and form management
  • Mantine React Table: Advanced table functionality
  • Wretch: Clean API calls
  • TypeScript: Type safety

Implementation Guide

1. Setting Up the Foundation

First, let’s define our types and API configuration:

// Types
export type GetAllFoods = {
id: number;
name: string;
category: string;
};
export type CreateNewFoodType = Pick<
GetAllFoods,
| 'name'
| 'category'
>;
// API Configuration
export const API = wretch('<http://localhost:9999>').options({
credentials: 'include',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
// TANSTACK QUERY
export const getFoodOptions = () => {
return queryOptions({
queryKey: ['all-foods'],
queryFn: async () => {
try {
return await API.get('/foods')
.unauthorized(() => {
console.log('Unauthorized');
})
.json<Array<GetAllFoods>>();
} catch (e) {
console.log({ e });
throw e;
}
},
});
};
export const useGetAllFoods = () => {
return useQuery({
...getFoodOptions(),
});
};

2. Building the Data Table

The table component using Mantine React Table:

const FoodsView = () => {
const { data } = useGetAllFoods();
  const columns = useMemo<MRT_ColumnDef<GetAllFoods>[]>(
() => [
{
accessorKey: 'id',
header: 'ID',
},
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'category',
header: 'Category',
},
// ... other columns
],
[]
);
const table = useMantineReactTable({
columns,
data: data ?? [],
// Optimistic update animation
mantineTableBodyCellProps: ({ row }) => ({
style: row.original.id < 0 ? {
animation: 'shimmer-and-pulse 2s infinite',
background: `linear-gradient(
110deg,
transparent 33%,
rgba(83, 109, 254, 0.2) 50%,
transparent 67%
)`,
backgroundSize: '200% 100%',
position: 'relative',
} : undefined,
}),
});
return <MantineReactTable table={table} />;
};

3. Creating the Form

A form component for adding new foods:

const CreateNewFood = () => {
const { mutate } = useCreateNewFood();
  const formInputs = [
{ name: 'name', type: 'text' },
{ name: 'category', type: 'text' },
];
const form = useForm<CreateNewFoodType>({
initialValues: {
name: '',
category: '',
// ... other fields
},
});
return (
<Box mt="md">
<form onSubmit={form.onSubmit((data) => mutate(data))}>
<Flex direction="column" gap="xs">
{formInputs.map((input) => (
<TextInput
key={input.name}
{...form.getInputProps(input.name)}
label={input.name}
tt="uppercase"
type={input.type}
/>
))}
<Button type="submit" mt="md">
Create New
</Button>
</Flex>
</form>
</Box>
);
};

4. Implementing Optimistic Updates

The heart of our implementation — TanStack Query mutation with optimistic updates:

export const useCreateNewFood = () => {
const queryClient = useQueryClient();
  return useMutation({
mutationKey: ['create-new-food'],
mutationFn: async (data: CreateNewFoodType) => {
await new Promise(resolve => setTimeout(resolve, 3000)); // Demo delay
return API.url('/foods').post(data).json<GetAllFoods>();
},
onMutate: async (newFood) => {
// Cancel in-flight queries
await queryClient.cancelQueries({ queryKey: ['all-foods'] });
// Snapshot current state
const previousFoods = queryClient.getQueryData<GetAllFoods[]>(['all-foods']);
// Create optimistic entry
const optimisticFood: GetAllFoods = {
id: -Math.random(),
...newFood,
verified: false,
createdBy: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
// Update cache optimistically
queryClient.setQueryData(['all-foods'], (old) =>
old ? [...old, optimisticFood] : [optimisticFood]
);
return { previousFoods };
},
onError: (err, _, context) => {
// Rollback on error
if (context?.previousFoods) {
queryClient.setQueryData(['all-foods'], context.previousFoods);
}
},
onSettled: () => {
// Refetch to ensure consistency
queryClient.invalidateQueries({ queryKey: ['all-foods'] });
},
});
};

5. Animation Styles

The animation that brings our optimistic updates to life:

@keyframes shimmer-and-pulse {
0% {
background-position: 200% 0;
transform: scale(1);
box-shadow: 0 0 0 0 rgba(83, 109, 254, 0.2);
}
50% {
background-position: -200% 0;
transform: scale(1.02);
box-shadow: 0 0 0 10px rgba(83, 109, 254, 0);
}
100% {
background-position: 200% 0;
transform: scale(1);
box-shadow: 0 0 0 0 rgba(83, 109, 254, 0);
}
}

Best Practices

Optimistic Updates

  • Immediately update UI for better UX
  • Handle error cases with rollbacks
  • Maintain data consistency with proper invalidation

Type Safety

  • Use TypeScript for better maintainability
  • Define clear interfaces for data structures
  • Leverage type inference where possible

Performance

  • Cancel in-flight queries during updates
  • Use proper query invalidation
  • Implement efficient form state management

User Experience

  • Provide immediate feedback
  • Show loading states
  • Handle errors gracefully

Future Enhancements

Consider these improvements for your implementation:

  • Undo/redo functionality
  • Form validation rules
  • Error boundary implementation

Results

final result

Conclusion

This implementation demonstrates how to create a robust data management system using modern React patterns. The combination of TanStack Query, Mantine UI, and thoughtful optimistic updates creates a smooth and professional user experience.

Remember to:

  • Keep your components focused and maintainable
  • Handle all possible states (loading, error, success)
  • Use TypeScript for better code quality
  • Consider user experience in your implementation

What challenges have you faced implementing optimistic updates in your React applications? Share your experiences in the comments below.

--

--

Enyel Sequeira
Enyel Sequeira

Written by Enyel Sequeira

Just a friendly developer who enjoys coding things from scratch

No responses yet