Delete row from table ( Cannot read property 'data' of null) - reactjs

I am new to Gatsbyjs and reactjs and i still don't understand much of how props.and states work.
I am building this simple application that gets a list of customers from an API and a list of tasks for each customer.
I am using Reach/Router to render the components. Everything works as expected as for as to displaying a table with a list of customers and when i click on a customer a new page is rendered which shows a list of task for that said customer.
Now, i am trying to make the table editable. I am starting by trying to simply delete some rows. this is where I am stuck.
edit
I believe that i get the error of Uncaught TypeError: Cannot read property 'data' of null because i am trying to access data (state) which is managed by the fetch.js class. How can I pass the data (state) to the ClientTasks class?
---
I have the following code
index.js
import React from "react"
import { createHistory, LocationProvider } from '#reach/router'
import createHashSource from '../utils/hash-source'
import { ToastContainer } from 'react-toastify';
import "../css/main.css"
import "../css/materialize.css"
import "../css/blackjack.css"
import '../../node_modules/react-toastify/dist/ReactToastify.css';
import { NavBar } from '../components/navBar'
import { Main } from '../components/main'
const isClient = typeof window !== 'undefined' && window;
let source
let history
if (typeof window !== `undefined` ) {
source = createHashSource()
history = createHistory(source)
}
class App extends React.Component {
render() {
return (
<LocationProvider history={history}>
<div className="app" >
<NavBar/>
<Main/>
<ToastContainer position="bottom-right"/>
</div>
</LocationProvider>
)
}
}
export default App
main.js
import React from 'react'
import { Router } from "#reach/router"
import { Home } from '../components/home'
import { Customers } from './customers';
import { ClientTasks } from './clientTasks1';
const Main = () => (
<main className="main container">
<Router className="row">
<Home path='/'/>
<Customers path='customers'/>
<ClientTasks path="customers/tasks/:customerId"/>
</Router>
</main>
)
export { Main }
fetch.js
I am using this file to work as a single class component that helps me fetch data for the tables I am displaying (customers and tasks). It works fine as is. there is probably better ways to do it, but for now this is how i am doing it. Any pointers are welcome.
import React, { Component } from 'react'
const axios = require('axios')
class Fetch extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
isLoading: true,
error: null,
};
}
componentDidMount() {
this.setState({ isLoading: true });
axios.get(this.props.url)
.then(result => this.setState({
data: result.data,
isLoading: false
}))
.catch(error => this.setState({
error,
isLoading: false
}));
}
render() {
return this.props.children(this.state);
}
}
export default Fetch
Customers.js
This where i display my customers' table. I have links on each customer and with the help of "reach/router" render the cutomer tasks table.
import React, { Component } from 'react'
import { Link } from "#reach/router"
import Fetch from './fetch'
import { UploadForm } from './upLoadtoS3'
import { AnimatedDiv } from './AnimatedDiv'
const APIURL = `https://SomeAIPURL`
let APIQuery = `customers`
const Customers = () => (
<Fetch url={APIURL + APIQuery}>
{({ data, isLoading, error }) => {
if (!data) {
return (
<div className="progress">
<div className="indeterminate"></div>
</div>)
}
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return (
<div className="progress">
<div className="indeterminate"></div>
</div>)
}
return (
<AnimatedDiv className='col m12 s12'>
<h1> Client List </h1>
<table className='highlight'>
<thead>
<tr>
<th>#</th>
<th>Client ID</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{data.map((customer, i) => (
<tr key={customer.customerid}>
<td>{i + 1}</td>
<td>
<Link to={`tasks/${customer.customerid}`}>{customer.customerid}</Link>
</td>
<td>{customer.enabled}</td>
</tr>
))}
</tbody>
</table>
<UploadForm></UploadForm>
</AnimatedDiv>
);
}
}
</Fetch>
)
export { Customers }
ClientTasks.js
Fetch is called once again and populates the table with data pulled from the API.
I used another file to define the contents of this table. listTasks.js
import React, { Component } from 'react'
import { Link } from "#reach/router"
import Fetch from './fetch'
// import Delete from './delete'
import { AnimatedDiv } from './AnimatedDiv'
import DisplayList from './listTasks'
const APIURL = `https://SomeAIPURL`
const CUSTOMERQUERY = `tasks?customerid=`
const TASKQUERY = `&taskid=`
class ClientTasks extends React.Component {
handleDelete(taskToBeDeleted) {
// console.log(taskToBeDeleted);
let newData = this.state.data.filter((_data) => {
return _data != taskToBeDeleted
});
this.setState({ data: newData })
}
render() {
let customerId = this.props.customerId
return (
<Fetch url={APIURL + CUSTOMERQUERY + customerId}>
{({ data, isLoading, error }) => {
if (!data) {
return (
<div className="progress">
<div className="indeterminate"></div>
</div>)
}
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return (
<div className="progress">
<div className="indeterminate"></div>
</div>)
}
else {
return (
<AnimatedDiv className='col m12 s12'>
<h1>{customerId} Tasks</h1>
<table id="customerList" className="highlight" >
<thead>
<tr>
<th>Task ID</th>
<th>Qty</th>
<th>Asset Category</th>
<th>Asset</th>
<th>Location</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<DisplayList handleDelete={this.handleDelete.bind(this)} data={data}/>
</table>
<Link to='/customers'> Back to Client List ... </Link>
</AnimatedDiv>
)
}
}
}
</Fetch>
)
}
}
export { ClientTasks }
>
Here i have an onClick function that runs handleDelete inside the ClientTasks.js file.
If i console.log(taskstobedeleted) then the console shows me the contents of the row that i am trying to delte. This is as for as i get. Then i am trying to use the following function in clientasks.js but i get an error in the console the says Cannot read property 'data' of null) and i believe that is because the props of data are the accessible that this clientTasks class.
I am still learning and there are better ways to structure the code, but i have hit a wall and i don't want to rebuild the app from scratch, if i did i would probably use redux, but that is another lesson for later. I figured that this project of mine is small enough and does not need Redux yet.
Can somehow give me any pointers of how to delete the rows of my table?
handleDelete(taskToBeDeleted) {
// console.log(taskToBeDeleted);
let newData = this.state.data.filter((_data) => {
return _data != taskToBeDeleted
});
this.setState({ data: newData })
}
Listtasks.js
import React from 'react'
import { Icon } from 'react-icons-kit'
import { ic_delete_forever } from 'react-icons-kit/md/ic_delete_forever'
export default class DisplayList extends React.Component {
render() {
return (
<tbody>
{this.props.data.map((task) => (
<tr key={task.taskid}>
<td>{task.taskid}</td>
<td>{task.qty}</td>
<td>{task.category}</td>
<td>{task.asset}</td>
<td>{task.location}</td>
<td>{task.enabled}</td>
<td>
<button style={{ padding: '0px', background: 'transparent', border: '0', cursor: 'pointer' }} onClick={this.props.handleDelete.bind(this, task)} >
<Icon style={{ color: 'red' }} icon={ic_delete_forever} />
</button>
</td>
</tr>
))}
</tbody>
)
}
}

