I have built a basic shopping cart simulator. All it does is add and remove items between two different basket (i.e. aisle vs basket).
While working on the problem I started to think about the approach and would love to hear what people think.
- Avoid initializing the state empty state in the constructor. However, I had to initialize
shoppingCart: []
incomponentDidMount
hook to avoid type error. What is the better and cleaner approach? - Is it good practice to keep the logic simple in
handleXXXClicks
, separation of concern one for handlingitems
and one forshoppingCart
? - I feel
aisleList
andcartList
bloated and I could have writtenitemComponent
which is avoidable duplicate code. - Is there any better way to handle the item in
setState
? I couldn't think of usingprevState
in my scenario.
Use setState
without initializing state
:
componentDidMount() {
// assuming data comes from API fetch
let items = ["banana", "eggs", "bread", "apple"];
this.setState({
items,
shoppingCart: [] // had to set this empty to avoid typeerror
});
}
Handle handleClick
and setState
:
handleCartClick = event => {
let newList = this.state.shoppingCart.filter(item => {
return item !== event.target.value;
});
this.setState({
items: [...this.state.items, event.target.value],
shoppingCart: [...newList]
});
};
Gist link
Demo link
1 Answer 1
Some Tips
- Avoid using constructor.
- Avoid using
.bind(this)
- Destructuring will help you to make cleaner code.
Questions
- Avoid initializing the state empty state in the constructor. However, I had to initialize shoppingCart: [] in componentDidMount hook to avoid type error. What is the better and cleaner approach?
You can have 2 aproaches to this
There is a way to avoid this, what you do is destruct the state in the variables you want and set then a default value of an array.
state = {} render() { const { shoppingCart = [] } = this.state ... }
This way you don't need to check if
shoppingCart
is an array, and you only need to declarethis.state
as an object ( it can be just an empty object ).You can also do
render() { const { shoppingCart = [] } = this.state || {} ... }
And now you don't even need
state = {}
Because you are using arrow functions, you don't need
.bind(this)
and also you can avoid de constructor and just usestate = { ... }
This way you can declare
shoppingCart
anditems
and don't need to declare it incomponentDidMount
(wich is much better to understand) and now don't need to destruct the state.state = { shoppingCart = [], items = [] }
This way is good if the data you will get is always an array (or the type you specify) and another good reason to use this is that when other programmer look at the code, they will immediately know what is stored in that component state, but when you have too many things in the state, it can get confused.
This aproaches will solve some problems like using ternary to check if you have an state and then return the mapping or if you don't have, return an empty array.
render() {
const {
items = [],
shoppingCart = []
} = this.state
let aisleList = items.map((item, index) => {
return (
<div key={index}>
<span>{item}</span>
<input
type="checkbox"
id={index}
value={item}
onClick={this.handleAileClick}
/>
</div>
);
});
let cartList = shoppingCart.map((item, index) => {
return (
<div key={index}>
<span>{item}</span>
<input
type="checkbox"
autoComplete="off"
value={item}
onClick={this.handleCartClick}
/>
</div>
);
});
...
}
Looking at this, is much clean and easy to understand.
- Is it good practice to keep the logic simple in handleXXXClicks, separation of concern one for handling items and one for shoppingCart?
You can do the way you want, but you need to see what you will do in the future, if both will always do the same, you can create one function for both and just send a different parameter, but if they will do some extra stuff, it's good to have then separated.
*(I'm not english native speaker and maybe I didn't understand exaclty what is this part about).
- I feel aisleList and cartList bloated and I could have written itemComponent which is avoidable duplicate code.
Yes, they look so similar that you should create a component for both and pass the diference in props.
- Is there any better way to handle the item in setState? I couldn't think of using prevState in my scenario.
Here is simple setState
using a function with prevState
. Note that the way you are doing it, you need to have event.persist()
, for more info look here
handleCartClick = event => {
event.persist();
this.setState(prevState => {
return {
items: [...prevState.items, event.target.value],
shoppingCart: prevState.shoppingCart.filter(item => {
return item !== event.target.value;
})
};
});
};
Overall
Your code looks ok, there is only one strange thing. You are using checkboxes and when you click, some get marked and other don't. I don't know if that was a test or you also wanted to solve the checkbox problem. Let me know and I can see what I can do.
I recommend you to start using some new things like I mentioned in the tips part, that help you alot and make your code cleaner.
Also have a look at hooks.
For me, using hooks makes alot of things easier, but also, the code looks cleaner.
I made a demo for you to test and see the new code. I changed some things.
- I changed
handleCartClick
but lefthandleAileClick
the way it was before, so you can try useingprevState
yourself. - And also left the part of
itemComponent
for you.
The best way to learn, is doing it!
Hope I helped you!
-
\$\begingroup\$ why avoid using of contructor? \$\endgroup\$degr– degr2019年04月30日 13:39:44 +00:00Commented Apr 30, 2019 at 13:39
-
\$\begingroup\$ @degr You should think in a different way, Why should I use the constructor? \$\endgroup\$Vencovsky– Vencovsky2019年04月30日 13:41:22 +00:00Commented Apr 30, 2019 at 13:41
-
\$\begingroup\$ I should use constructor because it is common practice in all OOP languages where I can declare state of object \$\endgroup\$degr– degr2019年04月30日 13:42:14 +00:00Commented Apr 30, 2019 at 13:42
-
\$\begingroup\$ You would use contructor for things like
.bind
or declaring the state, but now you don't need to use.bind
because arrow functions solve this and you can set the state outside this like a normal property of the class e.g.state = { anythingyouwant: something}
. And also, now with hooks, using class component will desapear over time, and functional components can't have constructor. \$\endgroup\$Vencovsky– Vencovsky2019年04月30日 13:43:29 +00:00Commented Apr 30, 2019 at 13:43 -
\$\begingroup\$
I should use constructor because it is common practice in all OOP languages
, Acctualy, you should use only necessary things, but now, you have better ways of doing the same thing, but in simpler way. I'm not saying you can't use contructor, but that is better to use other ways. \$\endgroup\$Vencovsky– Vencovsky2019年04月30日 13:46:17 +00:00Commented Apr 30, 2019 at 13:46
Explore related questions
See similar questions with these tags.