Skip to main content

useSettingsLoader

Documentation: useSettingsLoader Hook

The useSettingsLoader hook is a utility for fetching and updating organizational and resource-level configurations from an Apollo Client query in a Remix application. It provides an easy way to manage configurations with the ability to update them and automatically refetch them when needed.


Hook Signature

export const useSettingsLoader = <K = IConfigurationsFlattenedKeys, R = IPreferences>(
settingsVariable: ISettingsLoaderVariable<K>,
): IResponse<K>

Parameters

  • settingsVariable: ISettingsLoaderVariable<K>:
    • configKey: The key used to identify the configuration to fetch.
    • skip (optional): A flag to skip querying or fetching the configuration when set to true.

Return Value

The hook returns an object of type IResponse<K> with the following properties:

  • data: The current configuration value (string | number | boolean) corresponding to the configKey. It could come from either the initial data provided by the Remix loader or the refetched data from Apollo Client.
  • loading: A boolean indicating whether the hook is in a loading state.
  • error: Any error that occurred during the fetching or updating of configurations.
  • preferencesInput: Input preferences related to the configuration, retrieved from the Remix loader's context.
  • updateConfiguration: A function that allows updating the configuration value for a given key. After successfully updating, it refetches the configuration from the server.

Prerequisites

  1. Apollo Client Setup: Your project should be configured with Apollo Client to handle GraphQL queries and mutations.
  2. Route Configuration: In compute.ts of the specific route should have configurations with optional resourceParams. Please check the docs under the remix section for more information.

useSettingsLoader Hook Usage Guide

The useSettingsLoader hook is a utility for managing configuration settings in a React application, particularly in scenarios where initial configurations are loaded through a data loader (like Remix's useLoaderData), and you want to handle updates or refetches using Apollo Client.

Here’s a step-by-step guide on how to use the useSettingsLoader hook effectively in your application.


Prerequisites

  1. Apollo Client Setup: Your project should be configured with Apollo Client to handle GraphQL queries and mutations.
  2. Remix Data Loader: You'll use Remix's useLoaderData to fetch the initial configurations from the server during route rendering.
  3. React Environment: Ensure that your application is wrapped in an ApolloProvider and MemoryRouter (for testing), or another appropriate router setup during runtime.

Basic Usage

1. Loading Initial Configuration

The useSettingsLoader hook is designed to first retrieve configuration data from Remix’s useLoaderData. You need to pass a configKey to identify which configuration value you are targeting.

Here’s how you can use it in a basic component:

import { useSettingsLoader } from './hooks/useSettingsLoader';

const NotificationSettings = () => {
const { data, loading, error } = useSettingsLoader({
configKey: 'account.notification.primaryEmail',
});

if (loading) {
return <div>Loading...</div>;
}

if (error) {
return <div>Error: {error.message}</div>;
}

return <div>Notification Email: {data}</div>;
};

export default NotificationSettings;
  • configKey: This is the key used to fetch a specific configuration. In the above example, we are retrieving the account.notification.primaryEmail value.
  • data: The configuration value returned by the hook. It will either come from the Remix loader (initially) or be refetched via Apollo Client.
  • loading: A boolean indicating whether the configuration is currently being fetched.
  • error: If any errors occur during the data fetching process, they will be captured here.

2. Updating the Configuration

The useSettingsLoader hook also provides an updateConfiguration function, which you can use to update the configuration and trigger a refetch of the data.

Here’s an example:

const NotificationSettings = () => {
const { data, updateConfiguration } = useSettingsLoader({
configKey: 'account.notification.primaryEmail',
});

const handleUpdate = async () => {
await updateConfiguration({
updateKey: 'account.notification.primaryEmail',
value: 'new-email@example.com',
});
};

return (
<div>
<p>Notification Email: {data}</p>
<button onClick={handleUpdate}>Update Email</button>
</div>
);
};
  • updateConfiguration: This function accepts an object with parameters:
    • updateKey: The key for the configuration to update (optional if it's the same as the configKey).
    • value: The new value to set for the configuration.
    • updateOverrides: Any additional overrides for the update.
    • target: Where the configuration update is targeted (e.g., account, organization, etc.).

Once the update is successful, the hook will refetch the configuration data, and the data will reflect the updated value.

3. Skipping Apollo Queries

In some scenarios, you might want to skip the Apollo query altogether (e.g., if the configuration data is already up-to-date or doesn’t need fetching at that moment). You can use the skip parameter in the hook’s input.

const NotificationSettings = () => {
const { data } = useSettingsLoader({
configKey: 'account.notification.primaryEmail',
skip: true,
});

return <div>{data ? `Notification Email: ${data}` : 'No email set'}</div>;
};

Error Handling

Always ensure you handle the error state returned by the hook. Errors could occur due to network issues, GraphQL errors, or even loader issues.

const NotificationSettings = () => {
const { data, loading, error } = useSettingsLoader({
configKey: 'account.notification.primaryEmail',
});

if (loading) {
return <div>Loading settings...</div>;
}

if (error) {
return <div>Error loading settings: {error.message}</div>;
}

return <div>{`Notification Email: ${data}`}</div>;
};

Testing Components Using useSettingsLoader

When writing tests for components that use useSettingsLoader, you will need to mock both useLoaderData (for initial data) and the Apollo Client (for updates or refetches). Additionally, you should wrap your test in a MemoryRouter and ApolloProvider.

Here’s a basic test setup:

import { render, screen, waitFor, act } from '@testing-library/react';
import { createRemixStub, json } from '@remix-run/testing'; // Remix testing utilities
import MyComponent from './MyComponent'; // The component using useSettingsLoader
import { DefaultConfiguration } from './fixtures/default-configuration';

const preferencesInput = {
editableSettingsInput: 'some-input',
};

describe('useSettingsLoader Hook Test with RemixStub', () => {
it('should load initial configuration from loader', async () => {
// Create a RemixStub for the test
const RemixStub = createRemixStub([
{
path: '/',
Component: MyComponent,
loader() {
return json({
configurations: DefaultConfiguration.contents,
dataContext: { preferencesInput: preferencesInput },
});
},
},
]);

// Render the component within the RemixStub
await act(async () => {
render(<RemixStub />);
});

// Wait for the component to load and check the configuration output
await waitFor(() => {
expect(screen.getByTestId('config-output').textContent).toContain(
JSON.stringify(DefaultConfiguration.contents.organization.teams.visibility)
);
});

// Simulate updating the configuration
const updateButton = screen.getByRole('button', { name: /Update Email/i });
await act(async () => {
updateButton.click();
});

// Check if the new value is rendered after the update
await waitFor(() => {
expect(screen.getByTestId('config-output').textContent).toContain('new-email@example.com');
});
});
});


Best Practices

  1. Efficient Query Usage: Use the skip option when you don’t need to fetch the configuration data, such as in scenarios where the data is already available or doesn't need to be refetched.

  2. Optimize for Performance: Ensure you leverage the cache-first policy of Apollo Client to avoid unnecessary network requests. Use the skip parameter wisely when refetching is unnecessary.

  3. Error Handling: Always handle errors gracefully. Whether during the initial fetch or a refetch, providing fallback UI for error states improves the user experience.


Conclusion

The useSettingsLoader hook offers an efficient way to manage configuration settings in your React applications, particularly when using Remix and Apollo Client. It handles both initial data loading via useLoaderData and updates via Apollo Client’s GraphQL queries. Whether you're fetching configurations initially or updating them, useSettingsLoader provides the flexibility and scalability you need to manage configuration settings efficiently in any React environment.