I think your function should be like this:
handleDelete(taskToBeDeleted) {
// console.log(taskToBeDeleted);
let newData = this.state.data.filter((_data) => _data.taskid != taskToBeDeleted.taskid});
this.setState({ data: newData })
}
If your console is giving you object that you wanted, then, first, you do not need return in arrow function since return is implicit. Second, all of your tasks have been returned because you were asking for two objects are they the same which will always be false even if they have the same key value pairs inside of them. They have different references. That is why I used id since I suppose the value of that key is number and you can evaluate that with operator == or !=

Related

ReactJS: fake path issue but I need the correct file path

I have to upload an image on S3 bucket and I need a file object with proper path. Currently when I upload the image then I get the following in console
C:\fakepath\download (1).jpeg
and when it is uploaded on s3 bucket then I am getting 'undefined' as below:
https://test-profile-images.s3.amazonaws.com/undefined
Basically, I have created a repeater. Below is my code.
how can I fix this?
import React, {useState} from "react"
const BrandDetails = (props) => {
return (
props.brandDetail !== '' ?
props.brandDetail.map((val, idx) => {
let model = ` model-${idx}`
return (
<tr key={val.index}>
<td>
<input className="form-control" defaultValue={val.model} name="model" data-id={idx} id={model} accept="image/x-png,image/jpeg, image/jpg, image/webp" type="file" required/>
</td>
</tr >
)
})
: null
)
}
export default BrandDetails
import React, {Fragment, Component} from 'react'
import Breadcrumb from '../../common/breadcrumb'
import Brand from '../brands'
import BrandDetails from './brandRepeater'
import { uploadImage } from '../../common/imageUploader'
class CreateBrand extends Component {
constructor(props) {
super(props);
this.brandObj = new Brand();
this.state = {
brandDetail: [{ index: Math.random(), model:"" }],
}
}
onSubmit = (e) => {
let params = ''
this.state.brandDetail.map( detail => {
console.log('image url : ',detail['model'])
uploadImage(detail['model'])
.then(res => {
console.log('uploaded url', res)
let asset = {
'src': res.location,
'width': 400,
'height': 400,
'main': true
}
})
})
}
addNewRow = (e) => {
this.setState((prevState) => ({
brandDetail: [...prevState.brandDetail, { index: Math.random(), model:""}],
}));
}
deteteRow = (index) => {
this.setState({
brandDetail: this.state.brandDetail.filter((s, sindex) => index !== sindex),
});
}
clickOnDelete(record) {
this.setState({
brandDetail: this.state.brandDetail.filter(r => r !== record)
});
}
render() {
return {
<Fragment>
<table className="table mt-3">
<thead>
<tr>
<th>Model</th>
</tr>
</thead>
<tbody>
<BrandDetails add={this.addNewRow} delete={this.clickOnDelete.bind(this)} brandDetail={brandDetail} flag={flag} />
</tbody>
</table>
</Fragment>
}
}
}

