I have an immutable object as my state in a redux reducer, and am trying to add/updates objects to a list.
Here's my reducer:
import { fromJS } from 'immutable'
const initialState = fromJS({
editable: true
})
export default (state = initialState, action) => {
switch(action.type) {
case 'CONTENT_MODE': {
return state.set('editable', action.editable ? false : true)
}
case 'UPDATE_CONTENT': {
return state.set(action.page, action.content)
// action.page = String
// action.content = {}
}
default: {
return state
}
}
}
I want to add objects to the page key, however if it currently exists update the values. I've tried updateIn and add() the callback, but I'm fairly new to immutable.js and am not sure how to approach it correctly.
The set() method rewrites the 'page' value entirely, whilst I need to push the value and only set if it exists, ending up with:
Example
const initialState = fromJS({
editable: true,
home: {
title: {
name: 'this is the name',
value: 'this is the value'
},
body: {
name: 'this is the name',
value: 'this is the value'
}
},
page1: {
title: {
name: 'this is the name',
value: 'this is the value'
},
body: {
name: 'this is the name',
value: 'this is the value'
}
}
})
If you know the full path of where you want to add something from your action, you could use SetIn with a keypath. Example, say you want to add a footer to page1:
const footer = {name: 'Footer', value: "I'm a footer!" }
return state.setIn(["page1", "footer"], fromJS(footer)
If you need to update things (ie say footer has name and value but then you're updating it to also have a style property) you could use mergeIn which is more like Object.assign:
const myNewData = {footer: {style: "myStyle"}}
console.log(state.mergeIn(["page1"], fromJS(myNewData);
--> { page1: { name: 'Footer'
, value: "I'm a footer!"
, style: 'myStyle'
}
}
If you merged in another object, it would add to the props of page1. If that object had a property with the same name, the value would be overwritten.
Related
I'm attempting to use the rest operator and restructuring to omit an entry in the object. Based on the documentation, rest should no longer include the key entry 575. After the operation, rest still has the same keys as state. I'm not sure what I'm doing wrong. Thanks in advance.
book = {
id: 575,
title: "Vector Calc"
};
state = {
removedBooks: {
46: {
id: 46,
title: "economics"
},
575: {
id: 575,
title: "Vector Calc"
}
}
};
const {
[book.id]: data, ...rest
} = state;
console.log(rest);
EDIT: I am using React and it is not recommended to mutate the state object directly. Why can't I directly modify a component's state, really? among others
The books are part of the removedBooks property, and are not direct children of the state. You need to destructure the removedBooks property as well.
const book = {"id":575,"title":"Vector Calc"};
const state = {"removedBooks":{"46":{"id":46,"title":"economics"},"575":{"id":575,"title":"Vector Calc"}}};
const { removedBooks: { [book.id]: data, ...removedBooks } } = state;
const newState = { ...state, removedBooks };
console.log(newState);
Your destructuring assignment expects a pattern of { 575: data, ...other... } but state actually has { removedBooks: { 575: data, ...other... } }. Add the removedBooks into your destructuring assignment and it works fine.
book = {
id: 575,
title: "Vector Calc"
};
state = {
removedBooks: {
46: {
id: 46,
title: "economics"
},
575: {
id: 575,
title: "Vector Calc"
}
}
};
const { removedBooks: {
[book.id]: data, ...rest
} } = state;
console.log(rest);
I'm trying to create new object with different properties name from Array.
Array is:
profiles: Array(1)
0:
column:
name: "profileName"
title: "Profile name"
status: "Active"
I want to create new function that return object with two properties:
id: 'profileName',
profileStatus: 'Active'
The function that I have create is returning only one property as undefined undefined=undefined.
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.reduce((obj, profile) => {
console.log('profiles', profile);
return ({
...obj,
id: profile.column.name,
profileStatus: profile.status,
});
}, {});
}
The function getProfile is taking as input array 'profiles' from outside,
I've just tested here and this seems to be working actually
const getProfile1 = (p) => p.reduce((obj, profile) =>({
...obj,
id: profile.column.name,
profileStatus: profile.status,
}), {});
You can use map as an alternative.
var profiles = [{"column":{"name": "profileName3","title": "3Profile name"},"status": "Active"},{"column":{"name": "profileName","title": "Profile name"},"status": "Active"}];
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.map(function(profile,v){
return {id:profile.column.name,profileStatus: profile.status};
});
}
console.log(getProfile(profiles));
Whenever I use reduce in this way, I usually index the final object by some sort of an id. As noted in another answer, you could use map in this situation as well. If you really want your final data structure to be an object, however, you could do something like this:
/**
* returns object indexed by profile id
*/
const formatProfiles = (profiles) => {
return profiles.reduce((obj, profile) => {
return {
...obj,
[profile.id]: {
id: profile.column.name,
profileStatus: profile.status,
}
};
}, {});
};
const profiles = [
{
id: 0,
status: 'active',
column: {
name: "profile_name_1",
title: "profile_title_1",
},
},
{
id: 1,
status: 'inactive',
column: {
name: "profile_name_2",
title: "profile_title_2",
}
}
];
const result = formatProfiles(profiles);
/**
* Result would look like this:
*/
// {
// '0': { id: 'profile_name_1', profileStatus: 'active' },
// '1': { id: 'profile_name_2', profileStatus: 'inactive' }
// }
I have two vue components: GetAnimal.vue and DisplayAnimal.vue. GetAnimal.vue send a JSON with animal data to DisplayAnimal.vue using router push. DisplayAnimal.vue displays that data. It works like this: I go to /getanimal, click a button that triggers the getAnimal() function which leads me to /viewanimal (via a router push):
GetAnimal.vue:
<script>
import axios from 'axios';
export default {
data: function () {
return {
name: 'defaultAnimal',
defaultanimal: {
name: 'Cat',
furColor: 'red',
population: '10000',
isExtinct: false,
isDomesticated: true
},
animal: String
}
},
methods: {
getAnimal: function () {
console.log("this.defaultanimal: " +
JSON.stringify(this.defaultanimal));
this.$router.push({
name: "viewanimal",
params: {
animal: this.defaultanimal
}
});
},
...
DisplayAnimal.vue:
<template>
<div>
<h1>Displaying animal:</h1>
<p>Animal name: {{animal.name}}}</p>
<p>Fur color: {{animal.furColor}}</p>
<p>Population: {{animal.population}}</p>
<p>Is extinct: {{animal.isExtinct}}</p>
<p>Is domesticated: {{animal.isDomesticated}}</p>
</div>
</template>
<script>
import axios from "axios";
export default {
props: {
animal: {
name: {
type: String
},
furColor: {
type: String
},
population: String,
isExtinct: String,
isDomesticated: String
}
},
name: "DisplayAnimal",
methods: {
},
created() {
console.log("animal param: " +
JSON.stringify(this.$route.params.animal));
this.animal = this.$route.params.animal;
}
};
</script>
The animal gets displayed just fine:
However I get the warning in console:
The this.animal = this.$route.params.animal; line that assigns the props explicitly is likely the cause of the warning.
However if I remove that line the animal doesn't get displayed at all:
I have this
router.js:
{
path: "/viewanimal",
name: "viewanimal",
component: () => import('./views/DisplayAnimal.vue'),
props: {animal: true}
},
{
path: "/getanimal",
name: "getanimal",
component: () => import('./views/GetAnimal.vue')
}
I thought setting props: {animal: true} would make sure it's autoassigned, but it doesn't seem to be the case. How should I fix it?
Well updating prop directly is an antipattern
It also doesnt make sense to have animal prop in DisplayAnimal component when you know it is not part of parent component which passes data to it. animal should be inside data so you can update in created callback.
Example
data() {
return {
loading: true, // perhaps you'd like to show loader before data gets fetched
animal: {
id: -1,
name: '',
furColor: '',
population: 0,
isExtinct: false,
isDomesticated: false
}
}
},
created() {
this.animal = this.$route.params.animal;
this.loading = false;
}
Hi Im having troubles setting the state when I press a button 'Send' on one email input.
I'm trying it avoiding mutation as React docs recommends.
My state properties are this:
state = {
emailForm: {
email: {
elementType: 'email-invitation-input',
elementConfig: {
type: 'email',
placeholder: 'Enter an email..',
},
value: '',
valid: true,
required: true
}
},
requestStatus : false,
validationMessage : null,
formIsValid: false,
}
So I tried three ways to set empty value to my email input trough the state but no one worked :(
First try:
I used ES6 spread operator to change it value but it doesn't change the input value:
this.setState({
email: {
...this.state.emailForm.email,
value: '',
},
});
this.setState({
email: Object.assign({}, this.state.emailForm.email, {
value: '',
}),
});
Another try using immutability-helper package
import update from 'immutability-helper';
let newData = { email: {
...this.state.emailForm.email,
value: '',
}, };
this.setState({
email: update(this.state.newData, {
value: {$set: newData},
})
});
Second try:
I used Ramda.js but it neither works.
setObjectByPath(fieldPath, value) {
this.setState({
emailForm: R.set(R.lensPath(fieldPath), value, this.state.emailForm)
})
}
setObjectByPath(this.state.emailForm.email,'');
Third try:
I used react-addons-update:
import update from 'react-addons-update';
this.setState({
email: update(this.state.newData, {
value: {$set: newData},
})
});
All tries does nothing or it creates a new email input with empty value below.
Thanks beforehand
this.setState(prevState => ({
emailForm: {
email: {
...prevState.emailForm.email,
value: ''
}
}
}));
I'm creating a Page Builder with React.
I have a component which contains the structure of the page.
var LayoutPage = React.createClass({
getInitialState: function getInitialState() {
return {
items: {
'78919613':{
id: '78919613',
component : 'OneColumn',
cols:{
'565920458':{
id: '565920458',
content:{
'788062489':{
id: '788062489',
component : 'Text',
params: 'Lorem ipsum'
},
'640002213':{
id: '640002213',
component : 'Text',
params: 'Lorem ipsum'
}
}
}
}
}
}
};
},
.....
});
I have a system with drag'n drop to put a new element on the page and it works. But when the new element is dropped I want to update the state to add a new item in the array.
So how can I push a new item ? I did a test with that :
this.state.items.push({.....});
But I have an error :
TypeError: this.state.items.push is not a function
Can you help me ?
Thank you.
Instead of using an object in your state you should change it to the array like below :
this.state = {
items: [ // items array
{
id: 1,
name: "Stack"
},
{
id: 2,
name: "Overflow"
}],
count: 3, // another state
textValue : '' // and this is state too
}
Where the items it's an array of objects. And then you will be able to add new items to an array.
const newItem = {
id : this.state.count,
name: this.state.textValue
};
const newArr = this.state.items.concat(newItem);
this.setState({
items: newArr,
textValue: '',
count: this.state.count + 1
})
The whole example is here.
I hope it will help you!
Thanks
You'll start to get headaches if you directly mutate the state of your application. If you forget to call this.setState then it won't re-render!
Assuming you can't use an array (which would be easier), then you'll have to generate a unique key if you want to add another item to the object.
// create a new copy of items based on the current state
var newItems = Object.assign({}, this.state.items),
newItem = { id: '', component: '', cols: {} },
uniqueId = generateUniqueId();
// safely mutate the copy
newItems[uniqueId] = newItem;
// update the items property in state
this.setState({ items: newItems });
This is even easier with ES7/Babel.
const newItem = { id: '', component: '', cols: {} },
uniqueId = generateUniqueId(),
items = { [uniqueId]: newItem, ...this.state.items };
this.setState({ items });
You can generate a similar unique ID to the one you have there using Math.random.
function generateUniqueId() {
// removing leading '0.' from number
return Math.random()
.toString()
.slice(3);
}