Skip to content

Commit

Permalink
Merge pull request #51 from artemis-ag/implement-basic-lazy-load
Browse files Browse the repository at this point in the history
Implement basic lazy load
  • Loading branch information
Joe Torreggiani authored Jun 1, 2020
2 parents 488c2dd + 57879b3 commit b29ca50
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 27 deletions.
51 changes: 45 additions & 6 deletions dist/mobx-async-store.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1417,15 +1417,51 @@ function () {
return _this.fetchAll(type, queryParams);
} else if (fromServer === false) {
// If fromServer is false never fetch the data and return
return _this.getMatchingRecords(type, queryParams);
return _this.getMatchingRecords(type, queryParams) || [];
} else {
return _this.findOrFetchAll(type, queryParams);
return _this.findOrFetchAll(type, queryParams) || [];
}
};

this.findAndFetchAll = function (type) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var beforeFetch = options.beforeFetch,
afterFetch = options.afterFetch,
beforeRefetch = options.beforeRefetch,
afterRefetch = options.afterRefetch,
afterError = options.afterError,
queryParams = options.queryParams;

var records = _this.getMatchingRecords(type, queryParams); // NOTE: See note findOrFetchAll about this conditional logic.


if (records.length > 0) {
beforeRefetch && beforeRefetch(records);

_this.fetchAll(type, queryParams).then(function (result) {
return afterRefetch && afterRefetch(result);
}).catch(function (error) {
return afterError && afterError(error);
});
} else {
beforeFetch && beforeFetch(records);

_this.fetchAll(type, queryParams).then(function (result) {
return afterFetch && afterFetch(result);
}).catch(function (error) {
return afterError && afterError(error);
});
}

return records || [];
};

this.findOrFetchAll = function (type, queryParams) {
// Get any matching records
var records = _this.getMatchingRecords(type, queryParams); // If any records are present
var records = _this.getMatchingRecords(type, queryParams); // NOTE: A broader RFC is in development to improve how we keep data in sync
// with the server. We likely will want to getMatchingRecords and getRecords
// to return null if nothing is found. However, this causes several regressions
// in portal we will need to address in a larger PR for mobx-async-store updates.


if (records.length > 0) {
Expand Down Expand Up @@ -2338,9 +2374,12 @@ function getRelatedRecords(record, property) {
});
} else {
var foreignId = "".concat(singularizeType(record.type), "_id");
relatedRecords = record.store.getRecords(relationType).filter(function (rel) {
return String(rel[foreignId]) === String(record.id);
});

if (record.store.getRecords(relationType)) {
relatedRecords = record.store.getRecords(relationType).filter(function (rel) {
return String(rel[foreignId]) === String(record.id);
});
}
}

return new RelatedRecordsArray(relatedRecords, record, relationType);
Expand Down
51 changes: 45 additions & 6 deletions dist/mobx-async-store.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -1411,15 +1411,51 @@ function () {
return _this.fetchAll(type, queryParams);
} else if (fromServer === false) {
// If fromServer is false never fetch the data and return
return _this.getMatchingRecords(type, queryParams);
return _this.getMatchingRecords(type, queryParams) || [];
} else {
return _this.findOrFetchAll(type, queryParams);
return _this.findOrFetchAll(type, queryParams) || [];
}
};

this.findAndFetchAll = function (type) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var beforeFetch = options.beforeFetch,
afterFetch = options.afterFetch,
beforeRefetch = options.beforeRefetch,
afterRefetch = options.afterRefetch,
afterError = options.afterError,
queryParams = options.queryParams;

var records = _this.getMatchingRecords(type, queryParams); // NOTE: See note findOrFetchAll about this conditional logic.


if (records.length > 0) {
beforeRefetch && beforeRefetch(records);

_this.fetchAll(type, queryParams).then(function (result) {
return afterRefetch && afterRefetch(result);
}).catch(function (error) {
return afterError && afterError(error);
});
} else {
beforeFetch && beforeFetch(records);

_this.fetchAll(type, queryParams).then(function (result) {
return afterFetch && afterFetch(result);
}).catch(function (error) {
return afterError && afterError(error);
});
}

return records || [];
};