Search functionality not working consistently in React

I have created a React app using this API and then tried to add a search functionality. Everything is working fine, but sometimes I am not able to see the result of the search. For example, if you look at this screenshot you will be able to see what I am trying to say. Also, I want to ignore the case sensitivity and would like to get the exact result irrespective of its case. I tried to convert the searched term and the countries into uppercase but that was not giving the correct results.
App.js:
import React, { Component } from 'react'
import Result from './Result';
import Form from 'react-bootstrap/Form';
export default class App extends Component {
constructor(){
super();
this.state = {
data: [],
searchText:'',
searchResult:[],
isSearch:false
}
this.onSearchChange=this.onSearchChange.bind(this);
// this.fetchSearchResult=this.fetchSearchResult.bind(this);
}
onSearchChange= (e) =>{
console.log("search change "+this.state.searchText)
this.setState({
searchText:e.target.value,
isSearch:!this.state.isSearch
})
console.log("api data"+this.state.data)
}
/* fetchSearchResult= () =>{
console.log(this.state.searchText)
console.log("inside fetch")
let store= this.state.data.map(item=>{
let {country}=item
return(country)
})
console.log(store)
var areEqual = store.includes(this.state.searchText);
console.log(this.state.areEqual)
return (areEqual)?
store:'not matched'
// return store;
} */
componentDidMount() {
const url =
'https://corona.lmao.ninja/countries?sort=country'
fetch(url)
.then(result => result.json())
.then(result => {
this.setState({
data: result,
})
})
}
render() {
return (
<div>
<Form.Group>
<Form.Label>Search</Form.Label>
<Form.Control value={this.state.searchText}onChange={this.onSearchChange} type="text" placeholder="Enter country" />
</Form.Group>
<Result data={this.state.data}
toSearch={this.state.searchText}
searchCheck={this.state.isSearch}
searchValue={this.state.searchText}/>
</div>
)
}
}
Result.js
import React from 'react'
import Table from 'react-bootstrap/Table';
const Result = (props) => {
console.log('props value is:'+props.data)
let {searchCheck, searchValue}=props;
let update=props.data.reverse().map((item)=>{
const { countryInfo, country, cases, deaths, recovered, active, casesPerOneMillion} = item;
return(
(searchCheck)?country.includes(searchValue)?
<tbody>
<tr key={countryInfo._id}>
<td><img style={{height:'25px',width:'50px'}}src={countryInfo.flag}/></td>
<td>{country}</td>
<td>{cases}</td>
<td>{active}</td>
<td>{recovered}</td>
<th>{casesPerOneMillion}</th>
<td>{deaths}</td>
</tr>
</tbody>:
'':
<tbody>
<tr key={countryInfo._id}>
<td><img style={{height:'25px',width:'50px'}}src={countryInfo.flag}/></td>
<td>{country}</td>
<td>{cases}</td>
<td>{active}</td>
<td>{recovered}</td>
<th>{casesPerOneMillion}</th>
<td>{deaths}</td>
</tr>
</tbody>
)
})
return (
<div>
<Table striped bordered hover variant="dark">
<thead>
<tr>
<th>Flag</th>
<th>Country</th>
<th>Cases</th>
<th>Active</th>
<th>Recovered</th>
<th>Cases per one Million</th>
<th>Deaths</th>
</tr>
</thead>
{update}
</Table>
</div>
)
}
export default Result;
Sandbox link
The problem is with your onSearchChange function. For every onchange in the input field, you are trying to reset the isSearch state. Due to this the search becomes false at odd intervals resulting in not filtering the search results. Hence, you are seeing the entire list of countries though there is some search term in the search box. You don't need that isSearch at all. If there is any text in the search box, then filtering is done else it displays the entire list.
Here is a working demo of this - https://codesandbox.io/s/cool-poitras-tuu55
Hope this helps!

