Avoiding infinite useEffect loop while reflecting state change in React - javascript

I am trying to create an app where the user can click on a category and it will set the displayWork array to the value of the category and thus display the items in that array.
I have all of the work stored in an array
const [workList, setWorkList] = useState([
{ title: "aaa", image: "./images/1.jfif", type: "landscaping" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "Other 3", image: "./images/3.png", type: "other3" },
{ title: "Other 4", image: "./images/3.png", type: "other4" },
]);
and then I have my displayWork mapped to the page, I plan on changing it through using array.filter()
let filteredWork = workList.filter(function (work) {
return work.type === catagory;
});
then I have a row of categories like this to set the parameters of the filter
<div className="col-sm-2 workCatagory" onClick={() => setCatagory("landscaping")}>
Land Scaping
</div>
<div className="col-sm-2 workCatagory" onClick={() => setCatagory("plumbing")}>
plumbing
</div>
<div className="col-sm-2 workCatagory" onClick={() => setCatagory("other3")}>
test3
</div>
<div className="col-sm-2 workCatagory" onClick={() => setCatagory("other4")}>
Test4
</div>
And then I am setting the displayWork to the value of the filtered array like so:
<div className="row workBars" onClick={() => handleClick()}>
const handleClick = () => {
setDisplayWork(filteredWork)
}
The problem with this approach is the change is not reflected immediately upon clicking the button. Using this solution here I was able to fix this
useEffect(() => {
handleClick()
}, [handleClick])
But I ran into an error message and the page crashing on me very often, I cannot get the state to update immediately without also creating an infinite loop. A link to my full page is here github I appreciate any advice you may have

Because you use non-state variables to dependencies. So you need to wrap it into useMemo, useCallback to avoid infinitive loop
const filteredWork = useMemo(
() =>
workList.filter(function (work) {
return work.type === catagory;
}),
[workList, category],
);
const handleClick = useCallback(() => {
setDisplayWork(filteredWork);
}, [setDisplayWork, filteredWork]);

