Redux: re-rendering children on store state update - reactjs

I have started to learn react + redux recently. Prior to that I have not had experience with this sort of stack.
So I ran into a problem where I do not understand why child components do not get re-rendered when reducer returns new state.
Full code on GITHUB
This is my parent component source on git:
import React from "react"
import {connect} from "react-redux"
import {fetchWarehouses} from "../../actions/warehouseActions"
import WarehouseMenu from "./WarehouseMenu"
import WarehouseView from "./WarehouseView"
import WarehouseEdit from "./WarehouseEdit"
#connect((store) => {
return {
warehouses: store.warehouses.warehouses,
selectedWarehouse: store.warehouses.selectedWarehouse,
isSelected: store.warehouses.isSelected,
warehouseCount: store.warehouses.warehouseCount,
}
})
export default class WarehousePage extends React.Component {
componentWillMount() {
this.props.dispatch(fetchWarehouses())
}
render() {
return (
<div className="container-fluid">
<div className="row">
<div className="col-sm-2">
Warehouses ({this.props.warehouseCount}):
</div>
<div className="col-sm-10">
<WarehouseMenu warehouseList={this.props.warehouses} />
</div>
</div>
<div className="col-lg-12">
<WarehouseView test={this.props.isSelected} selectedWarehouse={this.props.selectedWarehouse} />
</div>
<div className="col-lg-12">
<WarehouseEdit />
</div>
</div>
)
}
}
And these are children source on git:
import React from "react"
import store from "../../store"
import {fetchOne} from "../../actions/warehouseActions"
export default class WarehouseMenu extends React.Component {
constructor(props) {
super(props)
}
select(id) {
store.dispatch(fetchOne(id))
}
render() {
const {warehouseList} = this.props.warehouseList
if (!warehouseList) {
return <button className="btn btn-success" key="new_warehouse">+</button>
}
const mappedWarehouses = warehouseList.map(wh => <button onClick={this.select.bind(this, wh.id)} className="btn btn-default" key={wh.id}>{wh.name}</button>)
mappedWarehouses.push(<button className="btn btn-success" key="new_warehouse">+</button>)
return (
<div className="btn-group">
{mappedWarehouses}
</div>
)
}
}
And source on git:
import React from "react"
import store from "../../store"
import {deleteWarehouse, fetchWarehouses} from "../../actions/warehouseActions"
export default class WarehouseView extends React.Component {
constructor(props) {
super(props)
}
render() {
const {test, selectedWarehouse} = this.props
if (!test) {
return null
}
return (
<div className="container-fluid">
<div className="row">
<div className="col-sm-2">
ID
</div>
<div className="col-sm-10">
{selectedWarehouse.id}
</div>
</div>
<div className="row">
<div className="col-sm-2">
Name
</div>
<div className="col-sm-10">
{selectedWarehouse.name}
</div>
</div>
<div className="row">
<div className="col-sm-12">
<button className="btn btn-warning">EDIT</button>
<button onClick={this.deleteWarehouse.bind(this, selectedWarehouse.id)} className="btn btn-danger">DELETE</button>
</div>
</div>
</div>
)
}
deleteWarehouse(id) {
store.dispatch(deleteWarehouse(id))
}
}
So whenever I dispatch deleteWarehouse I want WarehouseMenu to rerender since the state of store.warehouses.warehouses changes. I do not get the expected result. Only WarehousePage rerenders (e.g. store.warehouses.warehouseCount). I've tried #connectint store to child components but did not seem to get the desired result also.

You are not alterning the warehouses property inside your warehouseReducers.js when a DELETE_WAREHOUSE_FULFILLED action is dispatched, but you do alter the warehouseCount

Your delete action:
export function deleteWarehouse(id) {
return function (dispatch) {
axios.delete(`/api/sandy/api/warehouses/${id}`)
.then((response) => {
dispatch({
type: "DELETE_WAREHOUSE_FULFILLED",
payload: null
})
})
.catch((err) => {
})
}
}
never updates the state to remove the deleted warehouse from the state.warehouses array in warehouseReducers.js:
case "DELETE_WAREHOUSE_FULFILLED": {
return {...state,
selectedWarehouse: null,
isSelected: false,
warehouseCount: state.warehouseCount - 1
}
}

Related

Refreshing Component and not all view with react

