useEffect gets stuck in an infinite loop - reactjs

I am getting an infinite loop and I know the problem is because I am putting in brackets as the second argument the 'posts' and the 'setPost' inside the useEffect function, but I need the page to render whenever I add a new post, so the posts must be in brackets.
function Home() {
const {userData, setUserData} = useContext(userContext)
const [posts, setPost] = useState([])
const [createPost, setCreatePost] = useState('')
const handleToken = () => {
localStorage.removeItem('auth-token')
}
const token = localStorage.getItem("auth-token");
const handleOnSubmit = (e) => {
e.preventDefault()
axios.post('http://localhost:5000/posts', {textOfThePost: createPost}, {
headers: { 'auth-token': token },
})
.then((res) => {setCreatePost("")})
}
useEffect(() => {
axios.get('http://localhost:5000/posts')
.then(res => {
setPost(res.data)
})
}, [posts])
return (
<div className="home">
<div style={{display: 'flex', alignItems: 'center'}}>
<h1>this is the home: Welcome, {userData.username}</h1>
<Link style={{margin: 10}} to="/home">home</Link>
<Link style={{margin: 10}} to="/profile">profile</Link>
<Link style={{margin: 10}} onClick={handleToken} to="/">log out</Link>
</div>
<form onSubmit={handleOnSubmit}>
<input type="text" placeholder="What's happening?" value={createPost} onChange={e => setCreatePost(e.target.value)}/>
<button type="submit">tweet</button>
</form>
<div style={{display: 'flex', flexDirection: 'column'}}>
{posts.map(post => (
<div style={{border: '2px solid black', marginBottom: 10, marginRight: 'auto', marginLeft: 'auto', width: 300}} key={post._id}>
<div style={{display: 'flex', alignItems: 'center'}}>
<Avatar src={post.avatar}/>
<span style={{color: 'blue', marginLeft: 10}}>{post.name} <span style={{color: 'grey', fontSize: 11}}>#{post?.username}</span></span><br/>
</div>
<span>{post.textOfThePost}</span><br/>
<span>{moment(post.date).format('lll')}</span>
</div>
)).reverse()}
</div>
</div>
)
}

The problem here is the dependency array for useEffect (and similar hooks) doesn't use deep comparison (for performance reasons).
That is, whenever you get new data via Axios, res.data is a new JavaScript object and as you assign it to state, the effect dependency considers it an entirely changed object and runs the effect again, etc.
The easiest fix is to use a deep-comparing useEffect such as https://github.com/kentcdodds/use-deep-compare-effect .