I just changed the architecture of the component.
Instead of calling two functions (setCategory and handleClick) for a single action is not a good idea.
So I added an useEffect hook, which gets triggered whenever the category is changed. In this way, the whole execution will be somehow synchronous.
import React, { useState, useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
export const Work = () => {
gsap.registerPlugin(ScrollTrigger);
const [workList, setWorkList] = useState([
{ title: "aaa", image: "./images/1.jfif", type: "landscaping" },
{ title: "aaa", image: "./images/1.jfif", type: "landscaping" },
{ title: "aaa", image: "./images/1.jfif", type: "landscaping" },
{ title: "aaa", image: "./images/1.jfif", type: "landscaping" },
{ title: "aaa", image: "./images/1.jfif", type: "landscaping" },
{ title: "aaa", image: "./images/1.jfif", type: "landscaping" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "plumbing", image: "./images/2.jfif", type: "plumbing" },
{ title: "Other 3", image: "./images/3.png", type: "other3" },
{ title: "Other 4", image: "./images/3.png", type: "other4" },
]);
const [displayWork, setDisplayWork] = useState([
{ title: "aaa", image: "./images/test.png", type: "landscaping" },
{ title: "aaa", image: "./images/test.png", type: "landscaping" },
{ title: "aaa", image: "./images/test.png", type: "landscaping" },
{ title: "aaa", image: "./images/test.png", type: "landscaping" },
{ title: "aaa", image: "./images/test.png", type: "landscaping" },
]);
const [catagory, setCatagory] = useState("Select a catagory");
//let newArray = workList.filter(workList.type.includes(catagory))
const Animation = () => {
console.log('woo')
}
useEffect(() => {
let filteredWork = workList.filter(function (work) {
return work.type === catagory;
});
setDisplayWork(filteredWork);
}, [catagory, workList]);
return (
<div className="workPage" id="workPage">
<h1 className="text-center">Here's what we can do</h1>
<div className="row workBars">
<div className="col-sm-2"></div>
<div className="col-sm-2 workCatagory" onClick={() => setCatagory("landscaping")}>
Land Scaping
</div>
<div className="col-sm-2 workCatagory" onClick={() => setCatagory("plumbing")}>
plumbing
</div>
<div className="col-sm-2 workCatagory" onClick={() => setCatagory("other3")}>
test3
</div>
<div className="col-sm-2 workCatagory" onClick={() => setCatagory("other4")}>
Test4
</div>
</div>
<h1> {catagory}</h1>
<div className="row">
<div className="col-sm-2"></div>
{displayWork.map((displayWork) => (
<div className="col-sm-4">
{" "}
<br />
<p>{displayWork.title}</p>
<img
src={displayWork.image}
alt={displayWork.name}
className="img-fluid workImage"
></img>
</div>
))}
<div className="col-sm-2"></div>
</div>
</div>
);
};

Related

view more and view less with useState hook in react js

I am fresh to react and useState hooks (still learning). I want to create show more/less button with use of an Array of images and React hooks. I import the images into div, I wanted to set button on the last and the show the images on the click of button.
I am getting the error:
text.slice is not function
The thing is, the code is written with use of function component.
here's my code:
import React from 'react';
import { useState } from 'react';
import '../assets/css/Product.css';
const ReadMore = ({ children }) => {
const text = children;
const [isReadMore, setIsReadMore] = useState(true);
const toggleReadMore = () => {
setIsReadMore(!isReadMore);
};
return (
<p className='text'>
{isReadMore ? text.slice(0, 150) : text}
<span onClick={toggleReadMore} className='read-or-hide'>
{isReadMore ? '...read more' : ' show less'}
</span>
</p>
);
};
export const Product = () => {
const card_image = [
{
image: 'CLT_Food_BeverageBar.jpg',
title: 'Charlotte',
subtitle: '(CLT)',
button: 'Explore Lounge',
},
{
image: 'Centurion_Cropped_0001_Bar.jpg',
title: 'Dallas',
subtitle: '(DFW)',
button: 'Explore Lounge',
},
{
image: 'DEN_GameRoom-LR_1200x540.jpg',
title: 'Denver',
subtitle: '(DEN)',
button: 'Explore Lounge',
},
{
image: 'IAH_Bar&Buffet_1200x600.jpg',
title: 'Houston',
subtitle: '(IAH)',
button: 'Explore Lounge',
},
{
image: 'amxtcl_Lounge_01_cmyk_1200x600.jpg',
title: 'Las Vegas',
subtitle: '(LAS)',
button: 'Explore Lounge',
},
{
image: 'LAX_hero.jpg',
title: 'Los Angeles',
subtitle: '(LAX)',
button: 'Explore Lounge',
},
{
image: 'LoungeAreaTalent1200x600.jpg',
title: 'Miami',
subtitle: '(MIA)',
button: 'Explore Lounge',
},
{
image: 'JFK_Carousel_3.jpg',
title: 'New York',
subtitle: '(JFX)',
button: 'Explore Lounge',
},
];
return (
<div>
<div className='container'>
<ReadMore>
<div className='row introduction'>
{card_image.map((card) => (
<div className='col-lg-3 pt-5'>
<div
className='location_card'
style={{
backgroundImage: `url(${process.env.REACT_APP_ASSET_URL}/card_image/${card.image})`,
objectFit: 'cover',
}}>
<div className='location-overly'>
<h3 className='h2'>
{card.title}
<br />
{card.subtitle}
</h3>
<button
type='button'
class='btn_product '>
{card.button}
</button>
</div>
</div>
</div>
))}
</div>
</ReadMore>
</div>
</div>
);
};
I think you should use conditional rendering, this means that when a state changes, your UI also changes. Sorry, I don't think I explained it that well, so here's some example code.
import * from x "xyz xyz";
const App = () => {
const [showMore, setShowMore] = useState(false);
if(showMore){
return(
<MoreStuff/>
);
}
return(
<DefaultStuff/>
);
}
Resources:
https://www.digitalocean.com/community/tutorials/7-ways-to-implement-conditional-rendering-in-react-applications
https://www.w3schools.com/react/react_conditional_rendering.asp
https://zh-hans.reactjs.org/docs/conditional-rendering.html
import { useState } from 'react';
const allCars = [
{ name: 'Audi', country: 'Germany' },
{ name: 'BMW', country: 'Germany' },
{ name: 'Chevrolet', country: 'USA' },
{ name: 'Citroen', country: 'France' },
{ name: 'Hyundai', country: 'South Korea' },
{ name: 'Mercedes-Benz', country: 'Germany' },
{ name: 'Renault', country: 'France' },
{ name: 'Seat', country: 'Spain' },
];
function CarFilter() {
const [showMoreData, setShowMoreData] = useState(3);
const showMore = () => {
if (showMoreData === 3) {
setShowMoreData(allCars.length);
} else if (showMoreData > 3) {
setShowMoreData(3);
}
};
return (
<div className="container">
<ul>
{allCars.slice(0, showMoreData).map((car, i) => (
<li key={i}>
{car.name} - {car.country}
</li>
))}
</ul>
{allCars.length > 3 && (
<button type="button" onClick={showMore}>
{showMoreData > 3 ? 'Show More ' : 'Show less'}
</button>
)}
</div>
);
}
export default CarFilter;

How do I get hover effect on rows with material-table react?

I have used material-table with react to render my data. I want to show the hover effect and cursor pointer effect when I hover on rows. But I couldn't find this even in the documentation.
IN SHORT- I want to highlight some colors on a row when the cursor hovers to that row.
Note: I also found a similar question and answer here, but they used another state just to hover which downgrades the performance if I increase the data like thousands rows. Hence it's bad practice so here I am asking for alternate solutions for the same.
Here is my codesandbox link
Below I also pasted my code.
App.js
import React, { useState } from 'react';
import './App.css';
import MaterialTable from 'material-table'
const empList = [
{ id: 1, name: "Neeraj", email: 'neeraj#gmail.com', phone: 9876543210, city: "Bangalore" },
{ id: 2, name: "Raj", email: 'raj#gmail.com', phone: 9812345678, city: "Chennai" },
{ id: 3, name: "David", email: 'david342#gmail.com', phone: 7896536289, city: "Jaipur" },
{ id: 4, name: "Vikas", email: 'vikas75#gmail.com', phone: 9087654321, city: "Hyderabad" },
]
function App() {
const [data, setData] = useState(empList)
const columns = [
{ title: "ID", field: "id", editable: false },
{ title: "Name", field: "name" },
{ title: "Email", field: "email" },
{ title: "Phone Number", field: 'phone', },
{ title: "City", field: "city", }
]
return (
<div className="App">
<h1 align="center">React-App</h1>
<h4 align='center'>Material Table with CRUD operation</h4>
<MaterialTable
title="Employee Data"
data={data}
columns={columns}
editable={{
onRowAdd: (newRow) => new Promise((resolve, reject) => {
const updatedRows = [...data, { id: Math.floor(Math.random() * 100), ...newRow }]
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
}),
onRowDelete: selectedRow => new Promise((resolve, reject) => {
const index = selectedRow.tableData.id;
const updatedRows = [...data]
updatedRows.splice(index, 1)
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
}),
onRowUpdate:(updatedRow,oldRow)=>new Promise((resolve,reject)=>{
const index=oldRow.tableData.id;
const updatedRows=[...data]
updatedRows[index]=updatedRow
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
})
}}
options={{
actionsColumnIndex: -1, addRowPosition: "first"
}}
/>
</div>
);
}
export default App;
add in your inside of your CSS
tbody tr:hover {
background-color: blue;
color: white;
cursor: pointer;
}

How can I change the color of material-table icons of onRowAdd, onRowUpdate, onRowDelete?

I tried to the material-table the library for basic crud operation. By using onRowAdd, onRowUpdate, onRowDelete, I get the icons for the same but I would like to know that how can I change the color of each of these three icons?
You can see my table has few icons and I am focusing on add, edit, delete icons I want to change color of these icons.
Here is the link to my codesandbox.
App.js file
import React, { useState } from 'react';
import './App.css';
import MaterialTable from 'material-table'
const empList = [
{ id: 1, name: "Neeraj", email: 'neeraj#gmail.com', phone: 9876543210, city: "Bangalore" },
{ id: 2, name: "Raj", email: 'raj#gmail.com', phone: 9812345678, city: "Chennai" },
{ id: 3, name: "David", email: 'david342#gmail.com', phone: 7896536289, city: "Jaipur" },
{ id: 4, name: "Vikas", email: 'vikas75#gmail.com', phone: 9087654321, city: "Hyderabad" },
]
function App() {
const [data, setData] = useState(empList)
const columns = [
{ title: "ID", field: "id", editable: false },
{ title: "Name", field: "name" },
{ title: "Email", field: "email" },
{ title: "Phone Number", field: 'phone', },
{ title: "City", field: "city", }
]
return (
<div className="App">
<h1 align="center">React-App</h1>
<h4 align='center'>Material Table with CRUD operation</h4>
<MaterialTable
title="Employee Data"
data={data}
columns={columns}
editable={{
onRowAdd: (newRow) => new Promise((resolve, reject) => {
const updatedRows = [...data, { id: Math.floor(Math.random() * 100), ...newRow }]
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
}),
onRowDelete: selectedRow => new Promise((resolve, reject) => {
const index = selectedRow.tableData.id;
const updatedRows = [...data]
updatedRows.splice(index, 1)
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
}),
onRowUpdate:(updatedRow,oldRow)=>new Promise((resolve,reject)=>{
const index=oldRow.tableData.id;
const updatedRows=[...data]
updatedRows[index]=updatedRow
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
})
}}
options={{
actionsColumnIndex: -1, addRowPosition: "first"
}}
/>
</div>
);
}
export default App;
You can override the icons and provide custom styles by setting the icons props. It accepts an object where the key is a type of operation (Add, Edit, Delete,...) and the value is an icon component. For reference, see the all-props section here.
<MaterialTable
{...props}
icons={{
Edit: () => <EditIcon style={{ color: "orange" }} />,
Delete: () => <DeleteIcon style={{ color: "red" }} />
}}
>
Live Demo
It's Simple. Inspect on the page and Select the Icon and Copy its style Name in Styles Tab.
Now, Go to App.css file and Create New Style with the icon style name shown on Inspect-styles area and there you can enter your desired color.
It will work.
In your App.css File,
Add below code
.MuiIconButton-colorInherit {
color: red;
}
change to any color

react-sortablejs - Setting the 'onChange' method on an object with nested arrays

I'm using the react-sortablejs library.
When trying to move cards within the list. I get the error:
Cannot read property 'map' of undefined
I have a dense structure and it gets lost here. How to handle onChange so that I can see in the console that the order of the notes within the list has changed.
Demo here
import Sortable from 'react-sortablejs';
// Functional Component
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
onChange={(order, sortable, evt) => {
console.log(order)
onChange(order);
}}
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};
class App extends React.Component {
state = {
item: {
id: "abc123",
name: "AAA",
lists: [
{
id: "def456",
list_id: "654wer",
title: 'List1',
desc: "description",
listItems: [
{
id: "ghj678",
title: "ListItems1",
listItemsId: "88abf1"
},
{
id: "poi098",
title: "ListItems2",
listItemsId: "2a49f25"
},
{
id: "1oiwewedf098",
title: "ListItems3",
listItemsId: "1a49f25dsd8"
}
]
},
{
id: "1ef456",
list_id: "654wer",
title: 'List 2',
desc: "description",
listItems: [
{
id: "1hj678",
title: "ListItems4",
listItemsId: "18abf1"
},
{
id: "1oi098",
title: "ListItems5",
listItemsId: "1a49f25"
},
{
id: "1oiwewe098",
title: "ListItems6",
listItemsId: "1a49f25dsd"
}
]
},
{
id: "2ef456",
title: 'List 3',
list_id: "254wer",
desc: "description",
listItems: [
{
id: "2hj678",
title: "ListItems7",
listItemsId: "28abf1"
},
{
id: "2oi098",
title: "ListItems8",
listItemsId: "234a49f25"
},
{
id: "df098",
title: "ListItems9",
listItemsId: "1asd8"
}
]
}
]
}
};
render() {
const c = this.state.item['lists'].map(item => { return item.listItems});
return (
this.state.item['lists'].map(item => {
return (<div>
{item.title}
<SortableList
key={uniqueId()}
items={item}
onChange={(item) => {
console.log(item)
this.setState({item});
}}
>
</SortableList>
</div>)
})
)
}
};
Thanks in advance.
You have to update few changes in your code.
Update the SortableList function as below.
First pass data-id={val.id} in li and after that in onChange method you will receive the order with id. So based on that we are sorting the records.
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
onChange={(order, sortable, evt) => {
items.listItems.sort(function(a, b){
return order.indexOf(a.id) - order.indexOf(b.id);
});
onChange(items);
}}
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val.id}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};
Update the onChange event of App component.
onChange={(item) => {
let itemObj = {...this.state.item};
itemObj.lists.map(x=>{
if(x.id === item.id) x = item;
});
this.setState({itemObj});
}}
That's it!
Here is the working demo for you
https://stackblitz.com/edit/react-sortablejs-blzxwd
When remove the onChange event in the Sortable list, Its works.
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};

