Trying to delete an item from arrays of objects in React - javascript

I'm learning React. I'm trying to build a simple todo app on my own to learn & practice with the library. I have passed a list of tasks in the parent component & passed them to the child component as props. I was also able to output it in the child component using the map() method. However, I have no idea how to delete an item. I have tried searching online, but I'm unable to adapt their solutions to my use case.
Parent Component
import React, { Component } from 'react';
import './styles/components/App.css';
import Todos from './components/Todos'
class App extends Component {
state = {
todos: [
{ task: 'Study React', id: 1 },
{ task: 'Build something with it', id: 2 },
{ task: 'Apply for jobs', id: 3 },
],
}
render(){
return (
<div className="App">
<Todos todos={this.state.todos} />
</div>
);
}
}
export default App;
Child Component
import React, { Component } from 'react';
import '../styles/components/Todos.css'
class Todos extends Component {
render() {
let { todos } = this.props;
let todoList = todos.map(( todo => {
return (
<div className="todos" key={todo.id}>
<div>{ todo.task }</div>
</div>
)
}));
return (
<div onClick={this.deleteTask}>{ todoList }</div>
)
}
deleteTask() {
// checks if method is working
console.log('working');
// code to delete
}
}
export default Todos

Parent Component
import React, { Component } from 'react';
import './styles/components/App.css';
import Todos from './components/Todos'
class App extends Component {
state = {
todos: [
{ task: 'Study React', id: 1 },
{ task: 'Build something with it', id: 2 },
{ task: 'Apply for jobs', id: 3 },
],
};
// Event and data put in same Component.
deleteTodo(id) {
let workTodos = [...this.state.todos];
const deleteIndex = workTodos.findIndex(todo => todo.id === id);
workTodos.splice(deleteIndex, 1);
this.setState({
todos: [...workTodos]
})
}
render(){
// Use props give Todos Component the data and events to render dom
return (
<div className="App">
<Todos
todos={this.state.todos}
deleteTodo={this.deleteTodo.bind(this)}
/>
</div>
);
}
}
export default App;
Child Component
import React, { Component } from 'react';
import '../styles/components/Todos.css'
class Todos extends Component {
render() {
// Receiving events and data form props
let { todos, deleteTodo } = this.props;
// Click div trigger deleteTodo, It can execute deleteTodo in App component
return todos.map(( todo => {
return (
<div
className="todos"
key={todo.id}
onClick={() => { deleteTodo(todo.id) }}
>
<div>{ todo.task }</div>
</div>
)
}));
}
}
export default Todos
Like a commit, put delete event in App component, Then use props trigger it in the Todos component, Please let me know if you have any questions.

Related

this.state is NULL in ReactJs component