Im already learning about to fetch data from REST API with react, i have to components (form for submit and a card for get data) both summon from parent component (App), and is working, so i got to push new todo to db and get the news values on my card component, but instead only render cards components, render the all App (incluyed the form component), what am i doing wrong guys?
This is the parent Component
import React, { Component } from 'react';
import './App.css';
import SampleCard from './components/SampleCard';
import Form from './components/Form';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
componentDidMount() {
this.getTasks()
}
getTasks =_=> {
fetch('http://localhost:4000/users')
.then(response => response.json())
.then(data => this.setState({ data: data.data }))
.catch(err => console.log(err))
}
render() {
return (
<div>
<form onSubmit={this.getTasks}>
<Form />
</form>
{this.state.data.map((row, i) => (
<div key={i}>
<SampleCard row={row} />
</div>
))}
</div>
)
}
}
export default App;
This, the form component
import React, { Component } from "react";
class Form extends Component {
constructor(props) {
super(props);
this.state = {
tasks: {
task: '',
status: ''
}
}
}
addTask = _ => {
const { tasks } = this.state
fetch(`http://localhost:4000/users/add?task=${tasks.task}&status=${tasks.status}`)
.catch( err => console.log(err))
}
render() {
const { tasks } = this.state
return (
<div className="Form container mt-3">
<div className="input-group mb-3">
<div className="input-group-prepend">
<span className="input-group-text" id="basic-addon1">
Define Task!
</span>
</div>
<input
type="text"
value={tasks.task}
onChange={e => this.setState({ tasks: { ...tasks, task: e.target.value } })}
className="form-control"
placeholder="Task"
aria-label="Task"
aria-describedby="basic-addon1"
/>
</div>
<div className="input-group mb-3">
<div className="input-group-prepend">
<span className="input-group-text" id="basic-addon1">
Define Status!
</span>
</div>
<input
type="text"
value={tasks.status}
onChange={e => this.setState({ tasks: { ...tasks, status: e.target.value } })}
className="form-control"
placeholder="Status"
aria-label="Status"
aria-describedby="basic-addon1"
/>
</div>
<button
type="Submit"
className="btn btn-outline-success d-flex mr-auto"
onClick={this.addTask}
>
Add
</button>
</div>
);
}
}
export default Form;
and this the card component
import React, { Component } from "react";
export default class SampleCard extends Component {
render() {
return (
<div className="container pt-5">
<div className="col-xs-12">
<div className="card">
<div className="card-header">
<h5 className="card-title">{this.props.row.task}</h5>
</div>
<div className="card-body">
<h4 className="card-title">{this.props.row.created_at}</h4>
{this.props.row.status === 1 ? (
<h3 className="card-title">Pending</h3>
) : (
<h3 className="card-title">Completed</h3>
)}
</div>
</div>
</div>
</div>
);
}
}
You're not preventing default submit behavior. Do it like this
getTasks = (e) => {
e.preventDefault();
fetch('http://localhost:4000/users')
.then(response => response.json())
.then(data => this.setState({ data: data.data }))
.catch(err => console.log(err))
}
Whenever using submit with a form, use e.preventDefault() it prevents refreshing.

How to Add a Value to an Array on Button Press (Having Trouble)