this.findOrFetchAll = function (type, queryParams) {
// Get any matching records
var records = _this.getMatchingRecords(type, queryParams); // If any records are present
var records = _this.getMatchingRecords(type, queryParams); // NOTE: A broader RFC is in development to improve how we keep data in sync
// with the server. We likely will want to getMatchingRecords and getRecords
// to return null if nothing is found. However, this causes several regressions
// in portal we will need to address in a larger PR for mobx-async-store updates.


if (records.length > 0) {
Expand Down Expand Up @@ -2332,9 +2368,12 @@ function getRelatedRecords(record, property) {
});
} else {
var foreignId = "".concat(singularizeType(record.type), "_id");
relatedRecords = record.store.getRecords(relationType).filter(function (rel) {
return String(rel[foreignId]) === String(record.id);
});

if (record.store.getRecords(relationType)) {
relatedRecords = record.store.getRecords(relationType).filter(function (rel) {
return String(rel[foreignId]) === String(record.id);
});
}
}

return new RelatedRecordsArray(relatedRecords, record, relationType);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@artemisag/mobx-async-store",
"version": "1.0.23",
"version": "1.0.24",
"module": "dist/mobx-async-store.esm.js",
"browser": "dist/mobx-async-store.cjs.js",
"main": "dist/mobx-async-store.cjs.js",
Expand Down
133 changes: 125 additions & 8 deletions spec/Store.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,7 @@ describe('Store', () => {
describe('if records of the specified type do not exist', () => {
it('returns an empty array', () => {
expect.assertions(1)
const todos = store.findAll('todos', {
fromServer: false
})
const todos = store.findAll('todos', { fromServer: false })
expect(todos).toHaveLength(0)
})
})
Expand All @@ -234,9 +232,7 @@ describe('Store', () => {
it('fetches data from server', async () => {
expect.assertions(4)
fetch.mockResponse(mockTodosResponse)
const todos = await store.findAll('todos', {
fromServer: true
})
const todos = await store.findAll('todos', { fromServer: true })
expect(todos).toHaveLength(1)
expect(todos[0].title).toEqual('Do taxes')
expect(fetch.mock.calls).toHaveLength(1)
Expand Down Expand Up @@ -338,8 +334,7 @@ describe('Store', () => {
expect(todos).toHaveLength(1)
expect(todos[0].title).toEqual('Do taxes')
expect(fetch.mock.calls).toHaveLength(1)
expect(fetch.mock.calls[0][0])
.toEqual('/example_api/todos')
expect(fetch.mock.calls[0][0]).toEqual('/example_api/todos')
})
})

Expand Down Expand Up @@ -532,4 +527,126 @@ describe('Store', () => {
expect(typeof todos[1]).toBe('undefined')
})
})

describe('findAndFetchAll', () => {
let requestOptions
let lazyLoadOptions
let mockAfterFetch = jest.fn()
let mockBeforeFetch = jest.fn()
let mockTodosResponse2
let mockAfterError = jest.fn()

beforeEach(() => {
jest.resetAllMocks()

requestOptions = {
queryParams: {
filter: {
title: 'Do taxes'
}
}
}

lazyLoadOptions = {
...requestOptions,
afterRefetch: mockAfterFetch,
beforeRefetch: mockBeforeFetch,
afterError: mockAfterError
}

mockTodosResponse2 = JSON.stringify({
data: [
mockTodoData.data,
{ ...mockTodoData.data, id: 2, title: 'Test' }
]
})
})

it('triggers a fetch if no cached data is found', async (done) => {
fetch.mockResponse(mockTodosResponse)

lazyLoadOptions.afterRefetch = jest.fn((result) => {
expect(result).toHaveLength(1)
done()
})

await store.findAll('todos', requestOptions)
const result = store.findAndFetchAll('todos', lazyLoadOptions)

expect(result).toHaveLength(0)
expect(fetch).toHaveBeenCalled()
})

it('calls beforeRefetch callback with prefetch result', async () => {
fetch.mockResponse(mockTodosResponse)
await store.findAll('todos', requestOptions)

const result = store.findAndFetchAll('todos', lazyLoadOptions)

expect(result).toHaveLength(1)
expect(mockBeforeFetch).toHaveBeenCalledWith(result)
})

it('calls afterRefetch callback with refetch result', async (done) => {
const mockTodosResponse2 = JSON.stringify({
data: [
mockTodoData.data,
{ ...mockTodoData.data, id: 2, title: 'Test' }
]
})

fetch.mockResponses(
[mockTodosResponse, { status: 200 }],
[mockTodosResponse2, { status: 200 }]
)

// Trigger another request
await store.findAll('todos', requestOptions)

lazyLoadOptions.afterRefetch = jest.fn((result) => {
// The refetch result is different then the cached result, because
// mockTodosResponse2 has 2 records
expect(result).toHaveLength(2)
done()
})

store.findAndFetchAll('todos', lazyLoadOptions)
})

it('returns cached data before refetching', async (done) => {
fetch.mockResponses(
[mockTodosResponse, { status: 200 }],
[mockTodosResponse2, { status: 200 }]
)

await store.findAll('todos', requestOptions)

lazyLoadOptions.afterRefetch = jest.fn((result) => {
// The refetch result is different then the cached result, because
// mockTodosResponse2 has 2 records
expect(result).toHaveLength(2)
done()
})

const result = store.findAndFetchAll('todos', lazyLoadOptions)

// mockTodosResponse has only one record
expect(result).toHaveLength(1)
// fetch was called twice: once from the findAll and once from findAndFetchAll
// refetching
expect(fetch.mock.calls).toHaveLength(2)
})

it('calls afterError if bad request', (done) => {
fetch.mockResponses([mockTodosResponse, { status: 400 }])

lazyLoadOptions.afterError = jest.fn((error) => {
// NOTE: We should have better errors than this.
expect(error).toEqual(400)
done()
})

store.findAndFetchAll('todos', lazyLoadOptions)
})
})
})
44 changes: 41 additions & 3 deletions src/Store.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,46 @@ class Store {
return this.fetchAll(type, queryParams)
} else if (fromServer === false) {
// If fromServer is false never fetch the data and return
return this.getMatchingRecords(type, queryParams)
return this.getMatchingRecords(type, queryParams) || []
} else {
return this.findOrFetchAll(type, queryParams)
return this.findOrFetchAll(type, queryParams) || []
}
}

