Cannot increment variable in React component - reactjs

I am learning React and trying to call API for users using this component:
It works and I get users for page=1,
But, when I click on next button, the method next is triggered which should update page variable to '2' but it doesn't happen.
import React , {Component} from "react";
import Wrapper from "../components/Wrapper";
import axios from "axios";
import {User} from "../../classes/user";
import {Link} from "react-router-dom";
class Users extends Component {
state = {
users: []
}
page = 1
componentDidMount = async () => {
const response = await axios.get(`users?page=${this.page}`)
console.log(response)
this.setState({
users: response.data.data
})
}
next = async () => {
this.page++; // this never gets incremented
await this.componentDidMount();
}
render() {
return (
<Wrapper>
<div className="d-flex justify-content-between flex-wrap flex-md-no-wrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div className="btn-toolbar mb-2 mb-md-0">
<Link to={'users/create'} className="btn btn-sm btn-outline-secondary">Add</Link>
</div>
</div>
<div className="table-responsive">
<table className="table table-striped table-sm">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Role</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{this.state.users.map(
(user: User) => {
return (
<tr>
<td>{user.id}</td>
<td>{user.first_name} {user.last_name}</td>
<td>{user.email}</td>
<td>{user.role.name}</td>
<td>
<div className="btn-group mr-2">
Edit
Delete
</div>
</td>
</tr>
)
}
)}
</tbody>
</table>
</div>
<nav>
<ul className="pagination">
<li className="page-item">
Previous
</li>
<li className="page-item">
<a href="" className="page-link" onClick={this.next}>Next</a>
</li>
</ul>
</nav>
</Wrapper>
)
}
}
export default Users;
No matter what page is always equal to 1:
url: 'users?page=1' in console
Why page variabel never gets incremented?
Alternatively, as suggested :
state = {
users: [],
page: 1
}
componentDidMount = async () => {
const response = await axios.get(`users?page=${this.state.page}`)
console.log(response)
this.setState({
users: response.data.data
})
}
next = async () => {
//this.page++;
this.setState({
page: this.state.page + 1
})
await this.componentDidMount();
}
Also do not update page either...

You have to put the variable page inside the state object.
You can increment the variable page like that :
this.setState({
page: this.state.page + 1
})

The thing you're missing is State concept as #devcarme mentioned. Particularly, you need to store page counter as a State, not as a variable.
I will oversimplify for learning purpose:
Before React, when you click Next page button, you have to reload the whole page with a new URL. By only doing that, the page can have new content.
React don't need to do that. React can "react" to that change by storing update in a State. With State, React can bring new content without reloading the whole page.
If you're not learn for maintaining codebase, I suggest you code in Functional component as it is shorter than Class component version, which make it's easier to approach.
In the long run, React dev team will focus on Functional component and keep Class component for legacy codebase. They will provide ways to update, for example React Hooks, which makes functional component mostly equivalent to class component.