I'm setting up a multipage form and have the steps assigned to an array.
What I am trying to figure out is the best way to add a new page to the form on button press. My solution was to build the new pages of the form (there will be 20 duplicates though, each with own variables).
Then I have a button labeled 'Add New' which calls a function to add a new line to the array, thus enabling the next page.
Of course, ideally my first solution was to have react automatically build a new page and variables but I'm not sure if that's feasible for me to do.
steps.js:
import React from 'react'
import { StepOne } from './StepOne'
import { StepTwo } from './StepTwo'
import { StepThree } from './StepThree'
const steps =
[
{name: 'Job', component: <StepOne/>},
{name: 'device 1', component: <StepTwo/>},
]
export { steps }
home.js (where my form lives)
import React, { Component } from "react";
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import MultiStep from 'react-multistep'
import { steps } from './steps'
import {
Route,
NavLink,
HashRouter
} from "react-router-dom";
import newdevice from "./new-device";
class Home extends Component {
render() {
return (
<div>
<h1>Start a New Job</h1>
<div className='container'>
<div>
<MultiStep steps={steps} />
</div>
<div className='container app-footer'>
<h6>Press 'Enter' or click on progress bar for next step.</h6>
</div>
</div>
</div>
);
}
}
export default Home;
StepTwo.js
import React from 'react'
import { steps } from './steps'
import { StepThree } from './StepThree'
import update from 'react-addons-update';
export class StepTwo extends React.Component {
addNew() {
this.setState(previousState => ({
steps: [...previousState.steps, "{name: 'device 2', component: <StepThree/>},"]
}));
}
<div className='row'>
<div className="col-md-4">
<button onClick={this.addNew} variant="primary">Add a New Device</button>
</div>
</div>
</div>
)
}
}
If you want to see the entire page of the form for a better idea, full StepTwo.js
import React from 'react'
import { steps } from './steps'
import { StepThree } from './StepThree'
import update from 'react-addons-update'; // ES6
//
export class StepTwo extends React.Component {
addNew() {
this.setState(previousState => ({
steps: [...previousState.steps, "{name: 'device 2', component: <StepThree/>},"]
}));
}
constructor () {
super()
this.state = {
Box: '',
VIN: '',
Lbl: '',
Year: '',
Make: '',
Model: '',
Plate: '',
ODO: '',
Notes: '',
}
this.handleBoxChanged = this.handleBoxChanged.bind(this);
this.handleVINChanged = this.handleVINChanged.bind(this);
this.handleLblChanged = this.handleLblChanged.bind(this);
this.handleYearhanged = this.handleYearChanged.bind(this);
this.handleMakeChanged = this.handleMakeChanged.bind(this);
this.handleModelChanged = this.handleModelChanged.bind(this);
this.handlePlateChanged = this.handlePlateChanged.bind(this);
this.handleODOChanged = this.handleODOChanged.bind(this);
this.handleNotesChanged = this.handleNotesChanged.bind(this);
}
handleBoxChanged (event) {
this.setState({Box: event.target.value})
}
handleVINChanged (event) {
this.setState({VIN: event.target.value})
}
handleLblChanged (event) {
this.setState({Lbl: event.target.value})
}
handleYearChanged (event) {
this.setState({Year: event.target.value})
}
handleMakeChanged (event) {
this.setState({Make: event.target.value})
}
handleModelChanged (event) {
this.setState({Model: event.target.value})
}
handlePlateChanged (event) {
this.setState({Plate: event.target.value})
}
handleODOChanged (event) {
this.setState({ODO: event.target.value})
}
handleNotesChanged (event) {
this.setState({Notes: event.target.value})
}
render () {
return (
<div>
<div className='row'>
<div className='six columns'>
<label>Device #</label>
<input
className='u-full-width'
placeholder='Device #'
type='text'
onChange={this.handleBoxChanged}
value={this.state.Box}
autoFocus
/>
</div>
</div>
<div className='row'>
<div className='six columns'>
<label>VIN</label>
<input
className='u-full-width'
placeholder='VIN'
type='text'
onChange={this.handleVINChanged}
value={this.state.VIN}
/>
</div>
</div>
<div className='row'>
<div className='six columns'>
<label>Label</label>
<input
className='u-full-width'
placeholder='Label'
type='text'
onChange={this.handleLblChanged}
value={this.state.Lbl}
/>
</div>
</div>
<div className='row'>
<div className='six columns'>
<label>Year</label>
<input
className='u-full-width'
placeholder='Year'
type='text'
onChange={this.handleYearChanged}
value={this.state.Year}
/>
</div>
</div>
<div className='row'>
<div className='six columns'>
<label>Make</label>
<input
className='u-full-width'
placeholder='Make'
type='text'
onChange={this.handleMakeChanged}
value={this.state.Make}
/>
</div>
</div>
<div className='row'>
<div className='six columns'>
<label>Model</label>
<input
className='u-full-width'
placeholder='Model'
type='text'
onChange={this.handleModelChanged}
value={this.state.Model}
/>
</div>
</div>
<div className='row'>
<div className='six columns'>
<label>Plate</label>
<input
className='u-full-width'
placeholder='VPlateIN'
type='text'
onChange={this.handlePlateChanged}
value={this.state.Plate}
/>
</div>
</div>
<div className='row'>
<div className='six columns'>
<label>ODO</label>
<input
className='u-full-width'
placeholder='ODO'
type='text'
onChange={this.handleODOChanged}
value={this.state.ODO}
/>
</div>
</div>
<div className='row'>
<div className='six columns'>
<label>Notes</label>
<input
className='u-full-width'
placeholder='Notes'
type='text'
onChange={this.handleNotesChanged}
value={this.state.VIN}
/>
</div>
</div>
<div className='row'>
<div className="col-md-4">
<button onClick={this.addNew} variant="primary">Add a New Device</button>
</div>
</div>
</div>
)
}
}
The main problem is that you're trying to call setState inside StepTwo component but you don't store the steps in component's state there.
You're using a separate .js file to store the steps. This way React doesn't get notified when the steps array is changed and thus won't update (it will at some point, when it's re-rendered for some another reason, but you usually don't want that).
Looking at react-multistep docs, it seems that they don't provide a way to update steps array and don't allow you to pass additional props to form components.
I see two ways for you to fix the problem, considering you don't want to introduce a state management library like redux or mobx:
1. Use React's Context API to pass the state and update function to the form components
2. Create a fork of react-multistep to make it pass the props you pass to MultiStep component down to the form components - the library is pretty small (just 1 small component basically). This way you will be able to use component's state.
Solution with Context API:
import React, { Component } from "react";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import MultiStep from "react-multistep";
import { Route, NavLink, HashRouter } from "react-router-dom";
import newdevice from "./new-device";
export const StepsContext = React.createContext();
class Home extends Component {
state = {
steps: [
{ name: "Job", component: <StepOne /> },
{ name: "device 1", component: <StepTwo /> }
]
};
addStep = newStep => {
this.setState(prevState => ({
steps: [...prevState.steps, newStep]
}));
};
render() {
const { steps } = this.state;
return (
<StepsContext.Provider value={{ steps, addStep: this.addStep }}>
<div>
<h1>Start a New Job</h1>
<div className="container">
<div>
<MultiStep steps={steps} />
</div>
<div className="container app-footer">
<h6>Press 'Enter' or click on progress bar for next step.</h6>
</div>
</div>
</div>
</StepsContext.Provider>
);
}
}
export default Home;
Then, in your form components:
import { StepsContext } from "./Home";
const AddStepComponent = () => (
<StepsContext.Consumer>
{({ addStep }) => (
<button
onClick={() => addStep({ name: "device 2", component: <StepThree /> })}
>
Add a new step
</button>
)}
</StepsContext.Consumer>
);