ReactJs does not generate data into view after calling api

I just found out about reactjs, I do not understand why my code is not error but it can not render the data to view.
I tried the test function to display. it works normally,
But in the getAllProducts function, after calling the api, it seems impossible to update the html code on my page.
What was wrong with me?
Here is my code:
import React from 'react';
class ListObject extends React.Component {
getAllProducts() {
fetch("http://5bd054ce142d360013a172f3.mockapi.io/api/products")
.then(res => res.json())
.then((result) => {
// return (<h1>Why not display????</h1>);
result.map(
(product, i) => {
return <TableRow key={i} data={product} />
}
)
},
(error) => {
return "errrr";
}
)
}
test() {
return (<h1>Hello World</h1>);
}
render() {
return (
<div className="container-fluid">
<table className="table table-hover">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Avatar</th>
<th>Created At</th>
</tr>
</thead>
<tbody>
{this.getAllProducts()}
</tbody>
</table>
{this.test()}
</div>
);
};
}
class TableRow extends React.Component {
render() {
return (
<tr>
<td>{this.props.data.id}</td>
<td>{this.props.data.name}</td>
<td>{this.props.data.avatar}</td>
<td>{this.props.data.createdAt}</td>
</tr>
);
};
}
export default ListObject
You seem to have got it all wrong. In React, you need to render data based on component's state and props. So, you must do something like this:
class YourComponent extends React.Component {
getAllProducts() {
// you can handle a "loading" state as well
this.setState({isLoading: true});
fetch("http://example.com/api/products")
.then(res => res.json())
.then(
(result) => {
this.setState({
products: result,
isLoading: false,
});
},
(error) => {
return this.setState({hasError: true, error})
}
);
}
}
componentDidMount() {
fetchAllProducts();
}
render() {
const {products, isLoading, hasError} = this.state;
if (hasError) {
return (
<p>Something bad happened</p>
);
}
if (isLoading) {
return (
<p>Hey, we're fetching data...</p>
);
}
return (
<table>
{products.map(p => <TableRow ... />)
</table>
)
}
}
NOTE: I've used a few concepts that you should know about, so here are some docs:
Read about componentDidMount() here
We can declare special methods on the component class to run some code when a component mounts and unmounts. The componentDidMount() method runs after the component output has been rendered to the DOM.
Read about state here
Change your getAllProducts and add state object to the component like mentioned below. API call is asynchronous so you cannot return it directly. what you can do is use component state. And make the api call in componentDidMount to get the api data.
class ListObject extends React.Component {
state = {
result: []
};
componentDidMount() {
this.getAllProducts();
}
getAllProducts() {
return fetch("https://5bd054ce142d360013a172f3.mockapi.io/api/products")
.then(res => res.json())
.then(result => {
// return (<h1>Why not display????</h1>);
this.setState({
result
});
})
.catch(e => {
//dispatch some action to showcase error or
//make state update using setState to show error
return null;
});
}
getProductListUI = () => {
const { result } = this.state;
return result.map((product, i) => <TableRow key={i} data={product} />);
};
test() {
return <h1>Hello World</h1>;
}
render() {
return (
<div className="container-fluid">
<table className="table table-hover">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Avatar</th>
<th>Created At</th>
</tr>
</thead>
<tbody>{this.getProductListUI()}</tbody>
</table>
{this.test()}
</div>
);
}
}
class TableRow extends React.Component {
render() {
return (
<tr>
<td>{this.props.data.id}</td>
<td>{this.props.data.name}</td>
<td>{this.props.data.avatar}</td>
<td>{this.props.data.createdAt}</td>
</tr>
);
}
}
ReactDOM.render(<ListObject />, document.getElementById("root"));
Here is the codepen link working : working codepen link
Feedbacks welcome Thanks

