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
Related
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.
I am trying to learn React and Typescript. I am building a little demo app that sends a request to the api https://api.zippopotam.us/us to get postcode information and display the place information. If the request is submitted with an invalid postcode I want to display an error message.
Below is my code. I have the call to the api inside the useEffect method and notice that this runs twice when the app is loaded i.e. before the user has entered a zipcode and clicked the search button so the api call is made without the zipcode and hence returns a 404, so the error code is always displayed on initial load.
I thought that the useEffect method should only get run when the zipSearch value changes i.e. when a user enters a zip and clicks enter. Although reading up on the useEffect method is seems it runs everytime the app component renders. Im also a little confused why it runs twice on initial load.
How can I get this to work the way I want it to? Any help would be much appreciated. If a moderator deletes this question, can they please let me know why? Thanks.
import React, {FormEvent, useEffect, useState} from "react";
import { useForm } from 'react-hook-form'
import "./App.css";
import axios from "axios";
import { IPlace } from "./IPlace";
export default function App2(){
const [placeFound, setPlaceFound] = useState<IPlace[]>([]);
const [zipSearch, setZipSearch] = useState("");
const [errorFound, setErrorFound] = React.useState("");
const renderPlaces = () => {
console.log("Render places runs")
if(placeFound.length !== 0){
return (<div className="table-container">
<table>
<thead>
<tr>
<th><span>State</span></th>
<th><span>Longitude</span></th>
<th><span>Latitude</span></th>
<th><span>Place Name</span></th>
</tr>
</thead>
{placeFound.map((place) =>{
return (
<tbody>
<tr>
<td>{place.state}</td>
<td>{place.longitude}</td>
<td>{place.latitude}</td>
<td>{place["place name"]}</td>
</tr>
</tbody>
)})}
</table>
</div>)
}
}
React.useEffect(() => {
console.log("useEffect is run")
const query = encodeURIComponent(zipSearch);
axios
.get(`https://api.zippopotam.us/us/${query}`,{
})
.then((response) => {
setPlaceFound(response.data.places);
setErrorFound("");
})
.catch((ex) => {
let errorFound = axios.isCancel(ex)
? 'Request Cancelled'
: ex.code === 'ECONNABORTED'
? 'A timeout has occurred'
: ex.response.status === 404
? 'Resource Not Found'
: 'An unexpected error has occurred';
setErrorFound(ex.code);
setPlaceFound([]);
});
}
},[zipSearch]);
const search=(event: FormEvent<HTMLFormElement>) =>{
console.log("Search method runs")
event.preventDefault();
const form = event.target as HTMLFormElement;
const input = form.querySelector('#zipSearchInput') as HTMLInputElement;
setZipSearch(input.value);
}
return (
<div className="App">
<div className="search-container">
<h1>Place Search using Zip Code</h1>
<form className="searchForm" onSubmit={event => search(event)}>
<div>
<label htmlFor="zipSearchInput">Zip Code</label>
<input {...register('zipSearchInput', { required: true, minLength: 5, maxLength: 5 }) }
id="zipSearchInput"
name="zipSearchInput"
type="text"
/>
</div>
{
errors.zipSearchInput && <div className="error">Zip Code is required and must be 5 digits long</div>
}
<button type="submit">Search</button>
</form>
</div>
{placeFound.length !== 0 && renderPlaces()}
{errorFound !== "" && <p className="error">{errorFound}</p>}
</div>)
}
What you should probably do is actually use the library above react-hook-form and its functions.
Then I believe that the useEffect that you are using is pretty useless in this scenario, You are going the long way for a simpler task. You can simply call the api on submit of the form and get rid of the state zipSearch the react-hook-form is taking care of that for you.
Here's the fixed version of the code below:
import React, { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import axios from "axios";
interface IPlace {
state: string;
longitude: string;
latitude: string;
"place name": string;
}
export default function App2() {
const [placeFound, setPlaceFound] = useState<IPlace[]>([]);
const [errorFound, setErrorFound] = React.useState("");
const formMethods = useForm<{ zipSearchInput: string }>();
const renderPlaces = () => {
console.log("Render places runs");
if (placeFound.length !== 0) {
return (
<div className="table-container">
<table>
<thead>
<tr>
<th>
<span>State</span>
</th>
<th>
<span>Longitude</span>
</th>
<th>
<span>Latitude</span>
</th>
<th>
<span>Place Name</span>
</th>
</tr>
</thead>
{placeFound.map((place) => {
console.log(place);
return (
<tbody key={place.latitude}>
<tr>
<td>{place.state}</td>
<td>{place.longitude}</td>
<td>{place.latitude}</td>
<td>{place["place name"]}</td>
</tr>
</tbody>
);
})}
</table>
</div>
);
}
};
const search = (values: { zipSearchInput: string }) => {
console.log(values);
const query = encodeURIComponent(values.zipSearchInput);
axios
.get(`https://api.zippopotam.us/us/${query}`, {})
.then((response) => {
setPlaceFound(response.data.places);
setErrorFound("");
})
.catch((ex) => {
let _errorFound = axios.isCancel(ex)
? "Request Cancelled"
: ex.code === "ECONNABORTED"
? "A timeout has occurred"
: ex.response.status === 404
? "Resource Not Found"
: "An unexpected error has occurred";
setErrorFound(_errorFound);
setPlaceFound([]);
});
};
return (
<div className="App">
<div className="search-container">
<h1>Place Search using Zip Code</h1>
<FormProvider {...formMethods}>
<form className="searchForm" onSubmit={formMethods.handleSubmit(search)}>
<div>
<label htmlFor="zipSearchInput">Zip Code</label>
<input
{...formMethods.register("zipSearchInput", {
required: true,
minLength: 5,
maxLength: 5
})}
id="zipSearchInput"
name="zipSearchInput"
type="text"
/>
</div>
<button type="submit">Search</button>
</form>
</FormProvider>
</div>
{placeFound.length !== 0 && renderPlaces()}
{errorFound !== "" && <p className="error">{errorFound}</p>}
</div>
);
}
Good Luck
Here i want to change this code to react redux. How i can change this code using react redux. Kindly provide any solutions for changing this code to react redux using GET method api. As iam new to react js how can i change this code using react redux.
import React from "react";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
export default function User() {
const [users, setUsers] = useState([]);
const f = async () => {
const res = await fetch("https://reqres.in/api/userspage=1");
const json = await res.json();
setUsers(json.data);
};
useEffect(() => {
f();
}, []);
const handleLogout = (e) => {
localStorage.clear();
window.location.pathname = "/";
}
return (
<div>
<h1>List Users</h1>
<div>
<button onClick={handleLogout}>Logout</button>
<nav>
<Link to="/Home">Home</Link>
</nav>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>First_name</th>
<th>Last_name</th>
<th>Email</th>
<th>Avatar</th>
</tr>
</thead>
<tbody>
{users.length &&
users.map((user) => {
return (
<tr>
<td> {user.id}</td>
<td>{user.first_name}</td>
<td> {user.last_name} </td>
<td>{user.email}</td>
<td> <img key={user.avatar} src={user.avatar} alt="avatar" /></td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
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.
Actually, I am making Employee attendance portal. I have 2 buttons on the Dashboard named as CheckIn & CheckOut. When I click the CheckIn button I get the current date, attendance as 'PRESENT' & CheckIn time from the Backend & when I click the checkOut button I get CheckOut time from the Backend. I just wanted to display all of these data in a table on React. I am getting all the data from the backend but I am unable to show the checkOut time in a table. What I am doing is I have made an array UseState with the name checkIn and I am putting all the data in that array. But in my scenario, only CheckIn data is pushed in the CheckIn array, and CheckOut data is not pushed in CheckIn array. On the table, I have mapped on Checkin array data. Getting the chechIn data & checkOut column remains empty.
import React, { useState, useEffect } from "react";
import moment from "moment";
import EmployeeNavbar from "./EmployeeNavbar";
import { useDispatch, useSelector } from "react-redux";
import {
employeeCheckIn,
employeeCheckOut,
getEmployeeCheckIn,
getEmployeeCheckOut
} from "../../actions/employeeActions.js";
const EmployeePanel = () => {
const employee = useSelector(state => state.employee);
const dispatch = useDispatch();
const [checkIn, setCheckIn] = useState([]);
const handleCheckIn = id => {
dispatch(employeeCheckIn(id));
};
const handleCheckOut = id => {
dispatch(employeeCheckOut(id));
};
console.log(employee);
useEffect(() => {
dispatch(getEmployeeCheckIn());
dispatch(getEmployeeCheckOut());
{
employee.checkOut
? setCheckIn(employee.checkIn, employee.checkOut)
: setCheckIn(employee.checkIn);
}
console.log(checkIn);
});
return (
<div>
<EmployeeNavbar />
<div className="container">
<h4>
<b>Employee Attendance Portal</b>
</h4>
<div>
<button
className="btn-small waves-effect waves-light hoverable green"
onClick={() => handleCheckIn(employee.employee.employeeData._id)}
>
Check In
</button>
<button
className="btn-small waves-effect waves-light hoverable red accent-4"
onClick={() => handleCheckOut(employee.employee.employeeData._id)}
>
Check Out
</button>
</div>
<table className="striped centered">
<thead>
<tr>
<th>Date</th>
<th>Attendance</th>
<th>Check In</th>
<th>Check Out</th>
</tr>
</thead>
<tbody>
{checkIn
? checkIn.map((list, i) => {
return (
<tr key={i}>
<td>{moment(list.date).format("MMM Do YYYY")}</td>
<td>{list.attendance}</td>
<td>{list.checkIn}</td>
<td>{list.checkOut}</td>
</tr>
);
})
: ""}
</tbody>
</table>
</div>
</div>
);
};
export default EmployeePanel;
Hi in this line you are only showing checkin data
{checkIn
? checkIn.map((list, i) => {
return (
<tr key={i}>
<td>{moment(list.date).format("MMM Do YYYY")}</td>
<td>{list.attendance}</td>
<td>{list.checkIn}</td>
<td>{list.checkOut}</td>
</tr>
);
})
: ""}
you should merge both checkedIn and checkedOut data into single arry.then it will work