Code Review Asked by mister nobody on December 28, 2021
Aware that this is a very common exercise; but I’m getting a bit obsessed about creating a simple, clean-ui todo app, at least for myself.
It’s not quite finished yet, but I guess it will be much easier to review now than at the end, in case you look at the javascript code (react)
I’m looking any simple advice, best practice or error in the javascript-react file, or in the css html too.
function H1(){
let hello = "Hi,"
let h1 = (
<h1>
{hello}
</h1>
)
return h1
}
class Todos extends React.Component{
constructor(){
super()
this.state={todos:[]}
//todos will be an array of objects
this.addTodo = this.addTodo.bind(this)
this.toggleCompleted = this.toggleCompleted.bind(this)
}
addTodo(){
//press go -> take input value
let theValue = document.getElementById('input').value
//push array w value and id
this.setState(s => s.todos.push({id:s.todos.length, value:theValue, completed:false}))
return 1
}
toggleCompleted(e){
//toggle completed value in the state object
let parentId = e.target.parentElement.id
let completed = this.state.todos[parentId].completed
if (completed){
this.setState(s => s.todos[parentId].completed=false)
}
else {
this.setState(s => s.todos[parentId].completed=true)
}
}
render() {
let listOfTodos = ""
if (this.state.todos !== []){
listOfTodos = this.state.todos.map(todo => <TodoItem toggler={this.toggleCompleted} key={todo.id} thisItem={todo}/>)
}
return (
<div className="todos__area">
<div className="todos__events">
<input id='input' className="todos__input" placeholder="type here" type="text" />
<button onClick = {this.addTodo} className="todos__button">Go</button>
</div>
<ul className="todos__list">
{listOfTodos}
</ul>
</div>
)
}
}
function TodoItem(props){
return (<li id={props.thisItem.id}>
<input onClick={props.toggler} className="checkbox" type='checkbox'/>
<span style=
{{textDecoration:props.thisItem.completed?"line-through":"none"}}>
{props.thisItem.value}
</span> </li>)
}
function Footer(){
return (
<footer>
<ul className="footer__ul pointer">
<li>Github</li>
<li>Reddit</li>
<li>FCC</li>
</ul>
</footer>
)
}
function App(){
//render components
return (
<main>
<H1 />
<Todos />
<Footer />
</main>
)
}
ReactDOM.render(<App/>, document.getElementById('root'))
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
:root {
--blue:#2c3251;
}
ul {
list-style-type: none;
}
ul > li {
display: block;
padding: 0.5rem;
}
.pointer > li:hover {
cursor: pointer;
}
body {
font-family: "Ubuntu", sans-serif;
text-align: left;
background-color: white;
background-image: radial-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.6)), url("./table.png");
background-repeat: repeat;
padding: 2rem 2rem 0.1rem;
}
main {
display: grid;
min-height: auto;
height: 100vh;
width: 100%;
}
h1 {
font-size: 1.8rem;
color: var(--blue);
box-shadow: inset 0px 2px 5px var(--blue);
align-self: start;
padding: 1.5rem;
border-radius: 15px 15px 0 0;
margin-bottom: 0.5rem;
}
.todos__area {
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7), 1px 1px 5px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
max-height: 300px;
}
.todos__events {
display: flex;
flex-direction: row;
flex: 0 1 40px;
}
.todos__events .todos__input {
border: none;
padding: 0.5rem;
border-radius: 2px 0 0 2px;
flex: 2 1 150px;
}
.todos__events .todos__button {
border: none;
padding: 10px;
flex: 1 1 60px;
color: white;
font-weight: bold;
cursor: pointer;
max-width: 100px;
position: relative;
background-color: rgba(0, 0, 0, 0);
}
.todos__events .todos__button:after {
z-index: -1;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #2d7b57;
opacity: 0.5;
transition: opacity 0.3s ease-out;
content: "";
}
.todos__events .todos__button:hover:after {
opacity: 0.7;
}
/* Inline #1 | http://localhost:3000/ */
.todos__list {
font-family: "Dancing Script", "cursive";
font-size: 1.2rem;
padding: 2rem;
background-color: rgba(227, 227, 134, 0.58);
flex: 1 1 200px;
overflow: auto;
}
.todos__list input[type=checkbox] {
margin-right: 0.6rem;
}
footer {
background-color: rgba(0, 0, 0, 0.1);
align-self: end;
margin-top: 0.5rem;
}
.footer__ul {
display: flex;
justify-content: center;
color: var(--blue);
font-weight: bold;
}
.footer__ul > li {
padding: 1rem;
box-shadow: 1px 0px 1px var(--blue);
transition: transform 0.1s ease-out;
}
.footer__ul > li:hover {
transform: scaleX(1.05);
}
@media screen and (min-width: 700px) {
body {
max-width: 700px;
margin: auto;
}
}
/*# sourceMappingURL=index.css.map */
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<link href="https://fonts.googleapis.com/css2?family=Dancing+Script&family=Ubuntu&display=swap" rel="stylesheet">
<head>
<meta charset="utf-8" />
<title>React App: todoist</title>
</head>
<body >
<div id="root"></div>
</body>
</html>
A few things yet to add:
Hope you like 🙂
You've several issues with state mutation, which is a major anti-pattern in react, poor variable declarations, and other sub-optimal coding style.
constructor
super
). It doesn't appear that currently any props are passed to Todos
, but should this ever change it could cause issue later on down the road. Better to just assume some props can be passed.updates
constructor(props) {
super(props);
this.state={
todos: [],
};
...
}
addToDo
addTodo(){
//press go -> take input value
let theValue = document.getElementById('input').value;
//push array w value and id
this.setState(s => s.todos.push({id:s.todos.length, value: theValue, completed:false}));
return 1;
}
theValue
never changes so it should be declared const.addToDo
is used as an onClick
handler, so the return is meaningless.document.getElementById
is generally also considered an anti-pattern. Using a ref is the more "react way" of getting the valueUser Experience (UX): After finishing a code review I ran your code snippet and noticed the input doesn't clear after being added. Just a suggestion here, but maybe clear out the input field upon adding a todo item.
updates
constructor() {
super();
...
this.inputRef = React.createRef(); // <-- create input ref
...
}
addTodo(){
// press go -> take input value
const { value } = this.inputRef.current;
// shallow copy existing state and append new value
this.setState(({ todos }) => ({
todos: [...todos, { id: todos.length, value, completed: false }],
}));
// Suggestion to clear input
this.inputRef.current.value = '';
}
...
<input
ref={this.inputRef} // <-- attach inputRef to input
id='input'
className="todos__input"
placeholder="type here"
type="text"
/>
...
toggleCompleted
toggleCompleted(e){
//toggle completed value in the state object
let parentId = e.target.parentElement.id
let completed = this.state.todos[parentId].completed
if (completed){
this.setState(s => s.todos[parentId].completed=false)
} else {
this.setState(s => s.todos[parentId].completed=true)
}
}
parentId
and completed
don't change, so should also be declared const.if (completed)
are nearly identical, a more DRY approach would be to do the branching at the value, i.e. using a ternary, or even better, just simply toggle the boolean value, like the function's name suggests.updates
toggleCompleted(e){
// toggle completed value in the state object
const { id } = e.target;
this.setState(({ todos }) => ({
todos: todos.map(todo => todo.id === Number(id) ? { // <-- id is string
...todo,
completed: !todo.completed,
} : todo),
}));
}
render
this.state.todos
is an array, the map function can correctly handle an empty array, no need really to test that it isn't equal to an empty array ([]
), but if there is concern it is more common to conditionally render with a check on the length, i.e. this.state.todos.length && this.state.todos.map(...
.updates
render() {
return (
<div className="todos__area">
<div className="todos__events">
<input ref={this.inputRef} id='input' className="todos__input" placeholder="type here" type="text" />
<button onClick={this.addTodo} className="todos__button">Go</button>
</div>
<ul className="todos__list">
{this.state.todos.map(todo => (
<TodoItem
key={todo.id}
toggler={this.toggleCompleted}
thisItem={todo}
/>
))}
</ul>
</div>
)
}
TodoItem
onChange
handler versus an onClick
, it's semantically more correct.id
to the input instead of the parent node.checked
value of the input to the item completed status.updates
function TodoItem({ thisItem, toggler }){
return (
<li>
<label>
<input
id={thisItem.id}
checked={thisItem.completed}
className="checkbox"
onChange={toggler}
type='checkbox'
/>
<span
style={{
textDecoration: thisItem.completed ? "line-through" : "none"
}}
>
{thisItem.value}
</span>
</label>
</li>
)
}
function H1() {
let hello = "Hi,";
let h1 = <h1> {hello} </h1>;
return h1;
}
class Todos extends React.Component {
constructor() {
super();
this.state = {
todos: []
};
this.inputRef = React.createRef(); // <-- create input ref
//todos will be an array of objects
this.addTodo = this.addTodo.bind(this);
this.toggleCompleted = this.toggleCompleted.bind(this);
}
addTodo() {
// press go -> take input value
const { value } = this.inputRef.current;
// shallow copy existing state and append new value
this.setState(({ todos }) => ({
todos: [
...todos,
{
id: todos.length,
value,
completed: false
}
]
}));
// Suggestion to clear input
this.inputRef.current.value = "";
}
toggleCompleted(e) {
// toggle completed value in the state object
const { id } = e.target;
this.setState(({ todos }) => ({
todos: todos.map(todo =>
todo.id === Number(id)
? {
...todo,
completed: !todo.completed
}
: todo
)
}));
}
render() {
return (
<div className="todos__area">
<div className="todos__events">
<input
ref={this.inputRef}
id="input"
className="todos__input"
placeholder="type here"
type="text"
/>
<button onClick={this.addTodo} className="todos__button">
{" "}
Go{" "}
</button>{" "}
</div>{" "}
<ul className="todos__list">
{" "}
{this.state.todos.map(todo => (
<TodoItem
key={todo.id}
toggler={this.toggleCompleted}
thisItem={todo}
/>
))}{" "}
</ul>{" "}
</div>
);
}
}
function TodoItem({ thisItem, toggler }) {
return (
<li>
<label>
<input
id={thisItem.id}
checked={thisItem.completed}
className="checkbox"
onChange={toggler}
type="checkbox"
/>
<span
style={{
textDecoration: thisItem.completed ? "line-through" : "none"
}}
>
{thisItem.value}{" "}
</span>{" "}
</label>{" "}
</li>
);
}
function Footer() {
return (
<footer>
<ul className="footer__ul pointer">
<li> Github </li> <li> Reddit </li> <li> FCC </li>{" "}
</ul>{" "}
</footer>
);
}
function App() {
//render components
return (
<main>
<H1 />
<Todos />
<Footer />
</main>
);
}
ReactDOM.render( < App / > , document.getElementById('root'))
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
:root {
--blue: #2c3251;
}
ul {
list-style-type: none;
}
ul>li {
display: block;
padding: 0.5rem;
}
.pointer>li:hover {
cursor: pointer;
}
body {
font-family: "Ubuntu", sans-serif;
text-align: left;
background-color: white;
background-image: radial-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.6)), url("./table.png");
background-repeat: repeat;
padding: 2rem 2rem 0.1rem;
}
main {
display: grid;
min-height: auto;
height: 100vh;
width: 100%;
}
h1 {
font-size: 1.8rem;
color: var(--blue);
box-shadow: inset 0px 2px 5px var(--blue);
align-self: start;
padding: 1.5rem;
border-radius: 15px 15px 0 0;
margin-bottom: 0.5rem;
}
.todos__area {
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7), 1px 1px 5px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
max-height: 300px;
}
.todos__events {
display: flex;
flex-direction: row;
flex: 0 1 40px;
}
.todos__events .todos__input {
border: none;
padding: 0.5rem;
border-radius: 2px 0 0 2px;
flex: 2 1 150px;
}
.todos__events .todos__button {
border: none;
padding: 10px;
flex: 1 1 60px;
color: white;
font-weight: bold;
cursor: pointer;
max-width: 100px;
position: relative;
background-color: rgba(0, 0, 0, 0);
}
.todos__events .todos__button:after {
z-index: -1;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #2d7b57;
opacity: 0.5;
transition: opacity 0.3s ease-out;
content: "";
}
.todos__events .todos__button:hover:after {
opacity: 0.7;
}
/* Inline #1 | http://localhost:3000/ */
.todos__list {
font-family: "Dancing Script", "cursive";
font-size: 1.2rem;
padding: 2rem;
background-color: rgba(227, 227, 134, 0.58);
flex: 1 1 200px;
overflow: auto;
}
.todos__list input[type=checkbox] {
margin-right: 0.6rem;
}
footer {
background-color: rgba(0, 0, 0, 0.1);
align-self: end;
margin-top: 0.5rem;
}
.footer__ul {
display: flex;
justify-content: center;
color: var(--blue);
font-weight: bold;
}
.footer__ul>li {
padding: 1rem;
box-shadow: 1px 0px 1px var(--blue);
transition: transform 0.1s ease-out;
}
.footer__ul>li:hover {
transform: scaleX(1.05);
}
@media screen and (min-width: 700px) {
body {
max-width: 700px;
margin: auto;
}
}
/*# sourceMappingURL=index.css.map */
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<link href="https://fonts.googleapis.com/css2?family=Dancing+Script&family=Ubuntu&display=swap" rel="stylesheet">
<head>
<meta charset="utf-8" />
<title>React App: todoist</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Answered by Drew Reese on December 28, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP