Search functionality not working consistently in React - reactjs

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!

Related

React refresh the page after delete button

The delete function of my app is working fine, however it requires the user to manually refresh the page after the user click the delete button in order to see the new list of elements in my database. I would like to automatically refresh after the click event. I am using react hooks for this projects. However, I found one solution if I remove useEffect's [] but in my backend it shows, its requesting crazily. I don't know, is it wise to remove useffect's [ ]?
Here is the component where it fetches data from backend and passes the props to another component
import React, { useState, useEffect } from "react";
import axios from "axios";
import Table from "../Table/Table";
import "./Display.css";
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const [searchItem, setsearchItem] = useState({
item: ""
});
const Search = e => {
setsearchItem({ item: e.target.value });
};
useEffect(() => {
axios
.get("/students")
.then(response => {
setState({
students: response.data.students,
count: response.data.count
});
})
.catch(function(error) {
console.log(error);
});
}, []); //If I remove this square brackets, it works
const nameFilter = state.students.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
return (
<div>
<h3 align="center">Student tables</h3>
<p align="center">Total students: {state.count}</p>
<div className="input-body">
<div className="row">
<div className="input-field col s6">
<input placeholder="search student" onChange={Search} />
</div>
</div>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Phone</th>
<th>Email</th>
<th colSpan="2">Action</th>
</tr>
</thead>
{nameFilter.map((object, index) => {
return (
<tbody key={index}>
<Table obj={object} /> //In here I am passing the props to the another component.
</tbody>
);
})}
</table>
</div>
);
};
export default Display;
This is second component which receives the props.
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(console.log("Deleted"))
.catch(err => console.log(err));
};
return (
<React.Fragment>
<tr>
<td>{props.obj.name}</td>
<td>{props.obj.birthday}</td>
<td>{props.obj.address}</td>
<td>{props.obj.zipcode}</td>
<td>{props.obj.city}</td>
<td>{props.obj.phone}</td>
<td>{props.obj.email}</td>
<td>
<Link
to={"/edit/" + props.obj.id}
className="waves-effect waves-light btn"
>
Edit
</Link>
</td>
<td>
<button onClick={removeData} className="waves-effect red btn ">
Remove
</button>
</td>
</tr>
</React.Fragment>
);
};
export default Table;
The [] in the useEffect hook is a dependency array to trigger the effect to run. If you want to trigger the effect (without it going off mercilessly), you can create a new variable that triggers that effect to run.
import React, { useState, useEffect } from "react";
import axios from "axios";
import Table from "../Table/Table";
import "./Display.css";
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const [requestData, setRequestData] = useState(new Date());
const [searchItem, setsearchItem] = useState({
item: ""
});
const Search = e => {
setsearchItem({ item: e.target.value });
};
useEffect(() => {
axios
.get("/students")
.then(response => {
setState({
students: response.data.students,
count: response.data.count
});
})
.catch(function(error) {
console.log(error);
});
}, [requestData]);
const nameFilter = state.students.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
return (
<div>
<h3 align="center">Student tables</h3>
<p align="center">Total students: {state.count}</p>
<div className="input-body">
<div className="row">
<div className="input-field col s6">
<input placeholder="search student" onChange={Search} />
</div>
</div>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Phone</th>
<th>Email</th>
<th colSpan="2">Action</th>
</tr>
</thead>
{nameFilter.map((object, index) => {
return (
<tbody key={index}>
<Table obj={object} setRequestData={setRequestData} />
</tbody>
);
})}
</table>
</div>
);
};
export default Display;
Then you can trigger it from your Table component:
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(() => {
props.setRequestData(new Date());
})
.catch(err => console.log(err));
};
return (
<React.Fragment>
<tr>
<td>{props.obj.name}</td>
<td>{props.obj.birthday}</td>
<td>{props.obj.address}</td>
<td>{props.obj.zipcode}</td>
<td>{props.obj.city}</td>
<td>{props.obj.phone}</td>
<td>{props.obj.email}</td>
<td>
<Link
to={"/edit/" + props.obj.id}
className="waves-effect waves-light btn"
>
Edit
</Link>
</td>
<td>
<button onClick={removeData} className="waves-effect red btn ">
Remove
</button>
</td>
</tr>
</React.Fragment>
);
};
export default Table;
Not sure if helps but you can always remove the item from the current array, so a refresh is not needed, for example you can pass as props a function that receives an id and then filter the students array to exclude the element that matches with that id and then update the state with the new array and count properties, something like this
In your parent:
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const deleteItem = (id) => {
const newStudents = state.students.filter(student => student.id !== id)
const newCount = newStudents.length;
setState({ students: newStudents, count: newCount })
}
// Rest of the code
}
Now pass that function to your child component.
<Table obj={object} deleteItem={deleteItem} />
In the child component just modify your removeData method to add the deleteItem prop:
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(console.log("Deleted"))
.catch(err => console.log(err));
// Now if your request succeeds call the function to remove the item from the students state array
props.deleteItem(props.obj.id);
};
// Rest of the code
}
I know this does not answer your question, but when you're working with react or it is better to do this computations and filters on the app side, like in this case that even though the record was deleted from the db we also removed the record from the student state object and there's no need to refresh the page.
Remember, you're creating a single page application, so we want the nicest experience for the user without refreshing the page for every action the user makes.
Have a look at this
import React, { useState } from "react";
const Display = () => {
const [refresh, setRefresh] = useState(false)
const delete=() => {
// ................. //delete logic
reload ? setRefresh(false) : setRefresh(true) //toggle just to change state
}
useEffect(() => {
}, [reload]); //inject as dependency
}

React js to refresh page after onClick has been called

I've been following one of the MERN stack tutorials online (making a simple todo app), and decided to go off-script a little bit. I wanted to add a button to delete a specific item. The delete function is working fine, however it requires the user to manually refresh the page after they click the delete button in order to see the new list of elements in my database (MongoDB). I'd like the page to automatically refresh after the click event, however I'm not sure where to start. Within the react render there is a table, which references a variable to actually assemble the components of the table - this is where the delete button exists. Here is my code:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
const Todo = props => (
<tr>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_title}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_description}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_responsible}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_priority}</td>
<td>
<Link to={"/edit/"+props.todo._id}>Edit</Link>
</td>
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(console.log("Deleted: " + props.todo._id))
.catch(err => console.log(err))
}
>Delete</button>
</td>
</tr>
)
export default class TodosList extends Component {
constructor(props) {
super(props);
this.state = {todos: []};
}
componentDidMount() {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
})
}
todoList() {
return this.state.todos.map(function(currentTodo, i){
return <Todo todo={currentTodo} key={i} />;
})
}
render() {
return (
<div>
<h3>Todos List</h3>
<table className="table table-striped" style={{ marginTop: 20 }} >
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Responsible</th>
<th>Priority</th>
<th>Action</th>
<th>Remove Item</th>
</tr>
</thead>
<tbody>
{ this.todoList() }
</tbody>
</table>
</div>
)
}
}
Hopefully someone on here can get me pointed in the right direction.
Thanks
You can delete the specific item from TodosList component state after you have successfully deleted the item from Todo component. For that you can
1) add a method in TodosList component.
deleteItemHandler = (id) => {
const updatedTodos = this.state.todos.filter(todo => todo.id !== id);
this.setState({todos: updatedTodos})
}
2) pass the method deleteItemHandler as props to Todo component
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} deleteItem={this.deleteItemHandler} key={i} />;
})
}
3) use it after item is successfully deleted
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => props.deleteItem(props.todo._id))
.catch(err => console.log(err))
}
>Delete</button>
</td>
Another way
Instead deleting item from TodosList component you can also update the state. For that you can
1) add method that updates in TodosList component
updateStateHandler = () => {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
})
}
2) pass the method updateStateHandler as props to Todo component
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} updateState={this.updateStateHandler} key={i} />;
})
}
3) use it after item is successfully deleted
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => props.updateState())
.catch(err => console.log(err))
}
>Delete</button>
</td>
You need to do this
export default class TodosList extends Component {
constructor(props) {
super(props);
this.state = {todos: []};
this.fetchTodos = this.fetchTodos.bind(this);
}
fetchTodos() {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
});
}
componentDidMount() {
this.fetchTodos();
}
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} fetchTodos={this.fetchTodos} key={i} />;
})
}
...
Todo:
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => {
console.log("Deleted: " + props.todo._id);
props.fetchTodos();
})
.catch(err => console.log(err));
}
>Delete</button>
</td>
The authority that renders the table is your TodosList class, so it needs to be told to do the deleting:
class TodosList extends ... {
...
todoList() {
return this.state.todos.map((currentTodo, i) => {
let onDelete = () => {
this.removeItem(i);
};
// NEVER use an array position as key. The key is meant to uniquely
// identify the _item itself_ and is used in DOM diffing. Moving elements
// inside an array does not change those elements in the slightest and only
// requires moving DOM nodes around, but if you use array position as key,
// what you've now done is said that _everything in the DOM node has changed_
// So: don't do that. Use a real, item based, value.
return <Todo todo={currentTodo} key={currentTodo.id} onDelete={onDelete}/>;
// Of course this assumes todo items have an `id` property.
// If they don't, pick another property _on the todo item_ that
// uniquely identifies it.
});
}
removeItem(i) {
let todos = this.state.todos;
todos.splice(i,1);
// This single call now results in all the UI updates that you
// need to have happen: the todo item is no longer in the state,
// and so its DOM node will be removed from the page. And because
// we're now using real keys, React will not touch any of the other
// DOM nodes. The UI update is near-instant.
this.setState({ todos });
}
...
}
Then the individual buttons can call their own onDelete once deleting has happened:
const deleteThisItem = () => {
axios
.delete('http://localhost:4000/todos/'+props.todo._id)
.then(this.props.onDelete())
.catch(err => console.log(err))
};
<button onClick={deleteThisItem}>delete</button>
So the flow is:
TodoList knows all the todo items,
TodoList generates UI for each todo item,
item UI includes a button that will call TodoList's item deletion function, which will update the TodoList state
simply by virtue of removing an item from that state and calling setState with that change, React will render what needs to be rendered.