Onclick button I want to render multiple components in ReactJS?

I have made Matchinfo component and Navbar component. When I click on view stats button it should render Navbar(so that we can navigate back to home page and vice-versa) and Matchinfo component.
Content.js :
import React, { Component } from 'react';
import './content.css';
class Content extends Component {
constructor(props){
super(props);
this.state = {
matches:[],
loading:true
};
}
componentDidMount(){
fetch('api/matches')
.then(res => res.json())
.then(res => {
console.log(res)
this.setState({
matches:res.slice(0,16),
loading:false
})
})
}
renderMatches() {
return this.state.matches.map(match => {
return (
<div class="col-lg-3">
<div id="content">
<p class="match">MATCH {match.id}</p>
<h4>{match.team1}</h4>
<p>VS</p>
<h4>{match.team2}</h4>
<div class="winner">
<h3>WINNER</h3>
<h4>{match.winner}</h4>
</div>
<div class="stats">
Button ---> <button type="button" class="btn btn-success">View Stats</button>
</div>
</div>
</div>
);
})
}
render() {
if (this.state.loading) {
return <img src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif" />
}
return (
<div>
<div class="row">
{this.renderMatches()}
</div>
</div>
);
}
}
export default Content;
On button click it should render 2 different components how can I do that ?
see below component which must be rendered :
Navbar component:
import React, { Component } from 'react';
class Navbar extends Component {
render() {
return (
<div>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">Cricket App</a>
</div>
<ul class="nav navbar-nav">
<li>Home</li>
</ul>
</div>
</nav>
</div>
);
}
}
export default Navbar;
Matchinfo component:
import React, { Component } from 'react';
class Matchinfo extends Component {
constructor(props){
super(props);
this.state = {
info:[],
loading:true
};
}
componentDidMount(){
fetch('api/match/:match_id')
.then(res => res.json())
.then(res => {
console.log(res)
this.setState({
info:res,
loading:false
})
})
}
render() {
if (this.state.loading) {
return <img src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif" />
}
const {info} = this.state;
return (
<div>
<p class="match">MATCH {info.id}</p>
<h4>{info.team1}</h4>
<p>VS</p>
<h4>{info.team2}</h4>
<div class="winner">
<h3>WINNER</h3>
<h4>{info.winner}</h4>
</div>
</div>
);
}
}
export default Matchinfo;
Screenshot for more clarification see view stats button (green button):
I dont think you need to call route for MatchInfo from app.js. Check below updated code. You will see navbar in your page when you click on view stats if you my suggested code in previous post. It should work
The flow here is your content component displays view stats so within content I am calling MaTchInfo component by passing matchId as props and MatchInfo component passing that matchId to fetch api call in componentDidMount. Thats all.
import React, { Component } from 'react';
import './content.css';
import MatchInfo './components/MatchInfo'
class Content extends Component {
constructor(props){
super(props);
this.state = {
matches:[],
loading:true,
callMatchInfo: false,
matchId: ''
};
this.viwStats = this.viwStats.bind(this);
}
componentDidMount(){
fetch('api/matches')
.then(res => res.json())
.then(res => {
console.log(res)
this.setState({
matches:res.slice(0,16),
loading:false
})
})
}
viwStats(matchId){
this.setState({
callMatchInfo: true,
matchId: matchId
})
}
renderMatchInfo(){
<MatchInfo matchId={this.state.matchId} />
}
renderMatches() {
return this.state.matches.map(match => {
return (
<div class="col-lg-3">
<div id="content">
<p class="match">MATCH {match.id}</p>
<h4>{match.team1}</h4>
<p>VS</p>
<h4>{match.team2}</h4>
<div class="winner">
<h3>WINNER</h3>
<h4>{match.winner}</h4>
</div>
<div class="stats">
<button type="button" onClick={this.viwStats(match.id)} class="btn btn-success">View Stats</button>
</div>
</div>
</div>
);
})
}
render() {
if (this.state.loading) {
return <img src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif" />
}
return (
<div>
<div class="row">
{this.renderMatches()}
</div>
<div class="row">
{this.state.callMatchInfo ? this.renderMatchInfo() : ''}
</div>
</div>
);
}
}
export default Content;
AND in your Matchinfo component: this.props.matchId as request param in fetch call
componentDidMount(){
fetch(`api/match/${this.props.matchId}`)
.then(res => res.json())
.then(res => {
console.log(res)
this.setState({
info:res,
loading:false
})
})
}

