Object.assign does not update my state (React) - javascript

I have an app which has a few users. I would now like to be able to create a new user. So I have created this actionCreator:
export const createUser = (first, last) => {
console.log("You are about to create user: XX ");
return {
type: 'USER_CREATE',
first: first,
last: last,
payload: null
}
};
I am dealing only with first & last names for now. The actionCreator gets its parameters from the container. There is a button which calls the actionCreator like so:
<button onClick={() =>this.props.createUser(this.state.inputTextFirstName, this.state.inputTextLastName)}>Submit</button>
My UserReducer looks like this:
/*
* The users reducer will always return an array of users no matter what
* You need to return something, so if there are no users then just return an empty array
* */
export default function (state = null, action) {
if(state==null)
{
state = [
{
id: 1,
first: "Bucky",
last: "Roberts",
age: 71,
description: "Bucky is a React developer and YouTuber",
thumbnail: "http://i.imgur.com/7yUvePI.jpg"
},
{
id: 2,
first: "Joby",
last: "Wasilenko",
age: 27,
description: "Joby loves the Packers, cheese, and turtles.",
thumbnail: "http://i.imgur.com/52xRlm8.png"
},
{
id: 3,
first: "Madison",
last: "Williams",
age: 24,
description: "Madi likes her dog but it is really annoying.",
thumbnail: "http://i.imgur.com/4EMtxHB.png"
}
]
}
switch (action.type) {
case 'USER_DELETED':
return state.filter(user => user.id !== action.userIdToDelete);
case 'USER_CREATE':
console.log("Action first:" + action.first);
console.log("Action last:" + action.last);
Object.assign({}, state, {
id: 4,
first: action.first,
last: action.last,
age: 24,
description: "Some new Text",
thumbnail: "http://i.imgur.com/4EMtxHB.png"
});
return state;
}
return state;
}
Now I have a few questions.
1) Is this the proper way to do this, or am I writing bad code somewhere? Keep in mind that I am trying to use Redux here, I am not entirely sure though whether I am not sometimes falling back into React without Redux
2) Am I doing the state thing correctly? I initially used a tutorial and am now building upon that, but I am not sure why state seems to be an array:
state = [ <--- Does this mean that my state is an array?
{
id: 1,
// and so on ...
I am very confused by this, since in other Tutorials state is just an object containing other smaller objects and its all done with parentheses { }
3) What would be the best way to create a new user. My Object.assign does not work, it does not update anything, and I am not sure where the mistake lies.
4) And, relatedly, how could I update one individual user or a property of one individual user?