How to map() when state "selectedOptions2" triggered

i need you to look for bug my code. I know it is something wrong.
import React, { Component } from "react";
import { options1, options2, questions } from "./data";
import Select from "react-select";
class Esensial extends Component {
constructor(props) {
super(props);
this.state = {
selectedOption: [],
selectedOption2: []
};
}
handleChange1 = selectedOption => {
this.setState({ selectedOption, selectedOption2: null });
};
handleChange2 = selectedOption => {
this.setState({ selectedOption2: selectedOption });
};
filteredOptions() {
return options2.filter(o => o.link ===
this.state.selectedOption.value);
}
questionsOptions() {
return questions.filter(
question => question.link === this.state.selectedOption2.value
);
}
render() {
const filteredOptions = this.filteredOptions();
const questionsOptions = this.questionsOptions();
return (
<div>
<h1>UKM Esensial</h1>
<div className="form-group">
<label>Pilih Jenis Upaya Pelayanan Kesehatan</label>
<Select
className="form-control"
isClearable={false}
onChange={this.handleChange1}
options={options1}
value={this.state.selectedOption}
/>
</div>
<div className="form-group">
<label>Pilih Variable</label>
<Select
className="form-control"
isClearable
onChange={this.handleChange2}
options={filteredOptions}
value={this.state.selectedOption2}
/>
</div>
<div>
<table className="table table-striped">
<thead>
<tr>
<th>Sub-Variabel</th>
<th>Sasaran</th>
<th>Capaian</th>
</tr>
</thead>
<tbody>
{questionsOptions.map(q => (
<tr>
<td>{q}</td>
<td>
<input type="number" />
</td>
<td>
<input type="number" />
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
}
export default Esensial;
the code will run, when selectedOption get a value it will filter() the options2, when selectedOption2 get a value it will filter() the questionsOptions, but i don't know the code still crash.. help me please..
is it wrong with the map?
may be because i don't use componentDidUpdate(){}.
somehow when i use componentDidUpdate(), it execute by itself. maybe i use firebaseauth() to authenticate the login system.
This is because of this.state.selectedOption2.value, as you are setting value of selectedOption2 in handleChange1 null. Following can be solutions:
questionsOptions() {
const {selectedOption2} = this.state;
return questions.filter(
question => question.link === selectedOption2 && selectedOption2.value || ''
);
}
From your initial state I am assuming that selectedOption2 is array so you cannot access value as selectedOption2.value you have to access by index e.g selectedOption2[0].value(assuming 0 in the required index)
Your state is a bit confusing, because you've initialized them as arrays, but you'd like to access them as objects. The error message evinces that selectedOption2 doesn't own any property called value.
The answer strongly depends on what you expect to get when the onChange method is effected on.
I don't know your exact data structures, but if you're working with arrays, then the filter functions should be something similar to this:
questionsOptions() {
return questions.filter(
question => {
this.state.selectedOption2.map(option => {
return question === option.value;
});
}
);
}
In the aforementioned example, I assume that selectedOption2 is an array of objects, therefore you should use the map function in order to find the corresponding value that is expected.
By the way, if it's a simple value - i.e. it's a string, integer etc. -, then you should leave the value property on the right side of the comparison in your example.
questionsOptions() {
return questions.filter(
question => question === this.state.selectedOption2
);
}
On the contrary, if it's an array with simple values, then:
questionsOptions() {
return questions.filter(
question => {
this.state.selectedOption2.map(option => {
return question === option;
});
}
);
}
I'd also propose to look into your data structures and think about how the state field should be initialized, since it's quite misleading and causes impediments both for the readers and you.

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

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 !=

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