ra-data-local-storage
Version:
Local storage data provider for react-admin
170 lines (162 loc) • 6.35 kB
text/typescript
/* eslint-disable eqeqeq */
import fakeRestProvider from 'ra-data-fakerest';
import { DataProvider, RaRecord } from 'ra-core';
import pullAt from 'lodash/pullAt';
/**
* Respond to react-admin data queries using a local database persisted in localStorage
*
* Useful for local-first web apps. The storage is shared between tabs.
*
* @example // initialize with no data
*
* import localStorageDataProvider from 'ra-data-local-storage';
* const dataProvider = localStorageDataProvider();
*
* @example // initialize with default data (will be ignored if data has been modified by user)
*
* import localStorageDataProvider from 'ra-data-local-storage';
* const dataProvider = localStorageDataProvider({
* defaultData: {
* posts: [
* { id: 0, title: 'Hello, world!' },
* { id: 1, title: 'FooBar' },
* ],
* comments: [
* { id: 0, post_id: 0, author: 'John Doe', body: 'Sensational!' },
* { id: 1, post_id: 0, author: 'Jane Doe', body: 'I agree' },
* ],
* }
* });
*/
export default (params?: LocalStorageDataProviderParams): DataProvider => {
const {
defaultData = {},
localStorageKey = 'ra-data-local-storage',
loggingEnabled = false,
localStorageUpdateDelay = 10, // milliseconds
} = params || {};
const localStorageData = localStorage.getItem(localStorageKey);
let data = localStorageData ? JSON.parse(localStorageData) : defaultData;
// change data by executing callback, then persist in localStorage
const updateLocalStorage = callback => {
// modify localStorage after the next tick
setTimeout(() => {
callback();
localStorage.setItem(localStorageKey, JSON.stringify(data));
}, localStorageUpdateDelay);
};
let baseDataProvider = fakeRestProvider(
data,
loggingEnabled
) as DataProvider;
window?.addEventListener('storage', event => {
if (event.key === localStorageKey) {
const newData = event.newValue ? JSON.parse(event.newValue) : {};
data = newData;
baseDataProvider = fakeRestProvider(
newData,
loggingEnabled
) as DataProvider;
}
});
return {
// read methods are just proxies to FakeRest
getList: <RecordType extends RaRecord = any>(resource, params) =>
baseDataProvider
.getList<RecordType>(resource, params)
.catch(error => {
if (error.code === 1) {
// undefined collection error: hide the error and return an empty list instead
return { data: [], total: 0 };
} else {
throw error;
}
}),
getOne: <RecordType extends RaRecord = any>(resource, params) =>
baseDataProvider.getOne<RecordType>(resource, params),
getMany: <RecordType extends RaRecord = any>(resource, params) =>
baseDataProvider.getMany<RecordType>(resource, params),
getManyReference: <RecordType extends RaRecord = any>(
resource,
params
) =>
baseDataProvider
.getManyReference<RecordType>(resource, params)
.catch(error => {
if (error.code === 1) {
// undefined collection error: hide the error and return an empty list instead
return { data: [], total: 0 };
} else {
throw error;
}
}),
// update methods need to persist changes in localStorage
update: <RecordType extends RaRecord = any>(resource, params) => {
updateLocalStorage(() => {
const index = data[resource]?.findIndex(
record => record.id == params.id
);
data[resource][index] = {
...data[resource][index],
...params.data,
};
});
return baseDataProvider.update<RecordType>(resource, params);
},
updateMany: (resource, params) => {
updateLocalStorage(() => {
params.ids.forEach(id => {
const index = data[resource]?.findIndex(
record => record.id == id
);
data[resource][index] = {
...data[resource][index],
...params.data,
};
});
});
return baseDataProvider.updateMany(resource, params);
},
create: <RecordType extends Omit<RaRecord, 'id'> = any>(
resource,
params
) => {
// we need to call the fakerest provider first to get the generated id
return baseDataProvider
.create<RecordType>(resource, params)
.then(response => {
updateLocalStorage(() => {
if (!data.hasOwnProperty(resource)) {
data[resource] = [];
}
data[resource].push(response.data);
});
return response;
});
},
delete: <RecordType extends RaRecord = any>(resource, params) => {
updateLocalStorage(() => {
const index = data[resource]?.findIndex(
record => record.id == params.id
);
pullAt(data[resource], [index]);
});
return baseDataProvider.delete<RecordType>(resource, params);
},
deleteMany: (resource, params) => {
updateLocalStorage(() => {
const indexes = params.ids.map(id =>
data[resource]?.findIndex(record => record.id == id)
);
pullAt(data[resource], indexes);
});
return baseDataProvider.deleteMany(resource, params);
},
};
};
export interface LocalStorageDataProviderParams {
defaultData?: any;
localStorageKey?: string;
loggingEnabled?: boolean;
localStorageUpdateDelay?: number;
}