I am following a tutorial. I don't get why totalCounters is null. I searched online but I do not understand it.
The error message I get is :
TypeError: Cannot read property 'counters' of null.
I followed the tutorial from Mosh.
This is my App.js file.
import React, { Component } from "react";
import NavBar from "./components/navbar";
import Counters from "./components/counters";
import "./App.css";
class App extends Component {
render() {
return (
<React.Fragment>
<NavBar totalCounters={this.state.counters.length} />
<main className="container">
<Counters
counters={this.counters}
onReset={this.handleReset}
onIncrement={this.handleIncrement}
onDelete={this.handleDelete}
/>
</main>
</React.Fragment>
);
}
}
export default App;
This is my navbar.jsx
import React, { Component } from "react";
class NavBar extends Component {
render() {
return (
<nav className="navbar navbar-light bg-light">
<a className="navbar-brand" href="#">
Navbar <span className="badge badge-pill badge-secondary">{this.props.totalCounters}</span>
</a>
</nav>
);
}
}
export default NavBar;
This is my counters.jsx
import React, { Component } from "react";
import Counter from "./counter";
class counters extends Component {
state = {
counters: [
{ id: 1, value: 5 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
]
};
handleIncrement = counter => {
const countersCopy = [...this.state.counters];
const index = countersCopy.indexOf(counter);
countersCopy[index] = { ...counter };
countersCopy[index].value++;
this.setState({ counters: countersCopy });
};
handleReset = () => {
const resetCounters = this.state.counters.map(c => {
c.value = 0;
return c;
});
this.setState({ counters: resetCounters });
};
handleDelete = counterId => {
const newCounters = this.state.counters.filter(c => c.id !== counterId);
this.setState({ counters: newCounters });
};
render() {
return (
<div>
<button
onClick={this.handleReset}
className="btn btn-primary btn-sm m2"
>
Reset
</button>
{this.state.counters.map(counter => (
<Counter
key={counter.id}
onDelete={this.props.onDelete}
onIncrement={this.handleIncrement}
counter={counter}
/>
))}
</div>
);
}
}
export default counters;
In React, this.state is local to each component.
So, setting this.state.counters in counters does not allow App component to use the state.
This is why counters is null in App component.
Because you don't have a state field into your App class components.
Everywhere you want to use state, you have to create a state object.
Class field
class App extends Component {
state = { counters: [] }
}
Inside contructor
class App extends Component {
contructor(props) {
super(props)
this.state = { counters: [] }
}
}
You are not initializing the state. Your state is undefined. Fix it like this
class App extends Component {
this.state = { counters : [] }
}

React - Passing state from child to parent in drop down select

I am making a basic dropdown selector. I almost had it working when I realized I was setting the state in both the parent and the child so I refactored again to try to simplify it all and put most of the responsibility in one place.
My logic is in the MyDropDown component, then I have a Header component, then the Main which should render it all.
import React from 'react';
class MyDropdown extends React.Component {
render() {
let initialUsers = this.props.state.users;
let alphabetizeUsers = initialUsers
.sort((a, b) => {
return a.name > b.name;
})
.map(obj => {
return (
<option key={obj.id} value={obj.name}>
{obj.name}
</option>
);
});
return <select>{alphabetizeUsers}</select>;
}
}
export default MyDropdown;
Then I have my main component where I do the api call and pass the state into the dropdown component.
import React from 'react';
import MyDropdown from './MyDropdown';
class UserHeader extends React.Component {
state = {
users: []
};
componentDidMount() {
let initialUsers = [];
fetch('http://localhost:3000/users')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ users: data });
});
}
render() {
return <MyDropdown state={this.state} />;
}
}
export default UserHeader;
And finally my Main Component, where I want to show the value from the selected dropdown menu
import React, { Component } from 'react';
import './Main.css';
import MyDropdown from './components/MyDropdown';
import UserHeader from './components/UserHeader';
class Main extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<span className="App-title">SELECT A USER:</span>
<UserHeader />
</header>
<p className="App-intro">
I should get the dropdown value here: {this.state.user}
</p>
</div>
);
}
}
export default Main;
What I tried doing is moving the statement
I should get the dropdown value here: {this.state.policies} .
into the UserHeader component. How do I get the value selected in the child back up to its parent?
Another thing I've tried is adding a handler to the child component
onChange = e => {
this.setState({ selectedUser: e.target.value });
};
and add it to the select... but again not sure how to get this value up to the parent.
return <select onChange={this.onChange}>{alphabetizeUsers}</select>;
The easiest way to pass the value back to the parent component is through a callback.
Try defining and passing in an onChange={this.onChange} to your Main component like so your Main component becomes:
import React, { Component } from 'react';
import './Main.css';
import MyDropdown from './components/MyDropdown';
import UserHeader from './components/UserHeader';
class Main extends Component {
this.state = {
user: null,
}
constructor(props) {
super(props);
this.onChangeUser = this.onChangeUser.bind(this);
}
onChangeUser(newUser) {
this.setState({ user: newUser });
}
render() {
return (
<div className="App">
<header className="App-header">
<span className="App-title">SELECT A USER:</span>
<UserHeader onChangeUser={this.onChangeUser} />
</header>
<p className="App-intro">
I should get the dropdown value here: {this.state.user}
</p>
</div>
);
}
}
export default Main;
Now you are passing in a callback, you can do the same thing with your UserHeader component.
import React from 'react';
import MyDropdown from './MyDropdown';
class UserHeader extends React.Component {
state = {
users: []
};
componentDidMount() {
let initialUsers = [];
fetch('http://localhost:3000/users')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ users: data });
});
}
render() {
return <MyDropdown state={this.state} onChange={this.props.onChangeUser} />;
}
}
export default UserHeader;
And finally, you can now attach this callback to your <select> element.
import React from 'react';
class MyDropdown extends React.Component {
render() {
let initialUsers = this.props.state.users;
let alphabetizeUsers = initialUsers
.sort((a, b) => {
return a.name > b.name;
})
.map(obj => {
return (
<option key={obj.id} value={obj.name}>
{obj.name}
</option>
);
});
return <select onChange={(ev) => this.props.onChange(ev.target.value)}>{alphabetizeUsers}</select>;
}
}
export default MyDropdown;
By defining the onChange on your select element like this, onChange={(ev) => this.props.onChange(ev.target.value)}, you can return the value to the main component and use it in your state.