/**
* @method findAndFetchAll
* @param {String} type the type to find
* @param {Object} options
* @return {Array}
*/
findAndFetchAll = (type, options = {}) => {
const {
beforeFetch,
afterFetch,
beforeRefetch,
afterRefetch,
afterError,
queryParams
} = options

const records = this.getMatchingRecords(type, queryParams)

// NOTE: See note findOrFetchAll about this conditional logic.
if (records.length > 0) {
beforeRefetch && beforeRefetch(records)
this.fetchAll(type, queryParams)
.then((result) => afterRefetch && afterRefetch(result))
.catch((error) => afterError && afterError(error))
} else {
beforeFetch && beforeFetch(records)
this.fetchAll(type, queryParams)
.then((result) => afterFetch && afterFetch(result))
.catch((error) => afterError && afterError(error))
}

return records || []
}

/**
* returns cache if exists, returns promise if not
*
Expand All @@ -240,7 +274,11 @@ class Store {
// Get any matching records
const records = this.getMatchingRecords(type, queryParams)

// If any records are present
// NOTE: A broader RFC is in development to improve how we keep data in sync
// with the server. We likely will want to getMatchingRecords and getRecords
// to return null if nothing is found. However, this causes several regressions
// in portal we will need to address in a larger PR for mobx-async-store updates.

if (records.length > 0) {
// Return data
return records
Expand Down
7 changes: 4 additions & 3 deletions src/decorators/relationships.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ export function getRelatedRecords (record, property, modelType = null) {
})
} else {
const foreignId = `${singularizeType(record.type)}_id`
relatedRecords = record.store
.getRecords(relationType)
.filter(rel => String(rel[foreignId]) === String(record.id))
if (record.store.getRecords(relationType)) {
relatedRecords = record.store.getRecords(relationType)
.filter(rel => String(rel[foreignId]) === String(record.id))
}
}

return new RelatedRecordsArray(relatedRecords, record, relationType)
Expand Down

0 comments on commit b29ca50

Please sign in to comment.