Update Immutable.Map - reactjs

I would like to change the active for item 1 to true, while also setting others (some hundred) active to false.
import {Map, Record} from 'immutable';
const initialState = new Map({
0: new Record({
id: 0,
name: 'foo',
active: true,
}),
1: new Record({
id: 1,
name: 'bar',
active: false,
}),
2: new Record({
id: 2,
name: 'baz',
active: false,
}),
});
const TARGET = 1;
const newState = initialState.merge(
initialState.map((value) => {
if (value.id === TARGET) {
return value.set('active', true);
} else if (value.active) {
return value.set('active', false);
}
return value;
})
);
Is it possible to do it easiest or better? Thanks!

Related

How to set state in nested array of objects in ReactJs?

I have this object as a state in reactjs. I want to add another object inside the "childoptions: []" array which is nested inside the options array on button click.
How can I achieve this, pls help...
const [select1, setSelect1] = useState({
id: uuid(),
type: 'select',
properties: {
label: 'Select1',
options: [
// {
// id: uuid(),
// optionName: 'red 🔴',
// value: '',
// childOptions: [],
// },
// {
// id: uuid(),
// optionName: 'green 🟢',
// value: '',
// childOptions: [],
// },
// {
// id: uuid(),
// optionName: 'blue 🔵',
// value: '',
// childOptions: [],
// },
],
},
parentId: null,
});
This is achievable by copy the prevState in the new state with the new object inserted inside the options array.
A more detailed explanation could be found at https://stackoverflow.com/a/26254086/9095807
setSelect1((prevState) => {
return {
...prevState,
properties: {
label: 'Select1',
options: prevState.properties.options.map(obj => obj.id === id ? { ...obj, childOptions: [...obj.childOptions, newChildOptions] } : obj),
}
}
})

Redux-toolkit reducer partially changing state

Hi everyone I have some redux-toolkit issue that I believe comes from immer but cannot be sure.
Using createSlice I am creating reducer to manage open/close/move/add of UI tabs. all of the reducers are working properly except the closeTab reducer.
This is an example of the state at the moment of execution of closeTab reducer:
[{
id: 1,
active: false,
tittle: 'Cute',
},
{
id: 2,
active: true,
tittle: 'Cute',
}]
This is the entire createSlice
export const tabsSlice = createSlice({
name: "tabs",
initialState,
reducers: {
moveTab: (tabs, action: PayloadAction<{ dragIndex: number, hoverIndex: number }>) => {
tabs.splice(action.payload.hoverIndex, 0, tabs.splice(action.payload.dragIndex, 1)[0]);
},
selectTab: (tabs, action: PayloadAction<number>) => {
tabs.forEach(tab => tab.active = tab.id === action.payload);
},
closeTab: (tabs, action: PayloadAction<number>) => {
const isCurrentActive = tabs[action.payload].active;
tabs.splice(action.payload, 1);
if (isCurrentActive && tabs.length !== 0) {
const newActive = action.payload === 0 ? 0 : action.payload - 1;
tabs[newActive].active = true;
}
if (tabs.length === 0) {
tabs.push({
id: Date.now(),
active: true,
tittle: 'Cute2'
})
}
},
addTab: (tabs) => {
tabs.forEach(tab => tab.active = false)
tabs.push({
id: Date.now(),
active: true,
tittle: 'Cute2'
})
},
}
})
As mentioned moveTab, selectTab and addTab work perfectly but when closeTab is executed,
the array is spliced (the tab is removed), but the active property of the state is not changed. And I am sure that at the end of the reducer the state is as I want it.
State should be changed from:
[{
id: 1,
active: false,
tittle: 'Cute',
},
{
id: 2,
active: true,
tittle: 'Cute',
}]
to
[{
id: 1,
active: true,
tittle: 'Cute',
}]
But in the component I am receiving this:
[{
id: 1,
active: false,
tittle: 'Cute',
}]
The array length is changed, but not the active property
false issue selectTab overriding closeTab

React state is not changing after the props change