Prop is marked as required in component, but its value is `undefined`

single.js :
import React, { Component } from 'react';
import Details from '../components/details'
import { ProgressBar } from 'react-materialize';
import { Route, Link } from 'react-router-dom';
const Test = () => (
<div> RENDER PAGE 1</div>
)
class SinglePage extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
}
}
componentDidMount() {
fetch('http://localhost:1337/1')
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}
render() {
const { data } = this.state;
return (
<div>
<h2> SinglePage </h2>
{!data ? (
<ProgressBar />
) : (
<div>
<Details data={data} />
</div>
)}
</div>
);
}
}
export default SinglePage;
details.js :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Details extends Component {
static propTypes = {
item: PropTypes.shape({
date: PropTypes.string.isRequired,
}).isRequired,
}
render() {
const { item } = this.props;
return (
<div>
<p> {item.date} </p>
</div>
)
}
}
export default Details;
In console, I am getting an error : Warning: Failed prop type: The prop item is marked as required in Details, but its value is undefined.
From this I though my json was not catched but I have an other component which fetch on http://localhost:1337/ , get datas and display them correctly, and going to http://localhost:1337/1 send me a json response so I'm quite confused here.
Additional screenshot :
SinglePage is passing date props with name data as oppose to item that is defined in Details
<Details item={date} />
Also adding init value for date
constructor(props) {
super(props);
this.state = {
date: { date: null },
}
}

Unable to parse props in child component