Why is my component not effectively receiving props?

I keep getting a message that the item I'm trying to access via props is undefined. Can you please tell me why it's not working?
Here is where the instance where the props are attempting to be passed... I'm specifically talking about the tellus and yourTrial props.
import React from 'react'
import Info from './components/Info'
import Upsell from '../../../general/components/order/components/Upsell'
import FormTwo from '../../../general/components/forms/FormTwo'
import Footer from '../../../cbd-desktop/components/layout/Footer'
export default class Order extends React.Component {
render() {
return (
<div>
<div>
<div style={styles.header}>
<img style={styles.leaf} src="./resources/desktop-img/leaves_top.png" />
<img style={styles.logo} src="./resources/desktop-img/logo_white.png" />
</div>
<div style={styles.wrapper}>
<div style={styles.leftWrapper}>
<Info />
<Upsell styles={upsellStyles} />
</div>
<FormTwo styles={formStyles} tellus="Tell us where to send" yourTrial="YOUR TRIAL BOTTLE"} />
</div>
</div>
<Footer style={styles.footer}/>
</div>
)
}
}
And here is where I am trying to display these values on the child... towards the top in two h2s
import React from 'react'
import { connect } from 'react-redux'
import { stepTwoSubmit, saveBillingData } from
'../../actions/formStepTwoActions'
import { addReceiptProduct } from '../../actions/receiptActions'
import FormTwoInputs from './components/FormTwoInputsComponent.jsx'
import Throbber from '../throbber/Throbber'
import FormWarning from './components/FormWarningComponent.jsx'
import Button from '../../../cbd-desktop/components/layout/Button'
const mapStateToProps = state => ({
state:state,
config:state.config,
downsellProduct:state.downsell.downsellProduct || {},
receiptProducts:state.receipt.receiptProducts || []
})
const mapDispatchToProps = {
stepTwoSubmit,
saveBillingData,
addReceiptProduct
}
#connect(mapStateToProps, mapDispatchToProps)
export default class FormTwo extends React.Component {
constructor(props) {
super(props)
componentWillReceiveProps(nextProps) {
let formTwoResponse = nextProps.state.stepTwo.formTwoResponse
this.checkOrderStatus(formTwoResponse)
}
componentDidMount() {
this.fillMainOrder()
this.calculateViewers()
this.calculateTimer()
}
render() {
let { state, props, inputs, onInputFocus, saveInputVal, styles } = this,
CustomTag = props.labels ? 'label' : 'span',
{ submitting, formWarning } = state,
{ invalidInputID, text, visible } = formWarning
return (
<div style={styles.formWrapper}>
<p style={styles.yellowBanner}>{this.state.viewers} people viewing this product right now</p>
<div style={styles.formInnerWrapper}>
<div style={styles.headerWrapper}>
<h2 style={styles.header}>{this.props.tellus}</h2>
<h2 style={styles.header}>{this.props.yourTrial}</h2>
</div>
<div style={styles.weAccept}>
<p style={styles.weAcceptText}>We Accept:</p>
<img style ={styles.cardImage} src="resources/desktop-img/cards.png" />
</div>
<form onSubmit={this.submit}>
<FormTwoInputs onInputFocus={onInputFocus} saveInputVal={saveInputVal} CustomTag={CustomTag} styles={styles} />
<FormWarning visible={visible} invalidInputID={invalidInputID} text={text} />
<Button style={styles.button} buttonText="RUSH MY TRIAL" />
</form>
</div>
<img src="resources/desktop-img/secure.png" />
<div style={styles.urgencyWrapper}>
<div style={styles.urgencyTextWrapper}>
<span style={styles.redText}>{this.state.viewers} people are viewing this offer right now -</span>
<span style={styles.blueText}>{this.state.counter}</span>
</div>
<p style={styles.blueText}>Claim Your Bottle Now</p>
</div>
<Throbber throbberText='Confirming your shipment...' showThrobber={submitting} />
</div>
)
}
}

Pass map's argument to function in ReactJS

I am trying to make a todoList by ReactJS. I want to delete an item by its id, but when I console.log(id), it returns undefined. Here is my code
App.js:
import React, { Component } from 'react';
import './App.css';
import Header from './Components/header';
import InputTodo from './Components/todoInput';
class App extends Component {
constructor(props){
super(props);
this.state={
todos:[
{id:0,text:'Make dinner'},
{id:1,text:'Fold the laundary'},
{id:2,text:'Do homework'}
]
}
}
addHere=(text)=>{
this.setState({
todos:this.state.todos.concat([text])
})
}
removeHere=(id)=>{
console.log(id);
// let arr=this.state.todos;
// let index=arr.findIndex((x)=>x.id===id);
// console.log(index);
}
render() {
return (
<div className='todo-wrapper'>
<Header/>
<InputTodo todoText='Type Here...' addTodo={this.addHere}/>
<div>
{this.state.todos.map((value,key)=>{
return (
<div className='row myList' key={key}>
<p className='col-xs-10'> {value.text}-{value.id} </p>
<button className='btn btn-danger pull-right col-xs-2' onClick={this.removeHere(value.id)}>Delete</button>
</div>
)})}
</div>
</div>
);
}
}
export default App;
The following is InputTodo.js:
import React, {Component} from 'react';
import '../App.css';
export default class InputTodo extends Component{
constructor(props){
super(props);
this.state={
todoInput:{
id:2,
text:''
}
}
}
handleSubmit=(e)=>{
if(this.refs.title.value===''){
alert('You must input something');
}
else{
this.state.todoInput={
id:this.state.todoInput.id+1,
text:this.refs.title.value
};
this.setState(this.state);
this.props.addTodo(this.state.todoInput);
this.refs.title.value='';
}
e.preventDefault();
}
render(){
return(
<form className='input-group' onSubmit={this.handleSubmit}>
<input type='text' ref="title" className='form-control'placeholder={this.props.todoText}/>
<span className='input-group-btn'>
<input type='submit' value='Submit' className='btn btn-primary' />
</span>
</form>
);
}
}
While FuzzyTree's answer will work, a cleaner approach would be extracting the todo item's JSX into its own component. This would have the added benefit of not creating a new function for the button's onClick prop every time App's render function gets called.
The component might look like this:
// TodoItem
class TodoItem extends Component {
handleRemove = () => this.props.onRemove(this.props.id)
render() {
return (
<div className='row myList'>
<p className='col-xs-10'> {this.props.text}-{this.props.id} </p>
<button className='btn btn-danger pull-right col-xs-2' onClick={this.handleRemove}> Delete </button>
</div>
)
}
}
// App render
render() {
return (
<div className='todo-wrapper'>
<Header/>
<InputTodo todoText='Type Here...' addTodo={this.addHere}/>
<div>
{this.state.todos.map(({ id, text }, key) =>
<TodoItem key={key} id={id} text={text} onRemove={this.removeHere} />
)}
</div>
</div>
);
}

Resources