How can I update the this.state.songs to songsList

I cant update the state songs which needs to get values from songsList . How can I update the songs to songsList ? Is it anything to do with the component life cycle ? While running the below code , 'songsList is undefined' error throws up . const songList is in the render .
import React, { Component } from 'react';
import logo from './components/Logo/box8.png';
import './App.css';
import SearchBox from './components/SearchBox/SearchBox';
import SongCards from './components/SongCards/SongCards';
import 'tachyons';
import axios from 'axios';
class App extends Component {
state = {
songs : [],
searchField: '',
entries: []
};
componentDidMount() {
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then(response =>
{this.setState({ entries: response.data.feed.entry });
});
}
onSearchChange=(event)=>{
this.setState({songs : songsList})
this.setState({searchField : event.target.value})
const filteredSongs = this.state.songs.filter(song =>{
return song.title.toLowerCase().includes(this.state.searchField.toLowerCase())
});
}
render(){
const songsList = this.state.entries.map(entries => {
return (
<SongCards
key={entries.id.label}
artist={entries["im:artist"].label}
image={entries["im:image"][2].label}
link={entries.id.label}
price={entries["im:price"].label}
date={entries["im:releaseDate"].label}
title={entries.title.label}
/>
);
});
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange= {this.onSearchChange}/>
{songsList}
</div>
);
}
}
export default App;
Appreciate all your responses . I made it finally .
import React, { Component } from 'react';
import logo from './components/Logo/box8.png';
import './App.css';
import SearchBox from './components/SearchBox/SearchBox';
import Albums from './components/Albums/Albums';
import Scroll from './components/Scroll/Scroll';
import 'tachyons';
import emoji from 'emoji-dictionary';
import axios from 'axios';
class App extends Component {
state = {
show:false,
songs : [],
searchField: '',
};
componentDidMount() {
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then(response =>
{this.setState({songs:response.data.feed.entry });
});
}
itunesPageLoader=()=>{
this.setState({show:false})
}
onSearchChange=(event)=>{
this.setState({searchField : event.target.value})
}
render(){
const filteredSongs = this.state.songs.filter(song =>{
return
song.title.label.toLowerCase().includes(this.state.searchField.toLowerCase())
})
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange= {this.onSearchChange}/>
<Scroll >
<Albums songs={filteredSongs}/>
</Scroll>
<footer className="pv4 ph3 ph5-m ph6-l red">
<small className="f6 db tc">© 2018 <b className="ttu">Box8 Inc</b>., All
Rights Reserved</small>
<div className="tc mt3">
{`Made with ${emoji.getUnicode("purple_heart")} by Renjith`}
</div>
</footer>
</div>
);
}
}
export default App;
Try this. You are actually assigning songsList to songs using setState but the songsList doesn’t exist in onSearchChange. To push searched value to an array you need to push event.target.value to songs array
Try with below corrected code
onSearchChange=(event)=>{
this.setState(prevState => ({songs : [...prevState.songs, event.target.value]}));
this.setState({searchField : event.target.value})
const filteredSongs = this.state.songs.filter(song =>{
return song.title.toLowerCase().includes(this.state.searchField.toLowerCase())
});
}
You have mentioned that this.state.entries is an Object.
If this is true, then yo can't perform .map on it as .map is an Array method.
You can however use Object.entries to get an array of [key,value] pairs of this.state.entries.
Object.entries(this.state.entries).map(([key,value]) => ...)
Simple running example:
const object1 = { foo: 'this is foo', baz: "this is baz" };
Object.entries(object1).map(([key,value]) => console.log(`key: ${key}, value: ${value}`));
So i will do something like this:
const IN_PROGRESS = 'IN_PROGRESS';
const SUCCESS = 'SUCCESS';
class App extends Component {
state = {
songs : null,
entries: null,
status: null
};
componentDidMount() {
this.setState({status: IN_PROGRESS});
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then({data} => {
const songs = data.feed.entry;
this.setState({entries: songs});
this.setState({songs});
this.setState({status: SUCCESS});
});
}
onSearchChange = ({target}) => {
const {value} = target;
const songs = this.state.entires.filter(song =>
song.title.toLowerCase().includes(value.toLowerCase())
});
this.setState({songs});
}
render() {
const {status, songs} = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange={this.onSearchChange}/>
{
status === IN_PROGRESS &&
(/* you can insert here some kind of loader which indicates that data is loading*/)
}
{
status === SUCCESS && songs.map(entry => {
const {
id, ['im:artist']: artist, ['im:image']: image,
['im:price']: price, ['im:releaseDate']: date, title
} = entry;
return (
<SongCard
key={id.label}
artist={artist.label}
image={image[2].label}
link={id.label}
price={price.label}
date={date.label}
title={entry.title.label}
/>
)
}
}
{
//Here you can display error message if status === FAILURE
}
</div>
);
}
}
When component did mount, I set status into IN_PROGRESS (if you want some kind of loader to show), and data are beeing fetched - axios.get is asynchronous so remember that when data is fetching then render method is already triggered. When data is loaded then in state I hold two variables, entries which holds unfiltered list of songs, and songs which holds filteres songs.
When search is triggered then I filter entires by searched phrase and set into state this filtered array.
Component renders songCards mapping by filtered songs

