React runs function repeatedly, but I have not called it - reactjs

I'm using react table (https://github.com/react-tools/react-table) to render a table of expenses. In one column, there should be a button to 'approve' the expense. This is handled like so:
const columns = [
{
Header: "Description",
accessor: "description"
},
{
Header: "Approve",
accessor: d => {
return <button onClick={this.approveExpense(d.id)}>Approve</button>;
},
id: "approved"
}
];
Where the approveExpense function is defined as:
approveExpense = id => {
fetch(`${apiRoot}expenses_pending/`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Token ${this.props.auth.token}`
},
body: JSON.stringify({
id: id
})
}).then(res => {
if (res.status === 200) {
this.setState({
issues: this.state.expenses.filter(expense => expense.id != id)
});
} else {
console.log("Error");
}
});
};
Strangely, however, when the page loads, it behaves as if all of these buttons are being repeatedly pressed, many times per second (until the fans start going crazy and I stop the react server).
Am I doing something stupid?
Full class:
class ExpensePendingAdmin extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
fetch(`${apiRoot}expenses_pending`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Token ${this.props.auth.token}`
}
})
.then(response => response.json())
.then(data => {
console.log(data);
this.setState({
expenses: data
});
});
}
approveExpense = id => {
fetch(`${apiRoot}expenses_pending/`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Token ${this.props.auth.token}`
},
body: JSON.stringify({
id: id
})
}).then(res => {
if (res.status === 200) {
this.setState({
issues: this.state.expenses.filter(expense => expense.id != id)
});
} else {
console.log("Error");
}
});
};
render() {
const columns = [
{
Header: "Description",
accessor: "description"
},
{
Header: "Logged At",
id: "loggedAt",
accessor: d =>
moment(d.expense_incur_datetime).format("HH:mm - ddd d/M/YYYY")
},
{
Header: "Amount",
accessor: d => `£${d.amount}`,
id: "amount"
},
{
Header: "Approve",
accessor: d => {
return <button onClick={this.approveExpense(d.id)}>Approve</button>;
},
id: "approved"
},
{
Header: "Paid",
accessor: d => {
console.log(d);
return d.is_unpaid ? "No" : "Yes";
},
id: "paid"
}
];
return (
<div className="container-fluid">
{this.state.expenses ? (
<>
<div className="row">
<div className="col text-center">
<h2>Pending Expenses</h2>
</div>
</div>
<div className="row">
<div className="col">
<ReactTable
data={this.state.expenses}
columns={columns}
minRows="0"
minWidth="50"
showPagination={false}
/>
</div>
</div>
</>
) : (
"LOADING"
)}
</div>
);
}
}

Methods in event handlers in JSX do not require parentheses, if you want to pass down a parameter simply wrap it in a function:
onClick={() => this.approveExpense(d.id)}

All other answers are right, however you could also improve the syntax of your function calling by making your function will multiple parameter sets :
approveExpense = id => ev => {
And then setting your accessor rendering like this :
accessor: d => <button onClick={this.approveExpense(d.id)}>Approve</button>;
The function : this.approveExpense(d.id) will return another function capable of receiving another parameter (here, the click event names ev) and will work like a charm

You need to pass the approveExpense() function as a callback function like, so it will only trigger when you click.
<button onClick={(d) => this.approveExpense(d.id)}>Approve</button>

The problem with your code is that you are passing the event handler in the wrong way:
return <button onClick={this.approveExpense(d.id)}>Approve</button>;
by using directly this.approveExpense(d.id) inside your JSX code you are telling javascript to execute that function as soon as the interpreter reads it.
Instead you should proxy the function execution on the click, like this:
return <button onClick={(e) => {this.approveExpense(d.id)}}>Approve</button>;
For more in depth explanation on how to pass function to components in React you can check https://reactjs.org/docs/faq-functions.html

Related

setState is changed but the variable assigned to it can not get the updated value

developing a chat app with a loader to wait until receiving the response. According to console.log in the app the isLoading state changes correctly however inside the receiver's object the isLoaded key is not gonna getting update and only show the initial value of the assigned state. As a result the loader doesn't show in the UI. You can find the code below.
const [messagesList, setMessagesList] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [currentMessage, setCurrentMessage] = useState("");
const handleSend = () => {
if (currentMessage !== "") {
setIsLoading(true);
setMessagesList([
...messagesList,
{
dataType: "text",
item: {
id: uuidv4(),
type: "sender",
message: currentMessage,
},
},
]);
axios
.post(`${BASE_URL}`, {
data: currentMessage,
})
.then((response) => {
setIsLoading(false);
setMessagesList([
...messagesList,
{
dataType: "text",
item: {
id: uuidv4(),
type: "sender",
message: currentMessage,
},
},
{
dataType: "text",
item: {
id: uuidv4(),
type: "reciever",
isLoaded: isLoading,
message: response.data.data.answers
},
},
]);
setCurrentMessage("");
})
.catch((error) => {
setIsLoading(true);
setMessagesList([
...messagesList,
{
dataType: "text",
item: {
id: uuidv4(),
type: "sender",
message: currentMessage,
},
},
{
dataType: "text",
item: {
id: uuidv4(),
type: "reciever",
isLoaded: isLoading,
message: "something went wrong"
},
},
]);
setCurrentMessage("");
setIsLoading(false);
});
setCurrentMessage("");
}
};
return (
<>
<textarea
type="text"
placeholder="ask me ..."
rows={1}
cols={1}
onChange={(e) => setCurrentMessage(e.target.value)}
value={currentMessage}
autoFocus
/>
<button onClick={ ()=> handleSend }> Sned </button>
{messagesList.map(({ dataType, item }) => {
if (dataType === "text") {
if (item.type === "sender") {
return (
<p style={{ color: "red" }}>{item.message}</p>
);
}
if (item.type === "reciever") {
if (item.isLoaded) {
return <p> laoding</p>;
} else {
return <p> {item.message} </p>;
}
}
}
})}
</>
)
The problem here likely stems from the fact that you are using the value of isLoading right after setting it via setLoading.
Setting the state in React acts like an async function.
Meaning that the when you set the state and try to use it's value right after,
you will likely get the previous value of that state.
In this case, try to make the relevant messagesList values independent of isLoading.
Example:
.then((response) => {
const newIsLoading = false;
setIsLoading(newIsLoading);
setMessagesList([
...messagesList,
{
// Some code...
},
{
dataType: "text",
item: {
id: uuidv4(),
type: "reciever",
isLoaded: newIsLoading,
message: response.data.data.answers
},
},
]);
setCurrentMessage("");
})
State updates are dispatched asyncronously. That means that by the time your code reaches your second setMessagesList, that is the last one considered and thus dispatched.
setMessagesList([...]) // to be dispatched
axios.post((resp)=> resp.json().then(data=> setMessagesList([...]) //oops, not that one, this one
Probably, your console.log has to do with that. If you want to see the updated value, you will need to add it as an effect
useEffect(()=> console.log(messagesList), [messagesList])
Lastly, you may want to move shared code by .then and .catch to a .finally:
.finally(()=> {
setCurrentMessage("");
setIsLoading(false);
}

How to delete user by ID in react using react-confirm-alert

I'm trying to delete a user dependents by dependents id using react-confirm-alert dialog but the list refreshes, how do I stop this from happening?
import { confirmAlert } from 'react-confirm-alert';
import 'react-confirm-alert/src/react-confirm-alert.css';
//Api data sample
"Details": [
{
"name": "test test",
"gender" "M"
"dependents": [
{
"blood_group": "A+",
"date_of_birth": "1990-08-10",
"gender": "Female",
"genotype": "AS",
"id": "621f191dcd7fe69a6a3b7",
}
],
},
]
function App() {
const [formalDetails, setFormalDetails] = useState([]);
//get formal details API call
const handleDelete = (detail) => {
const params = JSON.stringify({
"principal enrid": detail.principals_enrid,
"dependent id": detail.id,
mine: true,
});
Axios({
method: "POST",
url: "api",
headers: {
"Content-Type": "application/json",
},
data: params,
})
.then((response) => {
console.log(response.status);
//below is where my proble lies
setFormalDetails((current) =>
current.filter((dep) => {
return dep?.dependents?.id !== detail?.dependents?.id;
})
);
})
.catch(function (error) {
console.log(error);
});
};
const submit = (user) => {
confirmAlert({
title: 'Confirm to delete Dependent',
message: `Are you sure you want to delete ${user?.name}?`,
buttons: [
{
label: 'Yes',
onClick: () => handleRemove(user)
},
{
label: 'No',
onClick: () => null
}
]
});
}
return (
<div className="app">
{formalDetails.length === 0 ? (<p>No Data</p>) : (
formalDetails?.map((record, idx) => {
return (
<div key={idx}>
<p >{record.name}</p>
<p >{record.gender}</p>
{
record?.dependents?.map((user, indx) => {
return (
<div key={indx}>
<p >{user.name}</P>
<button
onClick={() => submit(user)}
type="button">
Delete
</button
</div
)
}}
</div>
)
)}
</div>
);
}
export default App;
Please how do can I delete a dependent by ID without refreshing the list/page/window to keep the user scrolling down to take more action(s) even after performing a delete action.

Not able to Received the id in onKeydown in react

code:-
useEffect(() => {
setPlayer();
window.addEventListener("keydown", handleKeyDown);
return function cleanup() {
window.removeEventListener("keydown", handleKeyDown);
};
}, [props])
const handleKeyDown = (event, ID) => {
if (event.key === "Delete") {
//alert(name);
axios({
method: 'post',
url: `${API_ENDPOINT}/api/setCue?Idx=${ID}&RDL=${props.val}`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Access-control-allow-origin': '*'
},
auth: {
username: 'admin',
password: 'password'
},
}).then(response => {
console.log("Delete Inside Axios");
}).catch(error => {
console.log("Error In Post Data", error);
});
console.log("Delete Key Press", ID, props.val);
}
}
<tbody>
{
PlaylistData && PlaylistData.map((playdata) => {
return (
<tr key={playdata.idx} tabIndex={playdata.idx} className="border_bottom"
KeyDown={(e) => handleKeyDown(e, playdata.idx)} onLoad={() => active(playdata.idx)}
>
<td style={{ color: "white", width: "200px" }}>
<img src={`data:image/jpeg;base64,${base64}`} alt="Clip Thumbnail" width="50%" />
</td>
when I click the delete button it does not send the table index but when I remove the window.addEventListener("keydown", handleKeyDown); its sending the id number but not the props values
I want both id and props values to print in the console.
How can I fix that?
please help.
You can get the value of target using event.code == "Delete". So replace event.key by event.code
You can see the example below to see how it works i.e. go to input and then press any key to see the key entered.
const input = document.querySelector('input');
const log = document.getElementById('log');
input.onkeydown = logKey;
function logKey(e) {
log.textContent += ` ${e.code}, `;
}
<input>
<h3 id="log"></h3>

how to send the url parameter query in react?

code:-
<div>
{ChannelName.map((val, index) => {
return (
<div className="TextLink" key={index}>
<NavLink to={`/`}
onClick={(e) => myClick(val, index)} >
<button className='notActive buttonLeft'
onClick={() => { handleOnClick(index); handleClick(val, index); setPage("Table") }} // pass the index
className={activeIndex === index ? "active" : "notActive"}>
{val}
</button>
</NavLink>
</div>
)
})
}
</div>
{page === "Table" ? <Table val={getname}/> : null}
2 component table url:-
const userId = props.val;
useEffect(() => {
setInterval(() => {
getData();
}, 300);
}, [userId]);
const getData = () => {
console.log(`inside${userId}`);
axios.get(`${API_ENDPOINT}/api/getPlaylist?RDL=${menuId}`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Access-control-allow-origin': '*'
},
auth: {
username: 'admin',
password: 'password'
}
}).then(response => {
setIsLoading(true);
setPlaylistData(response.data.Clips);
setChannel([(response.data.Players)]);
// console.log("table", response.data.Clips);
//console.log([...Object.values(response.data.Players).flat()]);
}).catch(error => {
console.log("Error In GET Data", error);
});
I want when I click the menu then menu URL parameters goes to the menuId and then the api show according then
Right now i am props the the onclick name and when i click on the second button its show me new api and 1 menu api
how can i fix that?

How to dynamically show/hide a list of items in react

I'm having trouble being able to show/hide certain elements in react. Basically, I have a dynamic list of li's, and within the li, I have an label, when you click on the li I want to hide the label and show an input. Usually with jQuery it's as easy as
$('#element').hide()
$('#element2').show()
I'm not quite understanding how to achieve this with my current layout
class EntriesTable extends React.Component {
constructor(props) {
super(props);
console.log(this.props.plannerId);
this.state = {
tasks: [],
plannerId: this.props.plannerId,
};
var state = this.state;
}
componentDidMount() {
this.getTasks(this.state.plannerId);
}
EditTask(id) {
console.log(id);
var spanEl = id + 'taskSpan';
var inputEl = id + 'taskInput';
//hide this span
//show input
$(spanEl).hide();
$(inputEl).show();
//when save
//hide input
//update task
//show span
}
updateTask(id, name) {
$.ajax({
type: "GET",
url: "/Task/Update",
data: { id: id, name: name },
contentType: "application/json; charset=utf-8",
success: function (data) {
console.log(data);
//this.setState({ tasks: data.ReturnObject, loading: false });
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
});
}
createTask(name) {
//make api call to create planner here.
var data = {
Name: name,
PlannerId: model.plannerId,
Status: "Not Complete",
Description: "",
Affiliation: "",
Footprint: "",
Created: new Date(),
Modified: null,
};
$.ajax({
type: "POST",
url: "/Task/Add",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
success: function (data) {
console.log(data);
this.getTasks(model.plannerId);
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
});
}
getTasks(id) {
this.setState({ tasks: [], loading: true });
$.ajax({
type: "GET",
url: "/Task/GetAll",
data: { id: id },
contentType: "application/json; charset=utf-8",
success: function (data) {
console.log(data);
this.setState({ tasks: data.ReturnObject, loading: false });
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
});
}
render() {
const tasks = this.state.tasks.map((task) => {
var spanId = task.Id + "taskSpan";
var inputId = task.Id + "taskInput";
return (
<li key={task.Id} className="list-group-item" style={{minHeight: '50px'}}>
<div className="pull-left" style={{width: '50%'}}>
<span id={spanId} onClick={this.EditTask.bind(this, task.Id) }>{task.Name}</span>
<input id={inputId} type="text" style={{ display: 'none' } } />
</div>
<div className="pull-right" style={{marginTop: '-5px', width: '50%'}}>
<div className="pull-right">
<button className="btn btn-default">Add</button>
<button className="btn btn-default">Edit</button>
</div>
</div>
</li>
);
});
return (
<div className="table-wrapper">
<div className="task-container">
<h3>{this.props.rowName}</h3>
</div>
<ul id="tasksContainer">
{tasks}
<li className="list-group-item list-group-item-last"><input type="button" value="Add Task" onClick={this.addTask.bind(this)} className="btn btn-success btn-override" /></li>
</ul>
</div>
);
}
};
I did see other SO's which tell you to use a variable and then to show/hide by changing the variable, but I'm not sure if that's doable for my need, since I have a dynamic list it's not just a single element I am trying to show or hide.
class EntriesTable extends React.Component {
constructor(props) {
super(props);
console.log(this.props.plannerId);
this.state = {
editableTasks: [],
tasks: [],
plannerId: this.props.plannerId,
};
var state = this.state;
/* This isn't completely necessary but usually it is recommended you
* bind the class method in the constructor so it isn't bound on each
* render
*/
this.EditTask = this.EditTask.bind(this);
}
componentDidMount() {
this.getTasks(this.state.plannerId);
}
EditTask(id) {
/* So jQuery and react are kind of at odds with each other on fundamentals
* React is supposed to be declarative whereas jQuery is imperative. Using
* jQuery and React together is typically discouraged unless there is a real
* need for it. In React you are supposed to define how you want the UI to render
* based on some variables. You have two options available to you props and state.
* props are passed from the parent and are immutable. state is managed within that
* component and is mutable. So we have added a variable called editableTasks to your
* state that will contain an array of all editable tasks. Instead of trying to hide
* or show items here, we are simply going to add the id of now editable task to that
* array
*
*/
var nextState = this.state;
nextState.editableTasks.push(id);
this.setState(nextState);
}
updateTask(id, name) {
$.ajax({
type: "GET",
url: "/Task/Update",
data: { id: id, name: name },
contentType: "application/json; charset=utf-8",
success: function (data) {
console.log(data);
//this.setState({ tasks: data.ReturnObject, loading: false });
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
});
}
createTask(name) {
//make api call to create planner here.
var data = {
Name: name,
PlannerId: model.plannerId,
Status: "Not Complete",
Description: "",
Affiliation: "",
Footprint: "",
Created: new Date(),
Modified: null,
};
$.ajax({
type: "POST",
url: "/Task/Add",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
success: function (data) {
console.log(data);
this.getTasks(model.plannerId);
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
});
}
getTasks(id) {
this.setState({ tasks: [], loading: true });
$.ajax({
type: "GET",
url: "/Task/GetAll",
data: { id: id },
contentType: "application/json; charset=utf-8",
success: function (data) {
console.log(data);
this.setState({ tasks: data.ReturnObject, loading: false });
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
});
}
render() {
const tasks = this.state.tasks.map((task) => {
var editable = this.state.editableTasks.filter(id => id === task.Id).length > 0;
/* Now here we are going to check whether this item is editable
* based on id. So we assign a variable that will eval to a bool
* based on whether when you filter editableTasks to see if it contains
* the current items id the length is greater than 0.
*
* Now below instead of applying some id attribute we are going to return either
* the input or the span based on whether it is editable using a ternary operation
*
*/
return (
<li key={task.Id} className="list-group-item" style={{minHeight: '50px'}}>
<div className="pull-left" style={{width: '50%'}}>
{editable ? <input type="text" /> : <span onClick={this.EditTask( task.Id)}>{task.Name}</span>}
</div>
<div className="pull-right" style={{marginTop: '-5px', width: '50%'}}>
<div className="pull-right">
<button className="btn btn-default">Add</button>
<button className="btn btn-default">Edit</button>
</div>
</div>
</li>
);
});
return (
<div className="table-wrapper">
<div className="task-container">
<h3>{this.props.rowName}</h3>
</div>
<ul id="tasksContainer">
{tasks}
<li className="list-group-item list-group-item-last"><input type="button" value="Add Task" onClick={this.addTask.bind(this)} className="btn btn-success btn-override" /></li>
</ul>
</div>
);
}
};
So the above should work for making items editable. Now it doesn't handle actually editing them or returning them to non-editable state. But this should illustrate how you should be accomplishing this the 'react-way'.
I encourage you to drop jQuery. jQuery is going to make your React code harder to manage and make it harder to embrace the react way. If you need something for ajax requests, there are plenty of smaller libraries that just as well suited (superagent is highly recommended but a quick google can lead you to many other)
Let me know if you have any other question.
To show dynamically show/hide a list of items in react implement Visible.js in your file:
import React, { Component } from 'react'
import { Link, Router } from 'react-router';
import { connect } from 'react-redux';
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card';
import { List, ListItem } from 'material-ui/List';
import Divider from 'material-ui/Divider';
import '../../../static/images/cms-img3.jpg';
import '../../../static/images/cms-img4.jpg';
import '../../../static/images/cms-img5.jpg';
import '../../../static/images/grid-list/vegetables-790022_640.jpg';
import '../../../static/images/grid-list/00-52-29-429_640.jpg';
import '../../../static/images/grid-list/burger-827309_640.jpg';
import '../../../static/images/grid-list/camera-813814_640.jpg';
import '../../../static/images/grid-list/morning-819362_640.jpg';
import '../../../static/images/grid-list/hats-829509_640.jpg';
import '../../../static/images/grid-list/honey-823614_640.jpg';
import '../../../static/images/grid-list/water-plant-821293_640.jpg';
import '../../../static/images/video.mp4';
import '../../../static/images/video123.mp4';
class VisibleData extends Component {
constructor(props) {
super(props);
this.state = {
items: [],
};
this.onTodoClick = this.onTodoClick.bind(this);
}
componentDidMount() {
fetch('http://new.anasource.com/team9/news-api/?operation=view')
.then(result => result.json()
.then(news => {
this.setState({ items: news.news });
})
);
}
componentWillMount() {
window.onpopstate = (event) => {
this.componentDidMount();
};
}
onTodoClick(id) {
this.setState({
items: this.state.items.filter(item => item.news_id == id)
});
}
render() {
return (
<Data show={this.onTodoClick} items={this.state.items} />
)
}
}
class Data extends Component {
onTodoClick(e, id) {
this.props.show(id);
}
render() {
return (
<div>
{this.props.items.map(item => {
const p = item.news_type == "image";
const r = item.news_type == "video";
return <Link to={"todo/#/" + item.news_id} key={item.news_id}>
<Card onClick={(e) => this.onTodoClick(e, item.news_id)} style={{margin:15}}>
<CardTitle title={item.news_title} subtitle={item.news_description}>
<CardMedia>
{p
? <img src={item.news_src_url} />
: null
}
</CardMedia>
<CardMedia>
{r
? <video controls><source src={item.news_src_url} type="video/mp4"/></video>
: null
}
</CardMedia>
<div className='date'>{item.news_created_date}</div>
</CardTitle>
</Card>
</Link>
})
}
</div>
)
}
}
export default VisibleData;

Resources