I am trying to use Immutable.js. So I used the Map object and I have a 2 fields for ex.
const initialState = Map({
isUserAuthorized : false,
pending : false
});
and I want to update both. How I can do that? I tried to use a standard Update method like that:
state.update( "isUserAuthorized", () => true, "pending", () => false);
But it isn't working somehow. So I have only one idea - update only particular key and after that do the same with other and return a result.
But I think it is a not so perfect idea. Maybe other normal variants exist? Thanks for any help!
P.S. I found that it can be done via set and withMutations like:
initialState.withMutations(map => {
map.set("isUserAuthorized", true).set("pending", false);
})
But is it really so hard to update multiple values in Map?
You can use .set():
const initialState = Map({
isUserAuthorized : false,
pending : false
});
initialState = initialState.set('isUserAuthorized', true);
initialState = initialState.set('pending', false);
If you don't want to repeat it. You can create a function to pass multiple params.
Another way is with .merge():
const newState = initialState.merge({
isUserAuthorized: true,
pending: false
});
Or chaining multiple sets:
initialState = initialState.set('isUserAuthorized', true)
.set('pending', false)
.set('key3', 'value3');
Bit of a stale post but in case this helps anyone else, I just had to figure this out yesterday. Here is what I ended up doing.
If you want to change all the keys to be the same then you can map.
const initialState = Map({
isUserAuthorized : false,
pending : false
});
const newState = initialState.map(() => true)
// { isUserAuthorized: true, pending: true }
If there's specific keys you want to set then add a condition or ternary expression in your map and only update what's relevant. Here's a quick and dirty example.
const initialState = Map({
isUserAuthorized : false,
pending : false,
dontChangeMe: false
});
const newState = initialState.map((val, key) => (
key === 'isUserAuthorized' || key === 'pending' ? true : value
)
// { isUserAuthorized: true, pending: true, dontChangeMe: false }
If you want to get clever and make things reusable you could make a curried utility to pass in a list of the target keys, then just invoke with the value you want them to be.
Related
My component needs to act upon a property change once, during initialization. Subsequent changes to the property do not require any action.
I've currently solved this with a workaround like the one below. Is there a better way to $watch something once?
const Component = () => ({
property: ""
hasInitialized: false,
init() {
this.$watch("property", (value, oldValue) => {
if (!this.hasInitialized) {
// Go do something
this.hasInitialized = true;
}
});
},
});
export default Component;
I want to disabled a button on a lightning datatable based on a boolean field, I came to this solution:
const columns = [
{ label: 'Client', type:'button',
typeAttributes: {
label: 'Client',
disabled: {fieldName: 'Client__c' }
} }
];
The problem is, I need to make visible when is true, but it actually doing the opposite, i search to a enabled property or trying something like this:
Client__c == true ? false : true;
but it doens't work..
I also try this solution here
this.tableData = result.data.map((value) => ({ ...value, clientDisabled: ('Client__c' == true) ? false : true }));
And the column:
const columns = [
{ label: 'Client', type:'button',
typeAttributes: {
label: 'Client',
disabled: {fieldName: 'clientDisabled' }
} }
];
Also, not work, all buttons became disabled.
Also, I would like to put a - when is disabled (and the field = false), like this:
'Client__c' == true is always false; you're comparing two literal values that will never be equal. You'll want to use the data from the record instead:
this.tableData = result.data.map((record) => ({ ...record, clientDisabled: !record.Client__c }));
There is an array in public users = new BehaviorSubject<User[]>([]).
I want to delete element from this observable and refresh it.
My solution is:
const idRemove = 2;
this.users.next(this.user.getValue().filter((u) => u.id !== idRemove)
But I seem I use wrong way of using RXJS
Toward Idiomatic RxJS
Using subscribe instead of .value.
interface User {
id: number
}
const users$ = new BehaviorSubject<User[]>([
{id:1},
{id:2},
{id:3}
]);
function removeId(idRemove: number) {
users$.pipe(
take(1),
map(us => us.filter(u => u.id !== idRemove))
).subscribe(
users$.next.bind(users$)
);
}
users$.subscribe(us =>
console.log("Current Users: ", us)
);
removeId(2);
removeId(1);
removeId(3);
Output:
Current Users: [ { id: 1 }, { id: 2 }, { id: 3 } ]
Current Users: [ { id: 1 }, { id: 3 } ]
Current Users: [ { id: 3 } ]
Current Users: []
To handle state within RxJS pipes you can use the Scan operator
Useful for encapsulating and managing state. Applies an accumulator (or "reducer function") to each value from the source after an initial state is established -- either via a seed value (second argument), or from the first value from the source.
const { Subject, merge } = rxjs;
const { scan, map } = rxjs.operators;
// This function is used to apply new users to the state of the scan
const usersFn = users => state => users
// This function is used to remove all matching users with the given id from the state of the scan
const removeFn = removeId => state => state.filter(user => user.id !== removeId)
// This Subject represents your old user BehaviorSubject
const users$$ = new Subject()
// This Subject represents the place where this.users.next(this.user.getValue().filter((u) => u.id !== idRemove) was called
const remove$$ = new Subject()
// This is your new user$ Observable that handles a state within its pipe. Use this Observable in all places where you need your user Array instead of the user BehaviorSubject
const user$ = merge(
// When users$$ emits the usersFn is called with the users argument (1. time)
users$$.pipe(map(usersFn)),
// When remove$$ emits the removeFn is called with the removeId argument (1. time)
remove$$.pipe(map(removeFn))
).pipe(
// Either the usersFn or removeFn is called the second time with the state argument (2. time)
scan((state, fn) => fn(state), [])
)
// Debug subscription
user$.subscribe(console.log)
// Test emits
users$$.next([
{id: 1, name: "first"},
{id: 2, name: "second"}
])
remove$$.next(2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
Ben Lesh (main Contributor of RxJS) wrote an anser about why not to use getValue in RxJS: The only way you should be getting values "out of" an Observable/Subject is with subscribe!
Using getValue() is discouraged in general for reasons explained here even though I'm sure there are exception where it's fine to use it. So a better way is subscribing to get the latest value and then changing it:
this.users
.pipe(take(1)) // take(1) will make sure we're not creating an infinite loop
.subscribe(users => {
this.users.next(users.filter((u) => u.id !== idRemove);
});
In an rxjs stream, I'm using distinctUntilChanged with lodash's isEqual to filter out duplicate values. However it appears to not be working as expected. Take the following code snippet
import { isEqual } from 'lodash-es';
let cachedValue: any;
function testFn(observableVal: Observable<any>) {
return observableVal
.pipe(
distinctUntilChanged(isEqual),
tap(val => {
const equal = isEqual(cachedValue, val);
console.log('"output":', equal, cachedValue, val);
cachedValue = val;
})
)
}
In this example, I would expect that const equal inside the tap function would never === true. I would expect that distinctUntilChanged(isEqual) would filter out any values where isEqual(cachedValue, val) === true --> meaning that const equal === false always. However, console output shows:
"output": false undefined [ContactList]
"output": true [ContactList] [ContactList]
"output": true [ContactList] [ContactList]
"output": true [ContactList] [ContactList]
Do I misunderstand something fundamental about how the distinctUntilChanged() operator works? I've posted a simplified example because the actual rxjs stream is very complex, but I wouldn't expect the complexity to make any difference in so far as const equal should always === false in the tap operator.
I'm just trying to understand what's going on, so any info is appreciated. Thanks!
Update
It should be noted that if I change the code to:
function testFn(observableVal: Observable<any>) {
return observableVal
.pipe(
filter(val => {
const equal = isEqual(cachedValue, val);
cachedValue = val;
return !equal;
}),
tap(val => {
console.log('"output":', val);
})
)
}
Then the filtering works as expected. I was under the impression that distinctUntilChanged(isEqual) was equivalent to:
filter(val => {
const equal = isEqual(cachedValue, val);
cachedValue = val;
return !equal;
})
Am I mistaken / misunderstanding the distinctUntilChanged operator?
I figured it out! Thanks to a comment in an rxjs issue: I had accidently subscribed to the observable multiple times (which shouldn't have happened). The multiple console.log instances were coming from different subscription instances.
In case someone will get here because of similar issue as I had - keep in mind that custom comparator function should return false to pass the distinctUntilChanged operator.
In other words this comparator will allow to pass when no changes in someProp:
distinctUntilChanged((prev, curr) => prev.someProp !== curr.someProp)
and this - only if someProp was changed:
distinctUntilChanged((prev, curr) => prev.someProp === curr.someProp)
I've tested in various ways... Still, It isn't working.
I don't seem to doing anything wrong
exactly same code as reselect doc
redux store is all normalized
reducers are all immutable
From parent component, I just pass down a prop with id and from child component, connected with redux and used selector to get that exact item by id(from parent component)
### This is what Parent components render looks like
render() {
return (
<div>
<h4>Parent component</h4>
{this.props.sessionWindow.tabs.map(tabId =>
<ChildComponentHere key={tabId} tabId={tabId} />
)}
</div>
);
}
### This is what Child component looks like
render() {
const { sessionTab } = this.props (this props is from connect() )
<div>
<Tab key={sessionTab.id} tab={sessionTab} />
</div>
))
}
### Selectors for across multiple components
const getTheTab = (state: any, ownProps: IOwnProps) => state.sessionWindows.sessionTab[ownProps.tabId];
const makeTheTabSelector = () =>
createSelector(
[getTheTab],
(tab: object) => tab
)
export const makeMapState = () => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props)
}
}
return mapStateToProps
}
Weirdly Working solution: just change to deep equality check.(from anywhere)
use selectors with deep equality works as expected.
at shouldComponentUpdate. use _.isEqual also worked.
.
1. const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
)
2. if (!_isEqual(this.props, nextProps) || !_isEqual(this.state, nextState)){return true}
From my understanding, my redux is always immutable so when something changed It makes new reference(object or array) that's why react re-renders. But when there is 100 items and only 1 item changed, only component with that changed props get to re-render.
To make this happen, I pass down only id(just string. shallow equality(===) works right?)using this id, get exact item.(most of the components get same valued input but few component get different valued input) Use reselect to memoize the value. when something updated and each component get new referenced input compare with memoized value and re-render when something trully changed.
This is mostly what I can think of right now... If I have to use _isEqual anyway, why would use reselect?? I'm pretty sure I'm missing something here. can anyone help?
For more clarification.(hopefully..)
First,My redux data structure is like this
sessionWindow: {
byId: { // window datas byId
"windowId_111": {
id: "windowId_111",
incognito: false,
tabs: [1,7,3,8,45,468,35,124] // this is for the order of sessionTab datas that this window Item has
},
"windowId_222": {
id: "windowId_222",
incognito: true,
tabs: [2, 8, 333, 111]
},{
... keep same data structure as above
}
},
allIds: ["windowId_222", "windowId_111"] // this is for the order of sessionWindow datas
}
sessionTab: { // I put all tab datas here. each sessionTab doesn't know which sessionWindow they are belong to
"1": {
id: 1
title: "google",
url: "www.google.com",
active: false,
...more properties
},
"7": {
id: 7
title: "github",
url: "www.github.com",
active: true
},{
...keep same data structure as above
}
}
Problems.
1. when a small portion of data changed, It re-renders all other components.
Let's say sessionTab with id 7's url and title changed. At my sessionTab Reducer with 'SessionTabUpdated" action dispatched. This is the reducer logic
const updateSessionTab = (state, action) => {
return {
...state,
[action.tabId]: {
...state[action.tabId],
title: action.payload.title,
url: action.payload.url
}
}
}
Nothing is broken. just using basic reselect doesn't prevent from other components to be re-rendered. I have to use deep equality version to stop re-render the component with no data changed
After few days I've struggled, I started to think that the problem is maybe from my redux data structure? because even if I change one item from sessionTab, It will always make new reference like {...state, [changedTab'id]: {....}} In the end, I don't know...
Three aspects of your selector definition and usage look a little odd:
getTheTab is digging down through multiple levels at once
makeTheTabSelector has an "output selector" that just returns the value it was given, which means it's the same as getTheTab
In mapState, you're passing the entire props object to theTabSelector(state, props).
I'd suggest trying this, and see what happens:
const selectSessionWindows = state => state.sessionWindows;
const selectSessionTabs = createSelector(
[selectSessionWindows],
sessionWindows => sessionWindows.sessionTab
);
const makeTheTabSelector = () => {
const selectTabById = createSelector(
[selectSessionTabs, (state, tabId) => tabId],
(sessionTabs, tabId) => sessionTabs[tabId]
);
return selectTabById;
}
export const makeMapState() => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props.tabId)
}
}
return mapStateToProps
}
No guarantees that will fix things, but it's worth a shot.
You might also want to try using some devtool utilities that will tell you why a component is re-rendering. I have links to several such tools in the Devtools#Component Update Monitoring section of my Redux addons catalog.
Hopefully that will let you figure things out. Either way, leave a comment and let me know.