I started using ngrx/entity package, where I can manage store by adapter. There is addOne method I'd like to use, but it adds item to the end of collection. I wanna add one at the beginning. Could you please help me with that? How to add item at the beginning with EntityAdapter.
How I create entity adapter:
export const adapter: EntityAdapter<AssetTreeNode> = createEntityAdapter({
selectId: (model: AssetTreeNode) => model.Id
});
Reducer looks like that:
export function reducer(state: AssetListState = initialState, action: AssetListAction) {
switch (action.type) {
(...)
case ASSET_LIST_ADD_ITEM:
let assetToAdd: AssetTreeNode = Object.assign({} as AssetTreeNode,
action.payload.asset,
{ Id: action.payload.createdAssetId });
return adapter.addOne(assetToAdd, state); <--- I wanna add here at the end.
(...)
default:
return state;
}
}
There is no proper way provided by #ngrx/entity team. One of the answer mentions to use sort-comparator. But i believe using sort-comparator is not the right way to go. Suppose there is two click actions and in one action we need to append item below and in other action on top. here we will run into the same problem again.
I had run into the same issue and my solution to the problem is to reconstruct the list when we want the item on top of the list.
To add at the top of entity list
const { selectAll } = myAdapter.getSelectors();
...
...
on(MyActions.addItem, (state, { item }) =>{
return myAdapter.setAll([item ,...selectAll(state)], { ...state})
}),
To add at the bottom of entity list
on(MyActions.addItem, (state, { item}) =>{
return myAdapter.addOne(item, state)
}),
The only way to change this behavior would be to use the sortComparer when you create the adapter - docs.
export const adapter: EntityAdapter<User> = createEntityAdapter<User>({
sortComparer: (a: User, b: User) => a.name.localeCompare(b.name),
});
Maybe you could place the item at the begining and replace the list
on(addAsset, (state, { payload }) => {
const currentList = Object.values(state.entities);
const newList = [payload, ...currentList];
return adapter.setAll(newList, state);
});
Related
I have an object in my pinia store like
import { defineStore } from "pinia";
export const useSearchStore = defineStore("store", {
state: () => {
return {
myobj: {
foo: 0,
bar: 2000,
too: 1000,
},
};
},
getters: {
changed() {
// doesn't work
return Object.entries(this.myobj).filter(([key, value]) => value != initialvalue
);
},
},
});
How do I get the initial value to test if the object changed. Or how can I return a filtered object with only those entries different from initial state?
My current workaround:
in a created hook I make a hard copy of the store object I then can compare to. I guess there is a more elegant way...
I had done this (although I do not know if there a better way to avoid cloning without duplicating your initial state).
Define your initial state outside and assign it to a variable as follows;
const initialState = {
foo: 0,
bar: 2000,
too: 1000
}
Then you can use cloning to retain the original state;
export const useSearchStore = defineStore("store", {
state: () => {
return {
myobj: structuredClone(initialState),
};
},
getters: {
changed: (state) => deepEquals(initialState, state.myobj);
},
});
where deepEquals is a method which deep compares the two objects (which you would have to implement). I would use lodash (npm i lodash and npm i #types/lodash --save-dev if you're using TypeScript) for this.
Full code (with lodash);
import { defineStore } from "pinia";
import { cloneDeep, isEqual } from "lodash";
const initialState = {
foo: 0,
bar: 2000,
too: 1000
}
export const useSearchStore = defineStore("store", {
state: () => ({
myobj: cloneDeep(initialState)
}),
getters: {
changed(state) {
return isEqual(initialState, state.myobj);
},
},
});
If you also want the differences between the two you can use the following function (the _ is lodash - import _ from "lodash");
function difference(object, base) {
function changes(object, base) {
return _.transform(object, function (result: object, value, key) {
if (!_.isEqual(value, base[key])) {
result[key] =
_.isObject(value) && _.isObject(base[key])
? changes(value, base[key])
: value;
}
});
}
return changes(object, base);
}
courtesy of https://gist.github.com/Yimiprod/7ee176597fef230d1451
EDIT:
The other way you would do this is to use a watcher to subscribe to changes. The disadvantage to this is that you either have to be OK with your state marked as "changed" if you change back the data to the initial state. Otherwise, you would have to implement a system (perhaps using a stack data structure) to maintain a list of changes so that if two changes which cancel each other out occur then you would remark the state as "unchanged". You would have to keep another variable (boolean) in the state which holds whether the state has been changed/unchanged - but this would be more complicated to implement and (depending on your use case) not worth it.
I'm having trouble mocking the call of an individually imported function to my tests. The test is a simple function that I put within my Redux actions to be able to set a variable based on a condition.
Here's the function in Body.duck.js:
export const getCurrentOrPrevSelection = isExecutedFromPagination => (dispatch, getState) => {
const {
editor: { selection },
body: { queryRequest },
} = getState();
if (isExecutedFromPagination && queryRequest.breadcrumb) {
const {
query: { branch, includeSplits, primaryFa, split, isInitial },
} = queryRequest.breadcrumb;
return {
branch,
includeSplits,
primaryFa,
split,
isInitial,
};
}
return selection;
};
And here's the test file:
import reudcer, { ...other exported functions, getCurrentOrPrevSelection } from '../Body.duck';
it ('should use selection in breadcrumb state when fetching new data from pagination action', () => {
let isExecutedFromPagination = false;
const bodyState = {
...initialState.body,
queryRequest: {
...initialState.body.queryRequest,
breadcrumb: {
...initialState.body.breadcrumb,
query: {
name: 'Full Book Performance',
branch: null,
includeSplits: true,
primaryFa: 'AXFO',
split: null,
isInitial: true,
},
},
},
};
const selection = {
branch: null,
includeSplits: true,
primaryFa: 'AXFO',
split: null,
isInitial: true,
};
expect(getCurrentOrPrevSelection(isExecutedFromPagination)(jest.fn(), () => ({
body: { ...bodyState },
editor: { faidSelection },
}))).toHaveReturnedWith({
branch: null,
includeSplits: true,
primaryFa: 'AXFO',
split: null,
isInitial: true,
});
});
If I don't include any sort of mock reference to getCurrentOrPrevSelection, I get this error below, but it returns the correct value as expected:
expect(jest.fn())[.not].toHaveReturnedWith()
jest.fn() value must be a mock function or spy.
Received:
object: {"branch": null, "includeSplits": true, "isInitial": true, "primaryFa": "AXFO", "split": null}
If I do something like getCurrentOrPrevFaidSelection = jest.fn();, I get an error saying getCurrentOrPrevFaidSelection is read-only
What can I do differently here?
You want to test this function. So you don't need to mock that.
Just call function and verify result with expect().toEqual or expect().toMatchObject.
expect(getCurrentOrPrevSelection(isExecutedFromPagination)(.....)).toMatchObject({
branch: null,
...
});
Also passing jest.fn() directly as argument does not really make sense: you cannot either verify it has been called or provide mock return.
const dispatchMock = jest.fn();
expect(getCurrentOrPrevSelection(isExecutedFromPagination)(dispatchMock, ....);
expect(dispatchMock).toHaveBeenCalledWith(...)
Once it's just not expected to be called as it is in your sample you better explicitly provide noop function () => {} instead of jest.fn(). This way you make it's explicit so nobody will be confused if it's expected there is no assertions against this function or not.
Offtop: actually this is not really good way to test redux action creators. See you actually test implementation details. What if you migrate from redux-thunk to redux-saga or redux-loop? Or split single action into 2 for better flexibility? By now it would mean you have to rewrite all your tests.
What if instead of testing action creator in isolation you connect action to real(not mocked) store? You could dispatch action(after mocking calls to external API) and validate store's state.
According to the angularfire2 documentation the following can be done when you wan't to update a item in a list :
const items = af.database.list('/items');
// to get a key, check the Example app below
items.update('key-of-some-data', { size: newSize });
But is is possible to update an item in the list, without having to specify key:values for the object like this?
items.update('key-of-some-data', item);
In angularfire this is possible to do like this:
<li ng-repeat="item in list">
<input type="text" ng-model="item.title" ng-change="list.$save(item)" />
</li>
Thanks for taking your time to read this question :)
The implementation of update looks like this:
update(item: FirebaseOperation, value: Object): firebase.Promise<void> {
return this._checkOperationCases(item, {
stringCase: () => this.$ref.ref.child(<string>item).update(value),
firebaseCase: () => (<firebase.database.Reference>item).update(value),
snapshotCase: () => (<firebase.database.DataSnapshot>item).ref.update(value),
unwrappedSnapshotCase: () => this.$ref.ref.child((<AFUnwrappedDataSnapshot>item).$key).update(value)
});
}
So it's possible to call update in the following ways:
Using a string key and a value:
const items = af.database.list('/items');
items.update('key-of-some-data', { size: newSize });
Using a Firebase ref and a value:
const items = af.database.list('/items');
const ref = items.$ref.ref;
items.update(ref.child('key-of-some-data'), { size: newSize });
Using a Firebase snapshot and a value:
const items = af.database.list('/items', { preserveSnapshot: true });
items.subscribe(list => {
const snapshot = list[0];
items.update(snapshot, { size: newSize });
});
Using an unwrapped list item and a value:
const items = af.database.list('/items');
items.subscribe(list => {
const item = list[0];
items.update(item, { size: newSize });
});
(The snippets above that call subscribe are only to illustrate that the snapshot and unwrapped items are the list observable's emitted values. Using subscribe like this to perform an update makes no sense.)
AngularFire2 is currently undergoing some refactoring and rearranging in preparation for a release candidate. If you have a use case for which none of the above options is suitable, now is the time to speak up. The discussion is here. However, for something this specific, you should create a new issue.
As far as my understanding goes, it's an anti-pattern to dispatch actions from within a store update handler. Correct?
How can I handle the following workflow then?
I have some company switcher on my page header
Clicking on a company dispatches some SELECTEDCOMPANY_UPDATE action
The active view reacts on the according change in the state store by forcing a data reload. E.g. by calling companyDataService.fetchOrders(companyName).
I'd like to show some loading animation during the data is being fetched and therefore have an dedicated action like FETCHINGDATA_UPDATE which updates the fetchingData section in my app state store to which all interested views can react by showing/hiding the load mask
Where do I actually dispatch the FETCHINGDATA_UPDATE action? If I directly do this from within companyDataService.fetchOrders(companyName) it would be called from within a store update handler (see OrdersView.onStoreUpdate in exemplary code below)...
Edit
To clarify my last sentence I'm adding some exemplary code which shows how my implementation would have looked like:
ActionCreator.js
// ...
export function setSelectedCompany(company) {
return { type: SELECTEDCOMPANY_UPDATE, company: company };
}
export function setFetchingData(isFetching) {
return { type: FETCHINGDATA_UPDATE, isFetching: isFetching };
}
// ...
CompanyDataService.js
// ...
export fetchOrders(companyName) {
this.stateStore.dispatch(actionCreator.setFetchingData(true));
fetchData(companyName)
.then((data) => {
this.stateStore.dispatch(actionCreator.setFetchingData(false));
// Apply the data...
})
.catch((err) => {
this.stateStore.dispatch(actionCreator.setFetchingData(false));
this.stateStore.dispatch(actionCreator.setFetchError(err));
})
}
// ...
CompanySwitcher.js
// ...
onCompanyClicked(company) {
this.stateStore.dispatch(actionCreator.setSelectedCompany(company));
}
// ...
OrdersView.js
// ...
constructor() {
this._curCompany = '';
this.stateStore.subscribe(this.onStoreUpdate);
}
// ...
onStoreUpdate() {
const stateCompany = this.stateStore.getState().company;
if (this._curCompany !== stateCompany) {
// We're inside a store update handler and `fetchOrders` dispatches another state change which is considered bad...
companyDataService.fetchOrders(stateCompany);
this._curCompany = stateComapny;
}
}
// ...
I agree with Davin, in the action creator is the place to do this, something like:
export function fetchOrders (company) {
return (dispatch) => {
dispatch ({ type: FETCHINGDATA_UPDATE });
return fetchOrderFunction ().then(
(result) => dispatch ({ type: FETCHING_COMPLETED, result }),
(error) => dispatch ({ type: FETCHING_FAILED, error })
);
};
}
Then in the reducer FETCHINGDATA_UPDATE can set your loading indicator to true and you can set it back to false I both SUCCESS and FAILED
Yo! I'm using Redux and Normalizr. The API I'm working with sends down objects that look like this:
{
name: 'Foo',
type: 'ABCD-EFGH-IJKL-MNOP'
}
or like this
{
name: 'Foo2',
children: [
'ABCD-EFGH-IJKL-MNOP',
'QRST-UVWX-YZAB-CDEF'
]
}
I want to be able to asynchronously fetch those related entities (type and children) when the above objects are accessed from the state (in mapStateToProps). Unfortunately, this does not seem to mesh with the Redux way as mapStateToProps is not the right place to call actions. Is there an obvious solution to this case that I'm overlooking (other than pre-fetching all of my data)?
Not sure that I have correctly understood your use-case, but if you want to fetch data, one simple common way is to trigger it from a React component:
var Component = React.createClass({
componentDidMount: function() {
if (!this.props.myObject) {
dispatch(actions.loadObject(this.props.myObjectId));
}
},
render: function() {
const heading = this.props.myObject ?
'My object name is ' + this.props.myObject.name
: 'No object loaded';
return (
<div>
{heading}
</div>
);
},
});
Given the "myObjectId" prop, the component triggers the "myObject" fetching after mounting.
Another common way would be to fetch the data, if it's not already here, from a Redux async action creator (see Redux's doc for more details about this pattern):
// sync action creator:
const FETCH_OBJECT_SUCCESS = 'FETCH_OBJECT_SUCCESS';
function fetchObjectSuccess(objectId, myObject) {
return {
type: FETCH_OBJECT_SUCCESS,
objectId,
myObject,
};
}
// async action creator:
function fetchObject(objectId) {
return (dispatch, getState) => {
const currentAppState = getState();
if (!currentAppState.allObjects[objectId]) {
// fetches the object if not already present in app state:
return fetch('some_url_.../' + objectId)
.then(myObject => (
dispatch(fetchObjectSuccess(objectId, myObject))
));
} else {
return Promise.resolve(); // nothing to wait for
}
};
}