You are not using posts in your effect, so it shouldn't be in your dependency array at all. One solution for your issue might be getting the posts in the first render, just once, then whenever you create a post, using the response updating the posts state.
const posts = [
{ id: "1", text: "foo" },
{ id: "2", text: "bar" },
{ id: "3", text: "baz" }
];
const API = {
getPosts: () =>
new Promise((resolve) => setTimeout(() => resolve(posts), 2000)),
createPost: () =>
new Promise((resolve) =>
setTimeout(() => resolve({ id: "3", text: "fizz" }), 1000)
)
};
function Posts() {
const [posts, setPosts] = React.useState([]);
React.useEffect(() => {
API.getPosts().then(setPosts);
}, []);
function handleOnSubmit() {
API.createPost().then((res) => setPosts((prev) => [...prev, res]));
}
return (
<div>
<div>
<button onClick={handleOnSubmit}>Create Post</button>
</div>
{!Boolean(posts.length) ? (
<span>Loading posts...</span>
) : (
posts.map((post) => <div>{post.text}</div>)
)}
</div>
);
}
ReactDOM.render(
<Posts />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root" />
Don't bother the API part, I'm just mimicking your requests. The important part is after creating the post, using the response, and updating the state.

Related

How can I call some code in another function after state change?

const App = (): JSX.Element => {
const [isShow, setIsShow] = useState(true)
useEffect(() => {
console.log(`is show: ${isShow}`)
},[isShow])
const handleClick = () => {
console.log('call setIsShow')
setIsShow(!isShow)
}
const onClick = () => {
$('.bt1').trigger('click') // click on the button with the class name is `bt1`
// call below code after state `isShow` change
console.log('hello world')
...
}
return (
<div>
<div className='container'>
<div style={{height: '200px', backgroundColor: 'red'}}/>
{isShow && <div className='green_container' style={{height: '200px', backgroundColor: 'green'}}/>}
<div style={{height: '200px', backgroundColor: 'blue'}}/>
</div>
<button onClick={handleClick} className='bt1'>Click...</button>
<button onClick={onClick}>GOGO</button>
</div>
);
};
on the above code, My action is...
click button GOGO
$('.bt1').trigger('click') is calling
function handleClick is calling for print message and change IsShow state
My Question is:
How can I print hello world and another command that below line $('.bt1').trigger('click') inside onClick function after state isShow change ?
Don't use jquery in react. use the ternary operator to show text in Jsx.
const App = (): JSX.Element => {
const [isShow, setIsShow] = useState(true)
const handleClick = () => {
setIsShow(!isShow)
}
const onClick = ()=>{
setIsShow(false)// if you want.
}
return (
<div>
<div className='container'>
<div style={{height: '200px', backgroundColor: 'red'}}/>
{isShow && <div className='green_container' style={{height: '200px', backgroundColor: 'green'}}/>}
<div style={{height: '200px', backgroundColor: 'blue'}}/>
</div>
<button onClick={handleClick} className='bt1'>Click...</button>
<button onClick={onClick}>{isShow ? "Hello World":"GOGO"}</button>
</div>
);
};
you can simply call handleClick in onClick funtion
sth like this:
const onClick = () => {
handleClick()
// call below code after state `isShow` change
console.log('hello world')
...
}
and use useEffect for listening to any change in isShow
if you only want to click on button you can use ref
I changed your code in any way that might be needed for you
take a look at this one:
import { useState, useRef, useEffect } from "react";
const App = () => {
const [isShow, setIsShow] = useState(true);
const btnRef = useRef();
useEffect(() => {
console.log(`is show: ${isShow}`);
}, [isShow]);
const handleIsShowChanged = () => {
console.log("hello world");
};
const handleClick = () => {
console.log("call setIsShow");
setIsShow((prevState) => {
handleIsShowChanged();
return !prevState;
});
};
const onClick = () => {
btnRef.current.click();
};
return (
<div>
<div className="container">
<div style={{ height: "200px", backgroundColor: "red" }} />
{isShow && (
<div
className="green_container"
style={{ height: "200px", backgroundColor: "green" }}
/>
)}
<div style={{ height: "200px", backgroundColor: "blue" }} />
</div>
<button ref={btnRef} onClick={handleClick} className="bt1">
Click...
</button>
<button onClick={onClick}>{isShow ? "Hello World" : "GOGO"}</button>
</div>
);
};
export default App;

How to handle backend data for react-beautiful-dnd

I have been working on a drag and drop planning feature, and I am attempting to pull saved data from my backend. I have been able to successfully log the needed data, however, when I am passing it into the react-beautiful-DnD template I have been using, the data fails to appear in the items array even though it is structured exactly the same as the static starter data in the other column.
const onDragEnd = (result, columns, setColumns) => {
if (!result.destination) return;
const { source, destination } = result;
if (source.droppableId !== destination.droppableId) {
const sourceColumn = columns[source.droppableId];
const destColumn = columns[destination.droppableId];
const sourceItems = [...sourceColumn.items];
const destItems = [...destColumn.items];
const [removed] = sourceItems.splice(source.index, 1);
destItems.splice(destination.index, 0, removed);
setColumns({
...columns,
[source.droppableId]: {
...sourceColumn,
items: sourceItems
},
[destination.droppableId]: {
...destColumn,
items: destItems
}
});
} else {
const column = columns[source.droppableId];
const copiedItems = [...column.items];
const [removed] = copiedItems.splice(source.index, 1);
copiedItems.splice(destination.index, 0, removed);
setColumns({
...columns,
[source.droppableId]: {
...column,
items: copiedItems
}
});
}
};
function DragTables() {
const itemStarter = [
{ id: uuid(), travel: "Flying from NYC to MCO", brand: "American Airlines", category: "Airline", Start: "8/12/21", End: "8/12/21", points: "10000", value: "500" }
];
useEffect (() => {
fetchNewData()
},[])
const [unplannedDataSet, setUnplannedDataSet] = useState([]);
async function fetchNewData() {
// const itineraryId = 2
const response = await fetch('http://localhost:5000/planner/getUnplannedItineraryData', {
method: "POST",
headers: {jwt_token: localStorage.token}
})
const dataSet = await response.json();
setUnplannedDataSet(dataSet)
}
useEffect (() => {
fetchPlannedData()
},[])
const [plannedDataSet, setPlannedDataSet] = useState([]);
async function fetchPlannedData() {
// const itineraryId = 2
const response = await fetch('http://localhost:5000/planner/getPlannedItineraryData', {
method: "POST",
headers: {jwt_token: localStorage.token}
})
const plannedDataSet = await response.json();
setPlannedDataSet(plannedDataSet)
}
const parsedData = [];
unplannedDataSet.forEach(element => {
parsedData.push({
id: element.id,
brand: element.brand
});
});
**const columnsFromBackend = {
//This does not
[uuid()]: {
name: "Itinerary",
items: plannedDataSet
},
//This works
[uuid()]: {
name: "Travel Options",
items: itemStarter
}
};**
const [columns, setColumns] = useState(columnsFromBackend);
//DND component
return (
<div>
<div style={{ display: "flex", justifyContent: "space-around", height: "100%", marginTop: 8}}>
<DragDropContext
onDragEnd={result => onDragEnd(result, columns, setColumns)}
>
{Object.entries(columns).map(([columnId, column], index) => {
return (
<div
style={{
display: "block",
flexDirection: "column",
alignItems: "center",
fontSize: 2
}}
key={columnId}
>
<h4 style={{ display: "flex",
justifyContent: "center",}}>{column.name}</h4>
<div style={{ display: "flex",
justifyContent: "center",
marginTop: 4}}>
<Droppable droppableId={columnId} key={columnId}>
{(provided, snapshot) => {
return (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
{column.items.map((item, index) => {
return (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided, snapshot) => {
return (
<div className="snapshot"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<p style={{paddingLeft: 5, paddingTop: 1}}> <div style={{borderBottom: "1px solid white" }}><strong>{item.travel}</strong> </div>
<strong>Brand:</strong> {item.brand} | <strong># of Points:</strong> {item.points} | <strong>Point Value:</strong> ${item.value}
<br></br><strong>Category:</strong> {item.category} | <strong>Start Date:</strong> {item.Start} | <strong>End Date:</strong> {item.End}</p>
<p></p>
</div>
);
}}
</Draggable>
);
})}
{provided.placeholder}
</div>
);
}}
</Droppable>
</div>
</div>
);
})}
</DragDropContext>
</div>
</div>
);
}
export default DragTables;```
The reason why your code is not working is that you put const columnsFromBackend nested in your React DragTables Component. When you do this JavaScript will compile the code each time, producing a new copy of the columnsFromBackend Object, and React will not initialize the useEffect right, causing an infinite loop. Just put columnsFromBackend in the root scope and it will work right, but you will need to sync with the database.
You should not pass an object into useEffect, only a plain-old-data-type, but I think a string is okay but not an Object. I personally use a timer to autosave my state for my Chrome Extension. I have a useState number to switch tabs/views with my nav bar, and in each tab/view the timer updates a different part of my state relevant to the mode. You can also use a boolean useState and each time you change from true to false or false to true it saves.

TypeError: Cannot read property 'clickNode' of undefined

I created these two functions inside my react component. The following code returns an error:
const UncontrolledDiagram = ({ sentence }) => {
// create diagrams schema
const [schema, { onChange, addNode, removeNode }] = useSchema(initialSchema);
const clickNode = () => {
console.log("Click event");
}
const BaseNode = ({ content }) => (
<div className='button' style={{ width: '70px', fontSize: '0.6rem', textAlign: 'center' }}>
<a onClick={this.clickNode}>
<div role="button">
{content}
</div>
</a>
</div>
);
How can I call clickNode from within BaseNode?
You no need to write 'this' inside the functional components, so your code should looks like this
const UncontrolledDiagram = ({ sentence }) => {
// create diagrams schema
const [schema, { onChange, addNode, removeNode }] = useSchema(initialSchema);
const clickNode = () => {
console.log("Click event");
}
const BaseNode = ({ content }) => (
<div className='button' style={{ width: '70px', fontSize: '0.6rem', textAlign: 'center' }}>
<a onClick={clickNode}>
<div role="button">
{content}
</div>
</a>
</div>
);

Why my props are undefined when sending to components?

I have a table in my project and I have an edit / view / add page that I can access from this table. My goal is to send the clicked data to the other component without any problem, but no matter how hard I try, I get an undefined error and the project is broken. I would be glad if you could help.
I am sharing my codes from parent to child component
Table page.
import React, { useState, useEffect, useCallback, useMemo } from "react";
import ManagementTable from '../components/ManagementTable'
import {
getApps,
updateStopRisk,
countLiveCountry,
updateAppShow,
deleteApp,
} from "../api/apiCalls";
import VisibilityIcon from "#material-ui/icons/Visibility";
import DeleteIcon from "#material-ui/icons/Delete";
import EditIcon from "#material-ui/icons/Edit";
import Switch from "#material-ui/core/Switch";
import DeleteModal from "../components/DeleteModal";
import { Link } from "react-router-dom";
const Management = () => {
const [apps, setApps] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [currentApp, setCurrentApp] = useState("");
const [appID, setAppID] = useState(0);
const fetchData = useCallback(async () => {
const { data: appsResponse } = await getApps();
const countLiveCountries = await fetchLiveCountriesForApps(appsResponse);
setApps(
appsResponse.map((app, idx) => ({
...app,
countLiveCountry: countLiveCountries[idx],
}))
);
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
const fetchLiveCountriesForApps = async (appwLive) => {
const countLiveCountries = await Promise.all(
appwLive.map((app) => countLiveCountry(app.appID))
);
return countLiveCountries.map(({ data: liveCountries }) => liveCountries);
};
const removeApp = async () => {
await deleteApp(appID);
setModalVisible(false);
fetchData();
};
const onClickCancel = () => {
setModalVisible(false);
};
const columns = useMemo(() => [
{
Header: "Application Name",
accessor: "app_name",
},
{
Header: "Business Area",
accessor: "businessarea.businessarea_name",
},
{
Header: "Live Plants",
accessor: "countLiveCountry",
},
{
Header: "Line Stop Risk",
accessor: "app_stoprisk",
Cell: ({ row: { original } }) => {
const changeCheck = async (id) => {
await updateStopRisk(id);
fetchData();
};
return (
<input
checked={original.app_stoprisk}
onClick={() => {
changeCheck(original.appID);
}}
id="appStopRisk"
type="checkbox"
style={{ width: 18, height: 18, marginTop: 5 }}
/>
)
},
sortType: (a, b, id) => {
if (a.original[id] > b.original[id]) return -1;
if (b.original[id] > a.original[id]) return 1;
},
},
{
Header: "Actions",
Cell: ({ row: { original } }) => {
const changeTrack = async (id) => {
await updateAppShow(id);
fetchData();
};
return (
<>
<Link
className="btn btn-manage-link btn-sm col-2"
to={{
pathname: `/management/${original.app_name}`,
mode: "view",
id: original.appID
}}
>
<VisibilityIcon></VisibilityIcon>
</Link>
<Link
to={{
pathname: `/management/${original.app_name}`,
mode: "edit",
id: original.appID
}}
className="btn btn-manage-link btn-sm col-2"
>
<EditIcon></EditIcon>
</Link>
<button
onClick={() => {
setModalVisible(true);
setCurrentApp(original.app_name);
setAppID(original.appID);
}}
className="btn btn-manage-link btn-sm col-3"
>
<DeleteIcon></DeleteIcon>
</button>
<Switch
onClick={() => changeTrack(original.appID)}
checked={original.app_show}
className="col-3"
></Switch>
</>
)
},
},
],
[fetchData]
);
return (
<div className="container">
<h2 style={{ float: "left", font: "bold" }}>Management</h2>
<div style={{ float: "right" }}>
<Link className="btn btn-danger btn-sm" to={{ pathname: `/management/add`, mode: "add" }}>
Add New App
</Link>
<Link className="btn btn-danger btn-sm ml-3" exact to="/management/plants">
Plant Management
</Link>
</div>
<ManagementTable columns={columns} data={apps} />
<DeleteModal
message={<strong>{currentApp}</strong>}
variety="app"
onClickCancel={onClickCancel}
onClickOk={removeApp}
visible={modalVisible}
/>
</div>
);
};
export default Management;
The page where I transfer the props.
import React, { useState, useEffect } from "react";
import Accordion from "../components/Accordion";
import Details from '../components/Details'
import {
getByIdApps,
} from "../api/apiCalls";
const ApplicationManagement = (props) => {
const [appById, setAppById] = useState([]);
const { id } = props.location;
const [selectOption, setSelectOption] = useState('add')
useEffect(() => {
getData();
getMode();
}, [])
const getData = async () => {
console.log(props.location.id)
if (props.location.id) {
await getByIdApps(props.location.id).then((response) => setAppById(response.data))
console.log(appById)
console.log(props)
}
else {
setSelectOption('add')
}
}
const getMode = () => props.location.mode ? setSelectOption(props.location.mode) : setSelectOption('add')
const handleOptionChange = (event) => {
console.log(event.target.value)
setSelectOption(event.target.value)
}
return (
<>
<div style={{ margin: 20 }}>
<h1>
{appById.app_shortcode} - {appById.app_fullname}
</h1>
<div className="float-right mb-auto">
<label><input type="radio" value="view" checked={selectOption === 'view'} onChange={handleOptionChange} />View</label>
<label> <input type="radio" value="add" checked={selectOption === 'add'} onChange={handleOptionChange} />Add</label>
<label> <input type="radio" value="edit" checked={selectOption === 'edit'} onChange={handleOptionChange} />Edit</label>
</div>
<br></br>
<div style={{ marginLeft: 50, marginRight: 50 }} >
<Accordion
title={
<div style={{ width: 1350 }}>
<h3>Details</h3>
<hr style={{ backgroundColor: "#aaa" }}></hr>
</div>
}
content={
<Details appID={id} data = {appById}></Details>
}
/>
<Accordion title={
<div style={{ width: 1350 }}>
<h3>Links</h3>
<hr style={{ backgroundColor: "#aaa" }}></hr>
</div>
}></Accordion>
<Accordion title={
<div style={{ width: 1350 }}>
<h3>Factory Management</h3>
<hr style={{ backgroundColor: "#aaa" }}></hr>
</div>
}></Accordion>
<Accordion title={
<div style={{ width: 1350 }}>
<h3>Issues Management</h3>
<hr style={{ backgroundColor: "#aaa" }}></hr>
</div>
}></Accordion>
<Accordion title={
<div style={{ width: 1350 }}>
<h3>Middleware Management</h3>
<hr style={{ backgroundColor: "#aaa" }}></hr>
</div>
}></Accordion>
</div>)
{selectOption === 'add' ? (
<div>
Add Mode
</div>
) : selectOption === 'view' ? (<div>View Mode</div>) : (<div>eidt</div>)}
</div>
</>
);
};
export default ApplicationManagement;
and the section where the details are kept on the ApplicationManagement page (My code is quite long, I just share the problem part.)
import React, { useState, useEffect } from 'react'
import axios from "axios";
import {
getResponsibleTeams,
getBusinessAreas
} from '../api/apiCalls'
const Details = (props) => {
const [rTeams, setrTeams] = useState([]);
const [bAreas, setbAreas] = useState([]);
const { data } = props;
useEffect(() => {
async function fetchData() {
const getrTeams = await getResponsibleTeams();
const getbAreas = await getBusinessAreas();
axios.all([getrTeams, getbAreas]).then(
axios.spread((...allData) => {
const allrTeams = allData[0].data;
const allbAreas = allData[1].data;
setrTeams(allrTeams);
setbAreas(allbAreas);
})
);
}
fetchData();
}, []);
return (
<div>
<div
style={{
float: "left",
width: 1350,
height: 340,
}}
>
<div className="form-group">
<label style={{ float: "left" }} htmlFor="appFullName">
Frontend:{" "}
</label>
<input
id="appFullName"
type="text"
class="form-control"
placeholder="dsfdsdsf"
value={data.frontend.frontend_name} // error here
//onChange={handleInputChange}
name="appShortCode"
style={{ width: 400, marginLeft: 150 }}
/>
</div>
</div>
</div>
)
}
export default Details;
Later I realized that using asynchronous functions caused some problems. I came up with a solution to this and the problem was solved.
Error Code Here :
<Accordion
title={
<div style={{ width: 1350 }}>
<h3>Details</h3>
<hr style={{ backgroundColor: "#aaa" }}></hr>
</div>
}
content={
<Details appID={id} data = {appById}></Details>
}
/>
and the solution to the problem
{appById &&
<Accordion
title={
<div style={{ width: 1350 }}>
<h3>Details</h3>
<hr style={{ backgroundColor: "#aaa" }}></hr>
</div>
}
content={
<Details appID={id} data = {appById}></Details>
}
/>}

React history.push auto redirect

I have implement a function to navigate to new screen when click a button in using media list in react bootstrap, but that function automatically called when screen is loading,
this is my code
function News() {
const history = useHistory()
const [eventData, setEventdata] = useState('');
const [MemberOffer, setMemberOffer] = useState('');
const [Loading, setLoading] = useState(true);
const [Loading2, setLoading2] = useState(true);
useEffect(() => {
getDatafromserver()
getallmemebroffers()
Logincheck()
}, []);
async function getDatafromserver() {
console.log("inside getDatafromserver funcation")
try {
await fetch('http://124.43.17.60:4000/getallevent')
.then((response) => response.json())
.then((json) => setEventdata(json.Rows))
.catch((error) => alert('Please check your network'))
.finally(() => setLoading(false))
console.log("rana data", eventData)
console.log("loding....", Loading)
} catch (error) {
console.log("error when get data from server")
}
}
async function getallmemebroffers() {
console.log("inside memebr offer funcation")
try {
await fetch('http://124.43.17.60:4000/memoffers')
.then((response) => response.json())
.then((json) => setMemberOffer(json.news))
.catch((error) => alert('Please check your network'))
.finally(() => setLoading2(false))
console.log("Member offers", MemberOffer)
console.log("loding....", Loading2)
} catch (error) {
console.log("error when get data from server")
}
}
const Logincheck = () => {
console.log("inside login check")
try {
Axios.get("http://124.43.17.60:4000/loging_check")
.then((response) => {
console.log("res cookie", response)
})
} catch (error) {
console.log("No internet")
}
}
function editNews(){
history.push('/admin/edit-news')
}
return (
<div className='home' >
<br></br>
<Card style={{ height: 'auto' }} >
{/* memebr offer */}
<Card.Header style={{ borderColor: 'white', fontSize: '30px', fontFamily: 'Quicksand-Bold' }}>Tech News</Card.Header>
{Loading2 ? <Loader type="Circles" color="#00BFFF" height={80} width={80} /> : (
<div>
{MemberOffer.map((object, index) => (
// <p key={index.id}>{object.event_name}</p>
<Card.Body >
<ul className="list-unstyled" style={{ marginLeft: '20px' }}>
<Media as="li">
<img
width={72}
height={72}
className="mr-3"
src={object.offer_image}
alt="Generic placeholder"
/>
<Media.Body style={{ marginLeft: '10px' }}>
<h5 style={{ fontSize: '20px', fontFamily: 'Quicksand-Bold' }}>{object.offer_name}</h5>
<p style={{ fontSize: '15px', fontFamily: 'Quicksand-Medium', color: '#707070' }}>
{object.offer_content}
</p>
</Media.Body>
<Button variant="primary" onClick={editNews()}>Edit</Button>
</Media>
<br></br>
</ul>
</Card.Body>
))}
</div>
)}
</Card>
</div >
)
}
export default News
when I'm reload the page this editNews() function already called and navigate to EditNews page without click the Edit button. Can any one tell me what I am doing wrong with in this code and please help me to solve this problem
Do not call the function in onClick. This should work.
<Button variant="primary" onClick={editNews}>Edit</Button>

Resources