The solution was to use SyntheticEvent like this:
next = async (e:SyntheticEvent ) => {
e.preventDefault()
this.page++;
Now page gets incremented as I wanted.

Related

How to pass partial data to a parent component in react

I have the following Component TBorrowed
import React, { Fragment, useState} from "react";
import {Link} from 'react-router-dom';
const EditItem = ({ item }) => {
const [name, setName] = useState(item.name)
const saveData = async (e) => {
e.preventDefault();
const body = { name}
await fetch(`http://127.0.0.1:5000/item/edit/${item.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
})
}
return (
<Fragment>
<Link className="link" data-toggle="modal" data-target={`#id${item.id}`} >{item.name}</Link>
<div className="modal" id={`id${item.id}`}>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">Edit Item</h4>
</div>
<div className="modal-body">
<label>Name</label>
<input value={name} onChange={e => { setName(e.target.value) }} type="text" />
</div>
<div className="modal-footer">
<button onClick={e => { saveData(e) }} type="button" className="btn btn-outline-success ml-auto" data-dismiss="modal">Save</button>
</div>
</div>
</div>
</div>
</Fragment>
)
}
export default EditItem;
The above is called in another component, Main as shown below
import React, { useState} from 'react';
import TBorrowed from './TBorrowed';
const Main = () => {
const [items, setItems] = useState([]);
...MANY ITEMS SKIPPED...
return (
<table className="layout">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Code</th>
</tr>
</thead>
<tbody>
{
items.map((item, index) => (
<tr key={item.id}>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{<TBorrowed item={item} />}</td>
</tr>
))
}
</tbody>
</table>
)
}
export default Main;
The above works well where I am able to see the item code in the Main component's <td></td> when rendered, which when I click, I am able to edit the particular item in a modal.
My issue is I no longer want to edit an item in a modal but I want it rendered on it's own page for editing.
When I try it without a data-toggle = "modal" in the TBorrowed component, I get all the contents of the TBorrowed component displaying in the Main component where the modal is called i.e <td>{<TBorrowed item={item} />}</td>. All the data in TBorrowed is shown in that <td></td> instead of just the item.code as it was showing while using the modal
My code has some parts missing so it can fit here.
Please assist, and if there's more information required I'll provide it.

Cannot update a component (`App`) while rendering a different component (`UserTable`)

I'm trying to learn React Hooks in functional components, and am following along with React Hooks tutorial but am getting the error: Cannot update a component (App) while rendering a different component (UserTable), and the error stack indicates this is related to the onClick={props.deleteUser(user.id)} property in the delete button in UserTable.js. I saw several posts indicating that one should try useEffect() to get around this issue, so I tried having deleteUser update a state variable, and then have useEffects change the users array. While the code compiled fine, the page simply hung and eventually timed out with an "out of memory" error (I assume caused by an endless cycle of trying to render and re-render?). Any ideas how to fix this situation?
App.js
import React, { useState } from 'react';
import UserTable from './tables/UserTable';
import AddUserForm from './forms/AddUserForm';
const App= () => {
const usersData = [
{id: 1, name: "Tania", username: "floppydiskette"},
{id: 2, name: "Craig", username: "siliconeidolon" },
{id: 3, name: "Ben", username: "benisphere"}
]
const [users, setUsers] = useState(usersData);
const addUser = (user) => {
user.id = users.length+1;
setUsers([...users,user])
}
const deleteUser = (id) => {
setUsers(users.filter((user)=>user.id !== id))
}
return (
<div className="container">
<h1> SIMPLE CRUD APP WITH HOOKS</h1>
<div className="flex-row">
<div className = "flex-large">
<h2> Add User </h2>
<AddUserForm addUser={addUser}/>
</div>
<div className = "flex-large">
<h2>View Users</h2>
<UserTable users={users} deleteUser={deleteUser}/>
</div>
</div>
</div>
);
}
export default App;
UserTable.js
import React from 'react';
const UserTable = (props) => {
return(
<table>
<thead>
<tr>
<th>Name</th>
<th>UserName</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{props.users.length > 0 ? (
props.users.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.username}</td>
<td>
<button className="button muted-button">Edit</button>
>>> This triggers the `cannot update a component . . .` error:
<button className="button muted-button" onClick={props.deleteUser(user.id)}>Delete</button>
</td>
</tr>
))
) : (
<tr colspan={3}>No Users</tr>
)}
</tbody>
</table>
);
}
export default UserTable
You just have to change
onClick={props.deleteUser(user.id)}>Delete</button>
to
onClick={()=> props.deleteUser(user.id)}>Delete</button>
Otherwise your delete function will get automaticaly fired on render itself

What should I do to successfully map data from axios API?

How do I map out the data I've put in my console/state? I've been trying to add a map function where I left the "//m", and it seems like it should be simple enough but I can't seem to do it properly.
import React, { useState, useEffect } from "react";
import axios from "axios";
import EmployeeDetail from "./EmployeeDetail";
function App() {
const [employees, setEmployees] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
axios.get("https://randomuser.me/api/?results=10&nat=us")
.then(res => {
console.log(res.data.results);
setEmployees(...res.data.results);
setLoading(false);
})
.catch(err => {
console.log(err);
});
}, []);
return (
<div className="App">
<h1>Employee List</h1>
//m
</div>
);
}
export default App;
I was able to make it using the API the guy in the youtube video I referenced used ("https://restcountries.eu/rest/v2/all") with the following function:
{countries.map((country) => (
<div key={country.name}>
{country.name} - {country-capital}
</div>
))}
I'm just having problems with doing it with my own API.
From your question it seems you are looking for rendering a table of output data from an API call.
When you call setEmployees(), react application will refresh the page using virtual DOM as you are setting a state using react hooks mechanism.
return(){
<div className="App">
<h1>Employee List</h1>
<table>
<thead>
// your table headers
</thead>
<tbody>
{this.employees.map((item, index) => {
<tr>
<td>{item.value1}</td>
<td>{item.value2}</td>
// as per your requirement
</tr>
})}
</tbody>
</table>
</div>
}
One more thing you can do is, create a function and return JSX from function.
Please visit below link for creating function and returning JSX.
How to loop and render elements in React.js without an array of objects to map?
You can use map as you want.
return (
<div className="App">
<h1>Employee List</h1>
<ul>
{
emplyees.map((employee) =>
<li>{employee.name}</li>
);
}
</ul>
</div>
);
There is a detailed document that you could follow step by step here

