I create https://github.com/storenth/react-redux-todo and ask to review my Todo-react-app for programming pattern: dividing components into presentational components and container components.
Here’s the basic idea behind it: if a component has to have state, make calculations based on props, or manage any other complex logic, then that component shouldn’t also have to render HTML-like JSX.
Instead of rendering HTML-like JSX, the component should render another component. It should be that component’s job to render HTML-like JSX.
So, my question about the /containers/TodosContainer
that contains /components/Todos
and /components/AddForm
. Does it match the pattern? I didn't get where to place the AddForm
, inside /components/Todos
(to complex I guess)?
import React, { Component, Fragment } from 'react';
import { Todos } from "../components/Todos";
import { AddForm } from "../components/AddForm";
export class TodosContainer extends Component {
constructor(props) {
super(props);
this.state = { items: [], text: '' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.removeItem = this.removeItem.bind(this);
}
handleChange = (e) => {
this.setState({
text: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
if (this.state.text.length === 0) {
return;
}
const newItem = {
text: this.state.text,
id: Date.now()
};
this.setState({
items: this.state.items.concat(newItem),
text: ''
})
}
removeItem = (id) => {
const items = this.state.items.filter(item => {
return item.id !== id;
})
this.setState({
items
})
console.log(this.state);
}
render() {
return (
<>
<Todos items={this.state.items} removeItem={this.removeItem} />
<AddForm items={this.state} onSubmit={this.handleSubmit} onChange={this.handleChange} />
</>
)
}
}
```
-
\$\begingroup\$ The current question title of your question is too generic to be helpful. Please edit to the site standard, which is for the title to simply state the task accomplished by the code. Please see How do I ask a good question?. \$\endgroup\$BCdotWEB– BCdotWEB2020年06月23日 08:34:49 +00:00Commented Jun 23, 2020 at 8:34
-
\$\begingroup\$ @BCdotWEB will be very helpful you start to editing my post. \$\endgroup\$storenth– storenth2020年06月23日 11:14:01 +00:00Commented Jun 23, 2020 at 11:14
1 Answer 1
I'd say, if it's the Presentation and Container Components pattern as described by Dan Abramov, then yes, you've pretty effectively nailed it. (Though Dan no longer advocates this pattern!!)
Suggestions/Review
AddForm
items
prop is really the text
state in the parent component, it doesn't need the entire state object. (If it does then name it more accurately!) What's used is just the input value, so value
is a better descriptor.
export const AddForm = ({ value, onSubmit, onChange }) => {
console.log(value);
return (
<div className="add-item-form">
<form onSubmit={e => onSubmit(e)}>
<label htmlFor="name">Add new todo:</label>
<input
type="text"
id="name"
onChange={e => onChange(e)}
value={value}
/>
<button
className="btn waves-effect waves-light"
type="submit"
name="action"
>
Add item
</button>
</form>
</div>
);
};
Usage: <AddForm value={this.state.text} ... />
TodosContainer
The handlers are all arrow functions, they already have the this
of the class bound to them, so there isn't need to bind them in a constructor. In fact, if all the constructor is doing is setting the initial state, then you don't really need the constructor either as you can simply set initial state with a state instance variable.
Instead of
constructor(props) {
super(props);
this.state = { items: [], text: '' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.removeItem = this.removeItem.bind(this);
}
Simply
state = { items: [], text: '' };
State updates
When any next state depends upon current state, like incrementing counters, or adding values to arrays, you should get into the habit of using functional state updates, and also not referencing this.state
within the state update.
handleSubmit
can be improved
handleSubmit = (e) => {
e.preventDefault();
const newItem = {
text: this.state.text,
id: Date.now(),
};
if (this.state.text) { // <-- if text is truthy, i.e. non-empty string
this.setState(prevState => ({
items: prevState.items.concat(newItem),
}));
}
}
State updates are also asynchronous, so don't try to console.log it after requesting the state update. Use a component lifecycle like componentDidUpdate
to log state after the update (or for class-based components, you can use the setState 2nd parameter callback function, but for simplicity sake stick to the lifecycle functions.).
removeItem
handler can be improved
removeItem = (id) => {
this.setState(prevState => ({
items: prevState.items.filter(item => item.id !== id),
}));
}
-
\$\begingroup\$ I will review your answer more closely in a while, thank you. And, yes, in 2019 update, Dan Abramov told us use pattern without dogmatic fervor. \$\endgroup\$storenth– storenth2020年06月27日 05:38:37 +00:00Commented Jun 27, 2020 at 5:38
Explore related questions
See similar questions with these tags.