I am trying to implement Presentational and Container Components pattern when creating React components. So I created a presentational component with only UI elements and container component with handling data capabilities.
component.jsx
import React from "react";
const MyComponent = ({props}) => (
<div>
{props.games.map((game, index) => (
<div key={index}>
{game.index + 1} - {game.contestName}
</div>
))};
</div>
);
export default MyComponent;
container.jsx
import React, { Component } from "react";
import MyComponent from "./component";
class MyContainer extends Component {
constructor(props) {
super(props);
this.state = {
games: [
{
id: 1,
categoryName: "Business/Company",
contestName: "Name1"
},
{
id: 2,
categoryName: "Magazine/Newsletter",
contestName: "Name2"
},
{
id: 3,
categoryName: "Software Component",
contestName: "Name3"
},
{
id: 4,
categoryName: "Website",
contestName: "Name4"
}
]
};
}
render() {
return (
<div>
<MyComponent games={this.state.games} />
</div>
);
}
}
export default MyContainer;
However, I can not render data and I get
Uncaught TypeError:
Cannot read property 'games' of undefined.
Would really appreciate your help, as two days of internet digging has not yielded positive results.
const MyComponent = ({props}) => (
When you do this, you actually do
{ props: props-received-from-parent }
You are enclosing your props in another object, remove those braces and change that line to
const MyComponent = (props) => (
and you are good to go.
You should destructure your games instead of props:
import React from "react";
const MyComponent = ({games}) => (
<div>
{games.map((game, index) => (
<div key={index}>
{game.index + 1} - {game.contestName}
</div>
))};
</div>
);
export default MyComponent;
You can define your MyComponent class like this
class MyComponent extends Component{
render(){
this.xyz = this.props.games.map((item,index) => {
return(<div key={index}>{item.id}</div>)
})
return(
<div>
{this.xyz}
</div>
)
}
}
export default MyComponent;
This will also work!

react redux assign data to component

I have searched around, all questions are something about How to pass props to {this.props.children}
But my situation is different,
I fill App with a initial data -- nodes, and map nodes to a TreeNodelist, and I want each TreeNode has the property of passed in node.
Pseudo code:
App.render:
{nodes.map(node =>
<TreeNode key={node.name} info={node} />
)}
TreeNode.render:
const { actions, nodes, info } = this.props
return (
<a>{info.name}</a>
);
Seems node not be passed in as info, log shows info is undefined.
warning.js?8a56:45 Warning: Failed propType: Required prop `info` was not specified in `TreeNode`. Check the render method of `Connect(TreeNode)`.
TreeNode.js?10ab:57 Uncaught TypeError: Cannot read property 'name' of undefined
below just a more complete code relate to this question(store and action is not much relation I think):
containers/App.js:
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Footer from '../components/Footer';
import TreeNode from '../containers/TreeNode';
import Home from '../containers/Home';
import * as NodeActions from '../actions/NodeActions'
export default class App extends Component {
componentWillMount() {
// this will update the nodes on state
this.props.actions.getNodes();
}
render() {
const { nodes } = this.props
console.log(nodes)
return (
<div className="main-app-container">
<Home />
<div className="main-app-nav">Simple Redux Boilerplate</div>
<div>
{nodes.map(node =>
<TreeNode key={node.name} info={node} />
)}
</div>
<Footer />
</div>
);
}
}
function mapStateToProps(state) {
return {
nodes: state.opener.nodes
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(NodeActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
containers/TreeNode.js
import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import classNames from 'classnames/bind'
import * as NodeActions from '../actions/NodeActions'
class TreeNode extends Component {
handleClick() {
this.setState({ open: !this.state.open })
if (this.state.open){
this.actions.getNodes()
}
}
render() {
const { actions, nodes, info } = this.props
if (nodes) {
const children =<div>{nodes.map(node => <TreeNode info={node} />)}</div>
} else {
const children = <div>no open</div>
}
return (
<div className={classNames('tree-node', { 'open':this.props.open})} onClick={ () => {this.handleClick()} }>
<a>{info.name}</a>
{children}
</div>
);
}
}
TreeNode.propTypes = {
info:PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
}
function mapStateToProps(state) {
return {
open: state.open,
info: state.info,
nodes: state.nodes
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(NodeActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TreeNode);
reducers/TreeNodeReducer.js
import { OPEN_NODE, CLOSE_NODE, GET_NODES } from '../constants/NodeActionTypes';
const initialState = {
open: false,
nodes: [],
info: {}
}
const testNodes = [
{name:'t1',type:'t1'},
{name:'t2',type:'t2'},
{name:'t3',type:'t3'},
]
function getFileList() {
return {
nodes: testNodes
}
}
export default function opener(state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
var {nodes} = getFileList()
return {
...state,
open:true,
nodes:nodes
};
case CLOSE_NODE:
return {
...state,
open:false
};
case GET_NODES:
var {nodes} = getFileList()
return {
...state,
nodes:nodes
};
default:
return state;
}
}
For complete code, can see my github https://github.com/eromoe/simple-redux-boilerplate
This error make me very confuse. The sulotion I see are a parent already have some children, then feed props to them by using react.Children, and them don't use redux.
When looping on nodes values, you call TreeNode and give the property info: that is good!
But when your component is rendered, this function is called:
function mapStateToProps(state) {
return {
open: state.open,
info: state.info,
nodes: state.nodes
};
}
As you can see, the prop info will be overriden with the value in state.info. state.info value is undefined I think. So React warns you that TreeNode requires this value. This warning comes from your component configuration:
TreeNode.propTypes = {
info:PropTypes.object.isRequired
}
Why state.info is undefined? I think you doesn't call it as it should. You should call state['reducerNameSavedWhenCreatingReduxStore].infoto retreive{}`.
You shouldn't fill ThreeNode through both props & connect().
It's because you are rendering a Redux connected component from within a parent Redux connected component and trying to pass props into it as state.
Why does TreeNode.js need to be connected to Redux? Props/Actions should be passed uni-directionally with only the top level component connected to state and all child components being essentially dumb components.
TreeNode should look similar to this:
class TreeNode extends Component {
handleClick() {
this.setState({ open: !this.state.open })
if (this.state.open){
this.props.actions.getNodes();
}
}
render() {
const { nodes, info } = this.props
if (nodes) {
const children =<div>{nodes.map(node => <TreeNode info={node} />)}</div>
} else {
const children = <div>no open</div>
}
return (
<div className={classNames('tree-node', { 'open':this.props.open})} onClick={ () => {this.handleClick()} }>
<a>{info.name}</a>
{children}
<div>{nodes.map(node => <TreeNode info={node} />)}</div>
</div>
);
}
}
TreeNode.propTypes = {
info: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
}
export default class TreeNode;
and the parent component would render TreeNode like this, passing the props in to the component:
<div>
{nodes.map(node =>
<TreeNode key={node.name} info={node} actions={this.props.actions} />
)}
</div>

Categories

Resources