update an array nested in an object in an immutable way - reactjs

So I have this reducer that I would like to update
import { fromJS } from 'immutable';
const initialState = fromJS({
searchParams: {
limit: 100,
page: 1,
order_by: 'name',
filters: {
and_keywords : []
}
},
})
when the action is triggered I would like to push an object into the array of and_keywords. What I have done so far is this
case ADD_KEYWORDS:
return state
.setIn(['searchParams', 'filters', 'and_keywords'], '123');
I also tried
case ADD_KEYWORDS:
return state
.updateIn(['searchParams', 'filters', 'and_keywords'], arr => arr.push('123'))
basing from the documents in https://facebook.github.io, but I can't seem to make it work. No changes have been made after executing this command

Your updateIn version should work:
const Immutable = require('immutable')
const initialState = Immutable.fromJS({
searchParams: {
limit: 100,
page: 1,
order_by: 'name',
filters: {
and_keywords : []
}
},
})
const newState = initialState.updateIn(['searchParams', 'filters', 'and_keywords'], arr => arr.push('123'))
console.log(newState)
Your setIn version code should work as well with a small modification:
const Immutable = require('immutable')
const initialState = Immutable.fromJS({
searchParams: {
limit: 100,
page: 1,
order_by: 'name',
filters: {
and_keywords : []
}
},
})
const newState = initialState.setIn(['searchParams', 'filters', 'and_keywords'], initialState.getIn(['searchParams', 'filters', 'and_keywords']).push('123'))
console.log(newState)
Both should output:
Map { "searchParams": Map { "limit": 100, "page": 1, "order_by": "name", "filters": Map { "and_keywords": List [ "123" ] } } }

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.

Why can't I push in <option> when I get the 'response.data'?

Why can't I push in my <option> when I get the response.data?
type State = {
companyManagerMap: null | Map<string, string[]>
}
useEffect(() => {
AdminListManager()
.then((response) => {
const { data } = response.data
console.log( { data });
setState((s) => ({
...s,
companyManagerMap: new Map(
Object.keys(data).map((key) => [key, data[key]])
),
}))
})
.catch(showUnexpectedError)
}, [showUnexpectedError])
data format
{"total":2,"data":[{"id":1,"name":"newspeed","contains_fields":[{"id":1,"name":"Official"}]},{"id":2,"name":"YAMAHA","contains_fields":[{"id":3,"name":"US"}]}]}
You are using your .map and Object.keys wrong
Look here at where you iterate over your Object keys properly :)
const data = {
total: 2,
data: [
{ id: 1, name: 'newspeed', contains_fields: [{ id: 1, name: 'Official' }] },
{ id: 2, name: 'YAMAHA', contains_fields: [{ id: 3, name: 'US' }] },
],
};
//now iterate over it properly
data.data.map((item) => {
Object.keys(item).map((key) => {
console.log(item[key]);
});
});
console.log will return this output
1
newspeed
[ { id: 1, name: 'Official' } ]
2
YAMAHA
[ { id: 3, name: 'US' } ]
I'm guessing you want to add the new data from your res.data to a state
So you can do something like this:
const fetchData = async () => {
try {
const res = await AdminListManager()
//do data manipulation over objects and set new state
} catch (error) {
showUnexpectedError()
}
}
useEffect(()=> {
fetchData()
}, [showUnexpectedError])

How to update existing object

I'm trying to update current object within an array with new property using state hooks. The array with object looks like this:
const myData = [
{
dataLabels: [
{
align: 'left'
}
],
name: 'my data',
data: [
{
y: 1,
name: 'Daryl'
},
{
y: 2,
name: 'Negan'
}
]
}
];
and I wan't to add color property to data objects inside useState hook. This is what I've tried so far:
const [ newMyData ] = useState({
...myData,
0: {
...myData[0],
data: myData[0].data.map((item, index) => ({ ...item, color: getChartColors()[index] }))
},
});
but the problem is that newMyData is now turned into an object instead of keep being an array. What am I doing wrong and how should I solve my problem? Thanks in advance
You are passing an object as the initial state:
const [ newMyData ] = useState([ /* <--- use '[' not '{' */
...myData,
0: {
...myData[0],
data: myData[0].data.map((item, index) => ({ ...item, color: getChartColors()[index] }))
},
] /* <--- same here - use ']' not '}' */ );
UPDATE:
Based on what you asked in the comments:
const myData = [
{
dataLabels: [
{
align: 'left'
}
],
name: 'my data',
data: [
{
y: 1,
name: 'Daryl'
},
{
y: 2,
name: 'Negan'
}
]
}
];
const myObject = myData[0];
const nextObject = {
...myObject,
data: myObject.data.map((item, index) => ({ ...item, color: getChartColors()[index] }))
}
const [myData, setMyData] = useState([ nextObject ]); /* If you still want this to be an array */
/* OR */
const [myData, setMyData] = useState( nextObject ); /* If you want it to be an object instead */
Hi you can follow this example to include new property in array using useState hooks.
import React, {useState} from "react";
export default function UseStateExample() {
const [myData, setMyData] = useState([
{
dataLabels: [
{align: 'left'}
],
name: 'my data',
data: [
{y: 1, name: 'Daryl'},
{y: 2, name: 'Negan'}
]
}
]);
function getChartColors() {
return ["red", "green", "blue"]
}
function clickHandler(event) {
let items = [];
myData[0].data.map((item, index) => {
item.color = getChartColors()[index];
items.push(item);
});
setMyData([
...myData
]);
console.log(myData)
}
return (
<div>
<button onClick={clickHandler}>Update myData and show in Console</button>
</div>
);
}

State Not Changing Properly In React Redux

I am new to React Redux I have initialized state which is properly displaying by calling its respective reducer which job to return the initialize State. But after mutating the state the state most probably not changing and i cant figure it out why
import { combineReducers } from 'redux';
const initialState = {
songs:[
{title:'No Scrubs', duration: '4:05'},
]
}
const songReducers = (state = initialState)=>{
return state
}
const selectedSongReducer =(selectedSong=null,action)=>{
if(action.type==='SONG_SELECTED'){
return action.payload
}
return selectedSong;
}
const addSongReducer = (state=initialState,action)=>{
if(action.type==="ADD_SONG"){
return {
...state,
songs:[
...state.songs, {title:'All demo', duration: '4:05'}
]
}
}
return state;
}
export default combineReducers({
songs: songReducers,
selectedSong: selectedSongReducer,
addSong:addSongReducer
})
Your overall state shape will look like
state = {
songs: {
songs: [
{ title: 'No Scrubs', duration: '4:05' },
]
},
selectedSong: null,
addSong: {
songs: [
{ title: 'No Scrubs', duration: '4:05' },
]
}
}
after the very first reducer call. Is this what you want? Asking because you have
state = {
songs: {
songs: [
{ title: 'No Scrubs', duration: '4:05' },
]
},
...
}
instead of just
state = {
songs: [
{ title: 'No Scrubs', duration: '4:05' },
]
...
}

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