I'm using redux with react, In my react state I'm assigning some state properties to the props but in my state the accounts are changing but the loading property doesn't change, I also tried to update the state in componentdidupdate but when I try to do that I get maximum update depth exceeded
In AllAccounts the accounts property is updating when the accounts are done fetching and the props change but the same doesn't happen for the loading property, It is set to the first value when it changes when it is set to true, The prop value is changing for loading but it is not changing in the state
This is my component
import React, { Component } from "react";
import { connect } from "react-redux";
import {
getAccounts,
deleteAccounts,
addAccount,
} from "../../../actions/accountActions";
import { getUsersForTenant } from "../../../actions/userActions";
import { Drawer } from "antd";
import getSortUrl from "../../../utils/getSortUrl";
import "../tablepage.css";
import Table from "../../../components/Table/Table";
import SearchHeader from "../../../components/SearchHeader/SearchHeader";
import Loading from "../../../components/Loading/Loading";
import AddAccount from "../../../components/AddEntities/AddAccount/AddAccount";
class AllAccounts extends Component {
constructor(props) {
super(props);
this.fetchData = this.fetchData.bind(this);
this.setSort = this.setSort.bind(this);
this.deleteRows = this.deleteRows.bind(this);
this.toggleDrawer = this.toggleDrawer.bind(this);
this.addAccount = this.addAccount.bind(this);
}
state = {
page: 0,
size: 50,
loading: this.props.loading,
accounts: this.props.accounts,
sort: undefined,
selectedItems: [],
checkboxActive: false,
drawerVisible: false,
columns: [
{
key: "name",
title: "Name",
enabled: true,
searchable: true,
type: "string",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: true,
},
{
key: "companyDomain",
title: "Company Domain",
enabled: true,
searchable: true,
type: "string",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "industrySector.name",
title: "Industry Sector",
enabled: true,
searchable: true,
type: "string",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "picture",
title: "Picture",
enabled: false,
searchable: false,
type: "picture",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "nbrEmployees",
title: "No. Employees",
enabled: true,
searchable: true,
type: "number",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "fixedPhone",
title: "Phone",
enabled: true,
searchable: true,
type: "string",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "source.name",
title: "Source",
enabled: false,
searchable: true,
type: "string",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "linkedIn",
title: "LinkedIn",
enabled: true,
searchable: false,
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "social",
title: "Social Networks",
title: "Social Networks",
enabled: true,
searchable: false,
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "twitterPage",
title: "Twitter",
title: "twitter",
enabled: false,
bindCol: "social",
bindColTitle: "Social",
searchable: false,
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "instagramPage",
title: "Instagram",
enabled: false,
searchable: false,
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "googlePlus",
title: "Google Plus",
enabled: false,
searchable: false,
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "user",
title: "User",
enabled: false,
searchable: false,
type: "string",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "isCustomer",
title: "Is Customer",
enabled: false,
searchable: false,
type: "boolean",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
{
key: "Tags",
title: "Tags",
enabled: true,
searchable: false,
type: "string",
selected: false,
sortActive: false,
sortOrder: "ASC",
showCheckbox: false,
},
],
};
toggleDrawer = () => {
this.setState({
drawerVisible: !this.state.drawerVisible,
});
};
deleteRows = (rows) => {
if (rows) {
this.props.deleteAccounts(rows, 1);
this.setState({
loading: true,
});
} else {
const { selectedItems } = this.state;
this.props.deleteAccounts(selectedItems, 1);
this.setState({
selectedItems: [],
loading: true,
});
}
};
addAccount = (accountData) => {
this.props.addAccount(accountData, 1);
};
componentDidUpdate(prevState, prevProps, snap) {
// if (prevProps.accounts != this.props.accounts) {
// this.setState({
// accounts: this.props.accounts,
// loading: false,
// });
// }
// if (prevProps.users != this.props.users) {
// this.setState({
// assignableUsers: this.props.users,
// });
// }
}
setSort = (column) => {
this.setState({
sort: column,
});
};
selectRow = (select, row) => {
if (select) {
this.setState({
selectedItems: [...this.state.selectedItems, row],
checkboxActive: true,
});
} else {
var { selectedItems } = this.state;
var index = 0;
for (var i = 0; i < selectedItems.length; i++) {
if (selectedItems[i].id == row.id) {
console.log(i);
index = i;
}
}
selectedItems.splice(index, 1);
if (selectedItems.length == 0) {
this.setState({
selectedItems: selectedItems,
checkboxActive: false,
});
} else {
this.setState({
selectedItems: selectedItems,
});
}
}
};
fetchData = () => {
this.setState(
{
loading: true,
},
() => {
const { page, size, sort } = this.state;
var sortUrl = "";
if (sort) {
const sortKey = sort.key;
const sortOrder = sort.sortOrder;
sortUrl = getSortUrl(sortKey, sortOrder);
}
this.props.getAccounts(page, size, sortUrl);
}
);
};
componentDidMount() {
this.fetchData(0, 50, undefined);
this.props.getUsersForTenant();
}
toggleEnableColumn = (index) => {
const { columns } = this.state;
var enableOccured = false;
columns[index].enabled = !columns[index].enabled;
columns.forEach((col) => {
if (!enableOccured && col.enabled) {
col.showCheckbox = true;
enableOccured = true;
} else {
col.showCheckbox = false;
}
});
this.setState({
columns: columns,
});
};
render() {
const {
size,
page,
accounts,
columns,
loading,
checkboxActive,
drawerVisible,
} = this.state;
return (
<div className="table-page-container">
<Loading loading={loading} />
<Drawer
title="Add New Account"
placement="right"
closable={true}
visible={drawerVisible}
onClose={this.toggleDrawer}
>
<AddAccount
toggleDrawer={this.toggleDrawer}
assignableUsers={this.props.users}
addAccount={this.addAccount}
/>
</Drawer>
<div className="breadcrumb-container"></div>
<div className="searchbar-container">
<SearchHeader
columns={columns}
checkboxActive={checkboxActive}
fetchData={this.fetchData}
deleteData={this.deleteRows}
toggleDrawer={this.toggleDrawer}
/>
</div>
<Table
size={size}
page={page}
data={accounts}
checkboxActive={checkboxActive}
module={"accounts"}
columns={columns}
toggleEnableColumn={this.toggleEnableColumn}
className="table-container"
selectRow={this.selectRow}
fetchData={this.fetchData}
setSort={this.setSort}
deleteData={this.deleteRows}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
accounts: state.account.accounts,
users: state.user.users,
loading: state.account.loading,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getAccounts: (page, size, sort) => dispatch(getAccounts(page, size, sort)),
addAccount: (accountData, accountType) =>
dispatch(addAccount(accountData, accountType)),
deleteAccounts: (rows, accountType) =>
dispatch(deleteAccounts(rows, accountType)),
getUsersForTenant: () => dispatch(getUsersForTenant()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(AllAccounts);
This is the reducer
import {
GET_ACCOUNT,
GET_ACCOUNTS,
GET_CUSTOMERS,
GET_PROSPECTS,
DELETE_ACCOUNT,
SET_ACCOUNTS_LOADING,
SET_ACCOUNTS_ERROR,
CLEAR_ERRORS,
} from "../actions/types";
const defaultState = {
accounts: [],
customers: [],
prospects: [],
loading: false,
errors: {},
};
const accountReducer = (state = defaultState, action) => {
switch (action.type) {
case GET_ACCOUNTS:
return {
...state,
accounts: action.payload,
errors: {},
};
case GET_CUSTOMERS:
return {
...state,
customers: action.payload,
errors: {},
};
case GET_PROSPECTS:
return {
...state,
prospects: action.payload,
errors: {},
};
case CLEAR_ERRORS:
return {
...state,
errors: {},
};
case SET_ACCOUNTS_LOADING:
return {
...state,
loading: action.payload,
};
case SET_ACCOUNTS_ERROR:
return {
...state,
errors: action.payload,
};
default:
return state;
}
};
export default accountReducer;
What you might need is the static getDerivedStateFromProps(props, state)
This method exists for rare use cases where the state depends on changes in props over time.
In order for your props to flow through your local state, you can write a logic inside that life-cycle method.
static getDerivedStateFromProps(props, state) {
if (!isEqual(props.accounts,state.accounts)) {
return {
accounts: props.accounts
};
}
return null;
}
}
Please take note that I am using isEqual from lodash/isEqual to deep check equality between state and props otherwise it will update everytime props flow in.
Reference: https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

Update state using nested map es6

My state has an array of objects that also contains an array of objects.
var state = {
prop1: null,
categories: [
{
categoryId: 1,
tags: [
{
tagId: 1,
name: 'AA11',
status: true,
},
{
tagId: 2,
name: 'AA22',
status: false,
}
]
},
{
categoryId: 2,
tags: [
{
tagId: 1,
name: 'BB11',
status: true,
},
{
tagId: 2,
name: 'BB22',
status: false, // let's say i want to toggle this
}
]
},
]
};
I have an action that will toggle a status of a tag. This action will receive parameters categoryId and tagId.
So far I've come up with this but it doesn't work
return {
...state,
categories: state.categories.map((category) => {
category.tags.map(tag => (
(tag.tagId === action.tagId && category.categoryId === action.categoryId) ? {
...tag,
status: !tag.status,
} : tag));
return category;
}),
};
I finally fixed the map code.
return {
...state,
categories: state.categories.map(category => ((category.id === action.categoryId) ?
{
...category,
tags: category.tags.map(tag => (tag.id === action.tagId ? {
...tag, status: !tag.status,
} : tag)),
} : category)),
};

How to update multiple element inside List with ImmutableJS?

Hi I am using immutableJS and I would want to update multiple objects in my array if it has the same id from action.contacts
const initialState = fromJS({
list: [{
id: 1,
loading: false,
}, {
id: 2,
loading: false,
}, {
id: 3,
loading: false,
}]
});
action.contacts = [{
id: 1
}, {
id: 2
}]
I expected when I call state.get('list') it would equal to
list: [{
id: 1,
loading: true,
}, {
id: 2,
loading: true,
}, {
id: 3,
loading: false,
}]
what I have done so far is this:
case UNLOCK_CONTACTS:
const indexesOfRow = state.get('list').findIndex((listItem) => {
return action.contacts.map((contact)=> listItem.get('id') === contact.id)
})
return indexesOfRow.map((index)=> {
state.setIn(['list', index, 'loading'], true);
});
}));
but it's not working out for me, didn't update anything
I created a similar solution in a fiddle http://jsfiddle.net/xxryan1234/djj6u8xL/398/
You are missing the point of immutable.js. The objects are not mutable.
const initialState = Immutable.fromJS({
list: [{
id: 1,
loading: false
}, {
id: 2,
loading: false
}, {
id: 3,
loading: false
}],
});
const contacts = [{
id: 1
}, {
id: 3
}]
let newState = initialState.set( 'list', initialState.get('list').map( (row,index) => {
let contact = contacts.find((item)=>{
return item.id == row.get('id')
})
if (contact){
return row.set('loading', true)
}
return row;
}))
console.log(newState.toJS())
see in the updated fiddle http://jsfiddle.net/djj6u8xL/399/
const newState = initialState.update('list', (oldValue) =>
oldValue.map(item =>
action.contacts.find(act =>
act.get('id') === item.get('id')) !== undefined ?
item.update('loading',(oldVal)=> !oldVal) : item))
console.log(newState.toJS())
notice: you need to turn action.contacts into immutable list of immutable Maps.
case UNLOCK_CONTACTS:
return state.set('list', state.get('list').map((listItem) => {
const matched = _.find(action.contacts, (contact) => listItem.get('id') === contact.id);
if (matched) {
return fromJS({ ...listItem.toJS(), loading: true });
}
return listItem;
}));
So I manage to solve it by mapping the list and then finding if the listItem exists in action.contacts. If it matches I return the matched object with loading: true and if not I return the same object.
I am open to suggestions how can I refactor this solution though, I am quite new to immutable js and I feel there is much more simpler way to solve this but I don't know yet.

Resources