React-Table not rendering data

I am trying to render some data in a react-table component however the data doesn't load. I have tested out with dummy data of the exact same format and it works fine. However when I make an API call and get data of the same format and push it to the list of data i'm passing to the react-table the table does not render it. Please help me identify the issue. Cheers
Setting up the columns:
columns: [
{
Header: "Employee ID",
accessor: "EmployeeID"
},
{
Header: "First Name",
accessor: "FirstName"
},
{
Header: "Last Name",
accessor: "LastName"
},
{
Header: "Date of Birth",
accessor: "DateOfBirth",
},
{
Header: "Status",
accessor: "Status",
},
{
Header: "Gender",
accessor: "Gender",
},
{
Header: "UpdatedDateUTC",
accessor: "UpdatedDateUTC",
}
]
What the data looks like:
{"EmployeeID":"63c571b3-bff0-4ce1-94f7-255c235580fa","FirstName":"Clive","LastName":"Thomas","Status":"ACTIVE","DateOfBirth":"/Date(697248000000+0000)/","Gender":"M","UpdatedDateUTC":"/Date(1533706298000+0000)/"}
My API call and how I'm saving the data item to state. (I console logged the value of the data I'm getting and it is in the correct format)
fetch('http://localhost:3100/employees')
.then((resp) => {
return resp.json()
})
.then((data) => {
let temp = this.state.posts;
temp.push(data.Employees[1])
this.setState({posts: temp})
console.log(this.state.posts)
})
.catch((error) => {
console.log(error, "catch the hoop")
})
The state and the 'posts' list storing the posts in state at bottom (with dummy data):
state = {
title: "Choose an Endpoint",
activeOrg: "Orginisation",
isExpanded: false,
activeLink: 0,
authLink:'',
response: '',
post: '',
responseToPost: '',
show: false,
modalContent:"",
token:'',
verifier:'',
org:'',
orginisations: [
{ id: 1, name: "ANU"},
{ id: 2, name: "Bar"},
{ id: 3, name: "FANG"},
{ id: 4, name: "Atlassian"}
],
list: [
{ id: 1, name: "Employees" },
{ id: 2, name: "Leave Applications" },
{ id: 3, name: "Pay Items" },
{ id: 4, name: "Payroll Calendars" },
{ id: 5, name: "Pay Runs" },
{ id: 6, name: "Pay Slips" },
{ id: 7, name: "Settings" },
{ id: 8, name: "Superfund Products" },
{ id: 9, name: "Timesheets" }
],
columns: [
{
Header: "Employee ID",
accessor: "EmployeeID"
},
{
Header: "First Name",
accessor: "FirstName"
},
{
Header: "Last Name",
accessor: "LastName"
},
{
Header: "Date of Birth",
accessor: "DateOfBirth",
},
{
Header: "Status",
accessor: "Status",
},
{
Header: "Gender",
accessor: "Gender",
},
{
Header: "UpdatedDateUTC",
accessor: "UpdatedDateUTC",
}
],
posts: [
{"EmployeeID":"63c571b3-bff0-4ce1-94f7-255c235580fa","FirstName":"Clive","LastName":"Thomas","Status":"ACTIVE","DateOfBirth":"/Date(697248000000+0000)/","Gender":"M","UpdatedDateUTC":"/Date(1533706298000+0000)/"}
]
}
Render function:
render() {
let myClass=""
let EndpointList = (
<div>
{this.state.list.map((i) => {
i.id === this.state.activeLink ? myClass="endpoint activeLink" : myClass="endpoint"
return <Endpoint
key={i.id}
name={i.name}
myClass={myClass}
clicked={(event) => this.handleClickEndpoint(i, i.id)}/>
})}
</div>
);
let orgContainer = ""
this.state.isExpanded ? orgContainer="orgItem expanded" : orgContainer="orgItem notExpanded"
let OrgList = (
<div className={orgContainer}>
{this.state.orginisations.map((o) => {
return <Orginisation
key={o.id}
name={o.name}
clicked={(event) => this.handleClickOrg(o,o.id)}
></Orginisation>
})}
</div>
);
var activeContent=<ReactTable columns={this.state.columns} data={this.state.posts} noDataText={"Loading..."}></ReactTable>
// const columns = Object.keys(this.state.data[0]).map((key, id)=>{
// console.log(key)
// return {
// Header: key,
// accessor: key,
// }
// })
return (
<Router>
<Route path='/' exact render={
() => {
return (
<div className='authenticateContainer'>
<a href={this.state.authLink} className='fill-div'>Click to Auntheticate</a>
</div>
)
}
}/>
<Route path='/home' render={
() => {
return (
<div>
<div className='sideBar'>
<div className='logoHolder'>
<img className='logo' alt='Logo' src={'./Assets/logo.png'}></img>
</div>
{EndpointList}
{OrgList}
<div style={{}} className="org button" onClick={this.expandOrg}>
<img className="orgLogo" alt='Logo' src={'./Assets/orgLogo.png'}></img>
{this.state.activeOrg}
</div>
</div>
<div className="container" id={this.state.title}>
{/* <button onClick={() => { this.setCredentials() }}>CLICK ME</button> */}
<div className="contentContainer">
<div className="head">
{this.state.title}
</div>
{activeContent}
</div>
</div>
</div>
)
}
} />
</Router>
);
}
}
Instantiating the react-table (also in render function above):
var activeContent=<ReactTable columns={this.state.columns} data={this.state.posts} noDataText={"Loading..."}></ReactTable>
I have also printed the dummy data that is successfully being inserted into the list as well as the API data which is not. As you can see they are clearly identical:
Not sure if this will resolve your issue, but IMO you should refactor this to be
fetch('http://localhost:3100/employees')
.then((resp) => {
return resp.json()
})
.then((data) => {
let temp = [...this.state.posts]
temp.push(data.Employees[1])
this.setState({
posts: temp,
data: data
})
console.log(this.state.posts) //this will not have the newest values yet, setState is async
})
.catch((error) => {
console.log(error, "catch the hoop")
})
It's not good practice to perform manipulations on react state
Why are you pushing Employees[1]? That would be the second record.

Categories