React - How can I get a re-render to happen in my table?

I have a table which starts off with data from a store which can then be edited by row. When the editing has been completed, a button is clicked which saves the new values and should present them in the table. However, this is not happening for me. I have been stuck on this for a while now and have tried various things but none of them have worked. I think it may be a problem with the state but I can't figure out how to change it to make it work!
The code I have currently is:
Table:
import React from 'react';
import TableWithDataHeader from './TableWithDataHeader.jsx';
import TableWithDataBody from './TableWithDataBody.jsx';
import TableWithDataRowForm from './TableWithDataRowForm.jsx';
import {updateRowHistory} from '../../actions/DALIActions';
import AppStore from '../../stores/AppStore';
export default class TableWithData extends React.Component {
state = {rows: [], isEditing: false, input: null};
componentDidMount() {
let rows = this.state.rows;
rows.push({id: AppStore.getRowId(), cells: AppStore.getCells().historycells});
this.setState({rows});
console.log(rows);
}
handleEdit = (row) => {
this.setState({isEditing: true});
};
handleInputChange = (newCellValuesArray) => {
let input = this.state.input;
input = newCellValuesArray;
this.setState({input});
};
editStop = (row) => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, row_id) => {
let newCellValuesArray = this.state.input;
updateRowHistory(access_token, row_id, newCellValuesArray);
this.setState({isEditing: false});
};
render() {
let {rows, isEditing, input} = this.state;
return (
<div>
<div className="row">
<table className="table table-striped">
<thead>
<TableWithDataHeader />
</thead>
<tbody>
{rows.map(row => this.state.isEditing ?
<TableWithDataRowForm
key={row.id}
cells={row.cells}
editStop={this.editStop.bind(null, row.id)}
handleSubmit={this.handleSubmit}
handleInputChange={this.handleInputChange}
/>
:
<TableWithDataBody
key={row.id}
cells={row.cells}
handleEdit={this.handleEdit.bind(null, row.id)}
/>
)}
</tbody>
</table>
</div>
</div>
);
}
}
Data table starts with:
import React from 'react';
export default class TableWithDataBody extends React.Component {
state = {cells: this.props.cells};
handleEdit = () => {
this.props.handleEdit();
};
render() {
let {cells} = this.state;
return (
<tr>
{cells.map(cell => {
return <td key={cell.id} className="text-center">{cell.contents}</td>
})}
<td>
<button className="btn btn-primary" onClick={this.handleEdit}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>
);
}
}
In-row edit form:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class TableWithDataRowForm extends React.Component {
state = {cells: this.props.cells, newCellValues: []};
onChange = (e) => {
let newCellValues = this.state.newCellValues;
newCellValues[e.target.id] = e.target.value;
this.setState({newCellValues});
console.log(newCellValues);
let newCellValuesArray = [];
for (let key in newCellValues) {
if (newCellValues.hasOwnProperty(key)) {
newCellValuesArray.push({contents: newCellValues[key]});
}
}
console.log(newCellValuesArray);
this.props.handleInputChange(newCellValuesArray);
};
editStop = () => {
this.props.editStop();
};
handleSubmit = (e) => {
e.preventDefault();
let access_token = AppStore.getToken();
let row_id = AppStore.getRowId();
this.props.handleSubmit(access_token, row_id);
};
render() {
let {cells, newCellValues} = this.state;
return (
<tr>
{cells.map(cell => {
return <td key={cell.id} className="text-center"><input type="text" className="form-control" id={cell.id} defaultValue={cell.contents} onChange={this.onChange} /></td>
})}
<td>
<button className="btn btn-default"><i className="fa fa-ban" onClick={this.editStop}></i>Cancel</button>
<button className="btn btn-success"><i className="fa fa-cloud" onClick={this.handleSubmit}></i>Save</button>
</td>
</tr>
);
}
}
Help with examples would be much appreciated, sorry if the code is a bit of a mess right now!
Thanks for your time
I might be missing something, but I don't see where you're editing the rows state? The change handler just changes the input, and you aren't passing the input down to the data table.
The only time I see rows being set is in componentDidMount, which explains why it's being populated, but not changed.

Resources