Need help refreshing a component in React.js without reloading the window/page

import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import { Button } from "react-bootstrap";
import { FiRefreshCw } from "react-icons/fi";
const Todo = (props) => (
<tr>
<td>{props.todo.todo_description}</td>
<td>{props.todo.todo_responsible}</td>
<td>{props.todo.todo_priority}</td>
<td>
<Link to={"/edit/" + props.todo._id}>
<Button size="sm" variant="info">
Update
</Button>
</Link>
 
<Link to={"/delete/" + props.todo._id}>
<Button size="sm" variant="danger">
Delete
</Button>
</Link>
</td>
</tr>
);
export default class TodosList extends Component {
refresh = () => {
// re-renders the component
this.setState({});
};
constructor(props) {
super(props);
this.state = { todos: [] };
}
componentDidMount() {
axios
.get("http://localhost:4000/todos/")
.then((response) => {
this.setState({ todos: response.data });
})
.catch(function (error) {
console.log("componentDidMount axios catch error", error);
});
}
todoList() {
return this.state.todos.map(function (currentTodo, i) {
return <Todo todo={currentTodo} key={i} />;
});
}
render() {
return (
<div>
<h3>
ToDo List{" "}
<Button
onClick={this.refresh}
style={{ color: "#70b5de" }}
variant="light"
size="sm"
>
<FiRefreshCw />
</Button>
</h3>
<table className="table table-striped" style={{ marginTop: 20 }}>
<thead>
<tr>
<th>Description</th>
<th>Responsible</th>
<th>Priority</th>
<th>Action</th>
</tr>
</thead>
<tbody>{this.todoList()}</tbody>
</table>
</div>
);
}
}
I'm want to onClick={refresh component}. Struggling a bit getting it to function correctly. I want to refresh just this component without reloading the window or it will break my session causing the entire app to start over. When I remove or update an entry into the database I'm recalling this list and even though it navigates to the page it's not refreshing the list. I not only want to create a refresh button but cause the component to refresh when it's being called via this.props.history.push("/");
My initial response was going to be to tell you to set a state variable and update that, as that should cause a re-render, but after looking at the react docs I am wondering if you could call ReactDOM.render() on your ToDoList to get it to render with your updated data.
You are basically asking (I think), how do I remount this component so it calls axios again, or to put it more simply, how do I call the axios function again. You can put the logic happening inside of the mount into its own function. After that you can call that function inside of onClick.

Update a component list after deleting record