As Flyer53 states you need to set the state to the return value of Object.assign() as this is designed to not mutate state it will not change the value of the state you're passing in.
The code's fine; I'd tend to use just one property on the action in addition to its type, so have a property of (say) user that is an object containing all the user data (first name, last name etc).
I believe it's quite idiomatic to define a default state outside of the reducer and then set this as the default value for the state parameter in the reducer function:
export default function (state = initialState, action) {
For a brilliant introduction by its creator, see https://egghead.io/courses/getting-started-with-redux
State can be any shape you like. As an application grows in complexity it will usually be represented as an object composed of different sections of data. So, for example, in your case in could be comprised of an array of users and, say, an 'order by' that could apply to some UI state):
{ users: [], orderBy: 'lastName' }
If you carry on using an array of users as the state then you can use the ES6 spread operator to append the new user, for example:
newState = [ ...state, action.user ];
whereas if you move to using an object for state, the following would similarly append a user:
newState = Object.assign({}, state, { users: [ ...state.users, action.user ] };
Finally, to update a single user you could just use map against the array of users as follows (this is obviously hardcoded, but you could match, say, on id and update the appropriate properties).
let modifiedUsers = state.users.map((user) => {
if (user.id === 3) {
user.name = user.name + '*';
}
return user;
});
let newState = Object.assign({}, state, { users: modifiedUsers });

There's maybe an easier way to log the state(users) in an object (not in an array as in your example code above) that works without Object.assign() which is supposed to work with objects, not arrays:
var state = {
user1: {
id: 1,
first: "Bucky",
last: "Roberts",
age: 71,
description: "Bucky is a React developer and YouTuber",
thumbnail: "http://i.imgur.com/7yUvePI.jpg"
}
};
state['user' + 2] = {
id: 2,
first: "Joby",
last: "Wasilenko",
age: 27,
description: "Joby loves the Packers, cheese, and turtles.",
thumbnail: "http://i.imgur.com/52xRlm8.png"
};
console.log(state);
console.log(state.user2);
Just an idea ...

Related

What do you suggest to make undo?

I use a series of data as follows:
[
{
name: 'name1',
background:'red',
child:[
{
name:'',
id:'',
color:'',
text:'',
border:''
},
{
name:'',
id:'',
color:'',
text:'',
border:''
}
]
},
{
name: 'name2',
background:'red',
child:[
{
name:'',
id:'',
color:'',
text:'',
border:''
},
{
name:'',
id:'',
color:'',
text:'',
border:''
}
]
}
]
I'm going to save all the changes to another variable, and I used a deep copy to do that, but when I log in, the variables are the same.I need to children all the children changes too.
I wrote it in Reducers
const Reducers =(state = initialState, action) => {
switch (action.type) {
case NEW_OBJECTS_PAST:
const OldPast = JSON.parse(JSON.stringify(state.past))
const newDate = JSON.parse(JSON.stringify(state.present))
// const newDate = _.cloneDeep(state.present);
const newPast = [
OldPast,
newDate
];
return {
...state,
past : _.cloneDeep(newPast) ,
}
case OBJECTS:
return {
...state,
present: action.objects,
// future:[]
}
Do you suggest another way to build undo in REACT and REDUX ?
I tried the libraries available for this, but got no answer.
First two remarks :
you should never deep clone parts of your state, it doesn't bring you any benefits as the state is immutable anyway, but is very detrimental for memory usage and for performance (both when doing the deep cloning and then when using the deep cloned state),
you should use Redux toolkit, which makes it way easier to write immutable reducers and prevent many errors.
To implement undo, I'm not sure what your actions are supposed to mean but you can do it as follows
the state contains state.present (the current state) and state.past (an array of past states)
whenever you do an action that you want to undo, you push the current state.present at the end of state.past and compute the new state.present
whenever you want to undo, you pop the last element of state.past and put it in state.present.
In your code I can't see any undo action, and you're also building nested arrays because of new Past = [oldPast, new Date], you most likely meant to spread oldPast.

updating a property in an array of object immutably using react hooks

I'm trying to update an array property in an array of objects using react hooks, as we all know when we need to update the state based on a previous state we use the function set"MypopertyName"
as shown below:
My code:
const [MenuList, setMenuList] = useState([
{
id: 0,
title: "sushi",
desc: "finest fish and vegies",
price: "$22.99",
amount: 1,
},
{
id: 1,
title: "shneitzel",
desc: "a german specialty",
price: "$50.99",
amount: 1,
},
{
id: 2,
title: "pizza",
desc: "an italian specialty",
price: "$100.99",
amount: 1,
},
{
id: 3,
title: "pasta",
desc: "an italian specialty",
price: "$200.99",
amount: 1,
},
]);
my problem that I wanna change the amount of the meal based on clicks of the user so here is what I tried:
const onClickHandler = (id) => {
for (const key in MenuList) {
if (MenuList.id === id) {
return (MenuList[key].amount = MenuList[key].amount + 1);
}
}
};
I know that the reactivity system of react only works when I call setMenuList() I tried that but it doesn't work besides I wanna update the specific object immutably so I need to use the function syntax like this:
setMenuList((prevMenuList)=>{
//my code
})
also, I didn't manage to get it work, sorry guys I'm one of the old gurus that used the class-based components for a long time you know with the state={} and so on the whole new hooks concept is new for me.
So any help would be appreciated and thank you in advance.
The use of the function-based setter
setMenuList((prevMenuList)=>{
is useful when the function may be called when the menuList currently in its scope is not what's currently being rendered - for example, for when the function is called after an API call finishes. This is completely different from React's need to not mutate state, which requires another approach entirely.
Map the state array, and when the ID matches, return a new object with the amount incremented.
const onClickHandler = (id) => {
setMenuList(
menuList.map(
obj => obj.id !== id ? obj : ({
...obj,
amount: obj.amount + 1
})
)
);
};

React set state to nested object value

I need to set state on nested object value that changes dynamically Im not sure how this can be done, this is what Ive tried.
const [userRoles] = useState(null);
const { isLoading, user, error } = useAuth0();
useEffect(() => {
console.log(user);
// const i = Object.values(user).map(value => value.roles);
// ^ this line gives me an react error boundary error
}, [user]);
// This is the provider
<UserProvider
id="1"
email={user?.email}
roles={userRoles}
>
The user object looks like this:
{
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
I need to grab the roles value but its parent, "website.com" changes everytime I call the api so i need to find a way to search for the roles.
I think you need to modify the shape of your object. I find it strange that some keys seem to be fixed, but one seems to be variable. Dynamic keys can be very useful, but this doesn't seem like the right place to use them. I suggest that you change the shape of the user object to something like this:
{
name: "GGG",
site: {
url: "website.com",
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
In your particular use case, fixed keys will save you lots and lots of headaches.
You can search the values for an element with key roles, and if found, return the roles value, otherwise undefined will be returned.
Object.values(user).find(el => el.roles)?.roles;
Note: I totally agree with others that you should seek to normalize your data to not use any dynamically generated property keys.
const user1 = {
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
const user2 = {
name: "GGG",
friends: {},
otherData: {}
}
const roles1 = Object.values(user1).find(el => el.roles)?.roles;
const roles2 = Object.values(user2).find(el => el.roles)?.roles;
console.log(roles1); // ["SuperUser"]
console.log(roles2); // undefined
I would recommend what others have said about not having a dynamic key in your data object.
For updating complex object states I know if you are using React Hooks you can use the spread operator and basically clone the state and update it with an updated version. React hooks: How do I update state on a nested object with useState()?

How do you use `reselect` to memoize an array?

Suppose I have a redux store with this state structure:
{
items: {
"id1" : {
foo: "foo1",
bar: "bar1"
},
"id2": {
foo: "foo2",
bar: "bar2"
}
}
}
This store evolves by receiving full new values of items:
const reduceItems = function(items = {}, action) {
if (action.type === 'RECEIVE_ITEM') {
return {
...items,
[action.payload.id]: action.payload,
};
}
return items;
};
I want to display a Root view that renders a list of SubItem views, that only extract a part of the state.
For example the SubItem view only cares about the foos, and should get it:
function SubItem({ id, foo }) {
return <div key={id}>{foo}</div>
}
Since I only care about "subpart" of the states, that's what I want to pass to a "dumb" Root view:
const Root = function({ subitems }) {
// subitems[0] => { id: 'id1', foo: "foo1" }
// subitems[1] => { id; 'id2', foo : "foo2" }
const children = subitems.map(SubItem);
return <div>{children}</div>;
};
I can easily connect this component to subscribe to changes in the state:
function mapStatesToProps(state) {
return {
subitems: xxxSelectSubItems(state)
}
}
return connect(mapStatesToProps)(Root)
My fundamental problem is what happens when the part of the state that I don't care about (bar) changes.
Or even, when I receive a new value of an item, where neither foo nor bar has changed:
setInterval(() => {
store.dispatch({
type: 'RECEIVE_ITEM',
payload: {
id: 'id1',
foo: 'foo1',
bar: 'bar1',
},
});
}, 1000);
If I use the "naive" selector implementation:
// naive version
function toSubItem(id, item) {
const foo = item.foo;
return { id, foo };
}
function dumbSelectSubItems(state) {
const ids = Object.keys(state.items);
return ids.map(id => {
const item = state.items[id];
return toSubItem(id, item);
});
}
Then the list is a completely new object at every called, and my component gets rendered everytime, for nothing.
Of course, if I use a 'constant' selector, that always return the same list, since the connected component is pure, it is re-renderered (but that's just to illustrate connected components are pure):
// fully pure implementation
const SUBITEMS = [
{
id: 'id0',
foo: 'foo0',
},
];
function constSelectSubItems(state) {
return SUBITEMS;
}
Now this gets a bit tricky if I use an "almostConst" version where the List changes, but contains the same element.
const SUBITEM = {
id: 'id0',
foo: 'foo0',
};
function almostConstSelectSubItems(state) {
return [SUBITEM];
}
Now, predictably, since the list is different, even though the item inside is the same, the component gets rerendered every second.
This is where I though 'reselect' could help, but I'm wondering if I am not missing the point entirely. I can get reselect to behave using this:
const reselectSelectIds = (state, props) => Object.keys(state.items);
const reselectSelectItems = (state, props) => state.items;
const reselectSelectSubItems = createSelector([reSelectIds, reSelectItems], (ids, items) => {
return ids.map(id => toSubItem(id, items));
});
But then it behaves exactly like the naive version.
So:
is it pointless to try to memoize an array ?
can reselect handle this ?
should I change the organisation of the state ?
should I just implement shouldComponentUpdate on the Root, using a "deepEqual" test ?
should I give up on Root being a connected component, and make each LeafItems be connected components themselves ?
could immutable.js help ?
is it actually not an issue, because React is smart and will not repaint anything once the virtual-dom is computed ?
It's possible what I'm trying to do his meaningless, and hides an issue in my redux store, so feel free to state obvious errors.
You're definitely right about the new array references causing re-renders, and sort of on the right track with your selectors, but you do need to change your approach some.
Rather than having a selector that immediately returns Object.keys(state.item), you need to deal with the object itself:
const selectItems = state => state.items;
const selectSubItems = createSelector(
selectItems,
(items) => {
const ids = Object.keys(items);
return ids.map(id => toSubItem(id, items));
}
);
That way, the array will only get recalculated when the state.items object is replaced.
Beyond that, yes, you may also want to look at connecting your individual list item components so that each one looks up its own data by ID. See my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance for examples. I also have a bunch of related articles in the Redux Techniques#Selectors and Normalization and Performance#Redux Performance sections of my React/Redux links list.

How do I turn part of Redux store into an array

Okay, here's the pickle that I'm in, one of my actions in actions/index.js is:
export function requestPeople() {
return (dispatch, getState) => {
dispatch({
type: REQUEST_PEOPLE,
})
const persistedState = loadState() // just loading from localStorage for now
console.log(persistedState)
//Object.keys(persistedState).forEach(function(i) {
//var attribute = i.getAttribute('id')
//console.log('test', i,': ', persistedState[i])
//myArr.push(persistedState[i])
//})
//console.log(myArr)
//dispatch(receivePeople(persistedState)) // I'm stuck here
}
}
and when I console.log(persistedState) from above in the Chrome console I get my people state exactly like this.
Object {people: Object}
Then when I drill down into people: Object above, I get them like this:
abc123: Object
abc124: Object
abc125: Object
and when I drill down into each one of these puppies (by clicking on the little triangle in Chrome console) I get each like this:
abc123: Object
firstName: 'Gus'
id: 'abc123'
lastName: 'McCrae'
// when I drill down into the second one I get
abc124: Object
firstName: 'Woodrow'
id: 'abc124'
lastName: 'Call'
Now, here's where I'm stuck.
The table I'm using is Allen Fang's react-bootstrap-table which only accepts array's, and it get's called like this <BootstrapTable data={people} /> so my above data needs to be converted to an array like this:
const people = [{
id: abc123,
firstName: 'Gus',
lastName: 'McCrae'
}, {
id: abc124,
firstName: 'Woodrow',
lastName: 'Call'
}, {
...
}]
// and eventually I'll call <BootstrapTable data={people} />
My question specifically is how do I convert my people state shown above into this necessary array? In my action/index.js file I've tried: Object.keys(everything!!!)
And lastly, once I have the array, what's the best way to pass that array into <BootstrapTable data={here} /> using state, a variable, a dispatched action, something I've never heard of yet?
Any help will be very much appreciated!! FYI, this is my first question in Stack Overflow, feels nostalgic. I'm a full-time police officer, and trying learn to code on the side. Thanks again!
UPDATE:
Thanks to a suggestion by Piotr Berebecki, I'm tying it this way:
export function requestPeople() {
return (dispatch, getState) => {
dispatch({
type: REQUEST_PEOPLE,
})
const persistedState = loadState()
console.log('persistedState:', persistedState)
const peopleArr = Object.keys(persistedState.people).map(function(key) {
return persistedState[key]
})
console.log(JSON.stringify(peopleArr))
//dispatch(receivePeople(persistedState))
}
}
and getting [null,null,null]
like this:
Welcome to Stack Overflow :)
To convert your nested persistedState.people object to an array you can first establish an interim array of keys using Object.keys(persistedState.people) and then map() over the keys to replace each key with an object found in your original nested object - persistedState.people - at that key. You can assign the resultant array to a variable which you can then pass to the BootstrapTable. Check the code below and a demo here: http://codepen.io/PiotrBerebecki/pen/yaXrVJ
const persistedState = {
people: {
'abc123' : {
id:'abc123',firstName: 'Gus', lastName: 'McCrae'
},
'abc124' : {
id:'abc124',firstName: 'Woodrow', lastName: 'Call'
},
'abc125' : {
id:'abc125',firstName: 'Jake', lastName: 'Spoon'
}
}
}
const peopleArr = Object.keys(persistedState.people).map(function(key) {
return persistedState.people[key];
});
console.log(JSON.stringify(peopleArr));
/*
Logs the following array:
[
{"id":"abc123","firstName":"Gus","lastName":"McCrae"},
{"id":"abc124","firstName":"Woodrow","lastName":"Call"},
{"id":"abc125","firstName":"Jake","lastName":"Spoon"}
]
*/

Categories