2
\$\begingroup\$

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} />
 </>
 )
 }
}
```
asked Jun 22, 2020 at 7:03
\$\endgroup\$
2
  • \$\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\$ Commented Jun 23, 2020 at 8:34
  • \$\begingroup\$ @BCdotWEB will be very helpful you start to editing my post. \$\endgroup\$ Commented Jun 23, 2020 at 11:14

1 Answer 1

2
\$\begingroup\$

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.

State and Lifecycle setState

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),
 }));
}
answered Jun 23, 2020 at 6:56
\$\endgroup\$
1
  • \$\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\$ Commented Jun 27, 2020 at 5:38

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.