I have a component. It displays a list of records. You can click the delete icon, and as soon as you go to a different page and return to the list, the record is no longer there. How do I remove the record from the list without going to a different page?
I've tried using componentWillUpdate() and componentDidUpdate() and placing my getTerritoryGeographies(this.props.params.id) in those functions, but those functions keep calling the data and do not stop. I'm restricted to API limits.
import React, { Component, PropTypes} from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { getTerritoryGeographies, deleteTerritoryGeography } from '../actions/index';
import TerritoryTabs from './territory-tabs';
class TerritoryGeographyList extends Component {
componentWillMount() {
//console.log('this is the child props (TerritoryGeographyList)');
console.log(this.props);
this.props.getTerritoryGeographies(this.props.params.id);
}
componentDidMount() {
console.log('componentDidMount');
}
componentWillUpdate() {
console.log('componentWillUpdate');
this.props.getTerritoryGeographies(this.props.params.id);
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
onDeleteClick(id) {
this.props.deleteTerritoryGeography(id);
}
static contextTypes = {
router: PropTypes.object
}
renderTerritoryGeographyList() {
return this.props.territoryGeographies.map((geography) => {
return (
<tr key={geography.Id}>
<th scope="row" data-label="Country">
<div className="slds-truncate">{geography.tpslead__Country__c}</div>
</th>
<td data-label="State/Provice">
<div className="slds-truncate">{geography.tpslead__State__c}</div>
</td>
<td data-label="Postal Start">
<div className="slds-truncate">{geography.tpslead__Zip_Start__c}</div>
</td>
<td data-label="Postal End">
<div className="slds-truncate">{geography.tpslead__Zip_End__c}</div>
</td>
<td className="slds-text-align--right" data-label="Action">
<button className="slds-button slds-button--icon" title="edit">
<svg className="slds-button__icon" aria-hidden="true">
<use xlinkHref={editIcon}></use>
</svg>
<span className="slds-assistive-text">Edit</span>
</button>
<button onClick={() => this.onDeleteClick(geography.Id)} className="slds-button slds-button--icon" title="delete" data-aljs="modal" data-aljs-show="PromptConfirmDelete">
<svg className="slds-button__icon" aria-hidden="true">
<use xlinkHref={deleteIcon}></use>
</svg>
<span className="slds-assistive-text">Delete</span>
</button>
</td>
</tr>
);
});
}
render() {
return (
<TerritoryTabs id={this.props.params.id} listTab="geography">
<Link to={"territory/" + this.props.params.id + "/geography/new"} className="slds-button slds-button--brand">
Add New Geography
</Link>
<table className="slds-table slds-table--bordered slds-table--cell-buffer slds-m-top--large">
<thead>
<tr className="slds-text-title--caps">
<th scope="col">
<div className="slds-truncate" title="Country">Country</div>
</th>
<th scope="col">
<div className="slds-truncate" title="State/Provice">State/Provice</div>
</th>
<th scope="col">
<div className="slds-truncate" title="Postal Start">Postal Start</div>
</th>
<th scope="col">
<div className="slds-truncate" title="Postal End">Postal End</div>
</th>
<th className="slds-text-align--right" scope="col">
<div className="slds-truncate" title="Action">Action</div>
</th>
</tr>
</thead>
<tbody>
{this.renderTerritoryGeographyList()}
</tbody>
</table>
</TerritoryTabs>
);
}
}
function mapStateToProps(state) {
//console.log(state);
return { territoryGeographies: state.territoryGeographies.all
};
}
export default connect(mapStateToProps, { getTerritoryGeographies, deleteTerritoryGeography })(TerritoryGeographyList);
UPDATE: I figured out how do remove it by updating my onDeleteClick(), but it seems unnecessarily heavy for a react app. Thoughts?
onDeleteClick(id) {
this.props.deleteTerritoryGeography(id);
var geographyIndex = this.props.territoryGeographies.findIndex(x => x.Id==id)
this.setState(state => {
this.props.territoryGeographies.splice(geographyIndex, 1);
return {territoryGeographies: this.props.territoryGeographies};
});
}
Please post your action and reducer so that we can see what you are doing on the Redux side.
If you are renderings a list from data that is in the Redux store, you can use React-Redux's connect Higher Order Function to wrap the component, thus enabling access to the store as component props. So that part looks correct.
When you are firing the action creator, you should be passing in the id of the data that you would like deleted, and in your reducer, something like this:
case 'DELETE_TERRITORY':
const territoryId = action.data;
return state.filter(territory => territory.id !== territoryId);
When the reducer returns the new, filtered array, your component should update with the list minus the territory you just deleted.
This code controls whether the deletion operation is performed correctly. Returns the state to the first state if the deletion operation fails
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
handleDelete = async productId => {
const originalProducts = this.state.products;
const products = this.state.products.filter(p => productId !== p.productId);
this.setState({ products });
try {
const result = await deleteProduct(productId);
if (result.status === 200) {
// codes goes here. for example send notification
}
}
catch (ex) {
if (ex.response && ex.response.status === 404) {
// codes goes here. for example send notification
}
this.setState({ products: originalProducts });
}
}
reactjs

Resources