Mutate one value in redux toolkit - reactjs

i canĀ“t wrap my head around changing one single value in my redux store.
I am story data in my redux store with writeItems reducer.
{
"val1": true,
"val2": false,
"val3": false,
"val4": true, <-
"val5": true,
"val6": false,
}
export const itemsSlice = createSlice({
name: "items",
initialState,
reducers: {
writeItems: (state, action) => {
return { ...state, items: [...action.payload] };
},
updateItem: (state, action) => {
return {...state, ...action.payload}
},
},
});
Now I am trying to mutate a single value in this object (e.g val4) with the updateItem reducer but it creates only a new object with only the new property in my store.
dispatch(updateItem([{ val4: false }]));
{
"val4": false,
}
How is it possible to get this object?
{
"val1": true,
"val2": false,
"val3": false,
"val4": false, <-
"val5": true,
"val6": false,
}

Related

Redux How to insert new Sub data in state

I'm making a todo app and using redux for state management. My todo state is made up of nested arrays.
const initialState = {
todos: [
{
id: 1,
name: "task1",
subdata: [
{
id: 101,
name: "subtask1",
complete: false,
},
{
id: 102,
name: "subtask2",
complete: true,
},
],
},
{
id: 2,
name: "task2",
subdata: [
{
id: 103,
name: "subtask3",
complete: false,
},
{
id: 104,
name: "subtask4",
complete: true,
},
],
},
Reducers:
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
const newTodo = state.todos.concat(action.payload);
return { ...state, todos: newTodo };
case ADD_SUBTODO:
const newSubtodo = action.payload;
?????????????????????????????????????????????
How can i append new subtodo to initialstate?
I used the immer library, but I want to do it the traditional way, for example the spread operator. I would be glad if you help.
You could do something like...
// send payload as {id:1,newSubtodo: newSubtodo}
case ADD_SUBTODO:
const newSubtodo = action.payload.newSubtodo;
//id is the task/todo id of which you want to add a new subdata
const newTask = initialState.todos.find(i=>i.id==action.payload.id)
//id is the task/todo id of which you want to add a new subdata
newTask.subdata.push(newSubtodo)
return {...initialState,todos:[...initialState.todos,newTask]}
Note: Using nested objects as state in React Js is not a good
practice.

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

I fetched an array and want to add it to my state

I fetched an array of objects but can't add (or replace) them to state.
const searchSlice = createSlice({
name: 'search',
initialState: [],
reducers: {
getResults(state, action) {
state = action.payload;
},
},
});
I tried "state.push(action.payload)" but it turned out a nested array. "action.payload" is the right response.
Since pushing it into the state creates a nested array,
For replacing, try
const searchSlice = createSlice({
name: 'search',
initialState: [],
reducers: {
getResults(state, action) {
return [...action.payload];
},
},
});
For adding, try
const searchSlice = createSlice({
name: 'search',
initialState: [],
reducers: {
getResults(state, action) {
return [...state, ...action.payload];
},
},
});

Update single value in reducer object without deleting prior data

My goal is to update the pause value in my ruducer's object without deleting any of the other data.
Here is my ruducer code:
export const initialState = {
audio: {
sound: false,
index: 0,
name: '',
image: '',
username: '',
pause: false,
},
};
And here is it's case:
case 'TRIGGER_AUDIO':
return {
...state,
audio: action.payload,
};
Here is how I access the audio values:
const [{ audio }, dispatch] = useStateValue();
And this is how I'm trying to update it when I press a button:
<TouchableOpacity
onPress={() =>
dispatch({
type: 'TRIGGER_AUDIO',
payload: {
...state.audio,
pause: true,
},
})
}>
If I understood correctly you need something like this
case 'TRIGGER_AUDIO':
const audio = {...state.audio, ...action.payload};
return {
...state,
audio
};

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

Resources