How to avoid Uncaught (in promise) Error: Too many re-renders in my case - javascript

I am using React as the frontend and Flask as the backend.
Today, the frontend needs to use the response from the backend in the user interface, which will be stored as a dialog using setDialog and rendered in the UI.
However, an error "Uncaught (in promise) Error: Too many re-renders. React limits the number of renders to prevent an infinite loop" keeps occurring.
I have tried using useEffect to fetch the dialog and also setting up a button to avoid repeated rendering, but neither method has worked.
Using useEffect:
const [dialog, setDialog] = useState([]);
useEffect(() => {
const handleAddDialog = async () => {
const url = `http://127.0.0.1:5000/question_hints_dialog/ww/dd/C1_P1`;
const response = await fetch(url);
const data = await response.json();
console.log("data", data);
setDialog(data);
};
handleAddDialog();
}, []);
Using button mode:
const handleAddDialog = async () => {
const url = `http://127.0.0.1:5000/question_hints_dialog/ww/dd/C1_P1`;
const response = await fetch(url);
dialogs = await response.json();
setDialog(dialogs)
};
return(
<Button onClick={()=>handleAddDialog()}>Start</Button>
)
I would like to know how to solve this issue. Thank you.
<List ref = {(ref)=>setScrollbarRef(ref)} className={classes.messageArea} style={{maxHeight: 500, overflow: 'auto'}}>
<Button onClick={()=>handleAddDialog()}>開始</Button>
{dialog && dialog.map((msg, idx) => {
console.log("detail",msg.detail)
let linkComponent = null;
if(msg.id === 1){
linkComponent =<></>;
}
else if (msg.id === 2) {
setHintsCount(1)
linkComponent = importConcept
//<Link href="#" onClick={() => handleProcessStage(false, "開始 PyTutor")}>開始 PyTutor</Link>;
} else if (msg.id === 3) {
linkComponent = <Link href="#" onClick={() => handleConcept(false)}>GOGo</Link>;
}
const detail_update = <>{msg.detail}<br/>{linkComponent}</>
return (
<React.Fragment key={idx}>
<ListItem key={idx} className = {msg.from === 'student'? classes.stuPos:classes.tutorPos}>
{msg.detail && (
<Grid container className = {msg.from === 'student'?classes.stuMsg:classes.tutorMsg}>
<Grid item={true} xs style={{display:'flex'}}>
<ListItemText primary= {
detail_update
}/>
</Grid>
<Grid item={true} xs={12}>
<ListItemText className={msg.from === 'student'? classes.stuPos:classes.tutorPos} secondary={currentTime}></ListItemText>
</Grid>
</Grid>
)}
</ListItem>
</React.Fragment>
);
})}
</List>
Here is now my frontend useEffect code:
useEffect(() => {
const fetchData = async () => {
const options = await getStoredOptions();
setOptions(options);
setOptionsLoaded(true);
};
const handleScrollbar = () => {
if (scrollbarRef) {
new PerfectScrollbar(scrollbarRef, {
wheelSpeed: 2,
wheelPropagation: true,
minScrollbarLength: 20
});
}
};
if (!optionsLoaded) {
fetchData();
}
handleScrollbar();
if (hint) {
console.log("Hint updated: ", hint);
}
if (optionsLoaded && options?.student_name && options?.student_id) {
console.log("initial");
setIsNew(true);
// do something here...
setIsNew(false);
}
}, [scrollbarRef, isSolved, optionsLoaded, hint, pesudo, cloze, originCode, advCode, count, options]);
Backend code:
#app.route('/question_hints_dialog/<string:stu_name>/<string:stu_id>/<string:hint_id>')
def generate_question_hints_dialog(stu_name, stu_id, hint_id):
name = userInfo.student_name
stu_id =userInfo.sudent_id
dialog = []
# dialog.append({"id": 1, "detail": f"... {stu_name} ... {stu_id}", "from": 'student'})
dialog.append({"id": 1, "detail": f"...,{stu_name}! ... " , "from": 'tutor' })
dialog.append({"id": 2, "detail": f"...", "from": 'tutor'})
dialog.append({"id": 3, "detail": "..." , "from": 'tutor' })
dialog.append({"id": 4, "detail": "..." , "from": 'tutor' })
dialog.append({"id": 5, "detail": "..." , "from": 'tutor' })
dialog.append({"id": 6, "detail": "..." , "from": 'tutor' })
dialog.append({"id": 7, "detail": "..." , "from": 'tutor' })
dialog.append({"id": 8, "detail": "..." , "from": 'tutor' })
return jsonify(dialog)

I tried many method to solve this issue but they couldn't work.
Finally, I found that
const [dialog, setDialog] = useState<{ id: number; detail?: JSX.Element; from: string }[]>([]);
The problem is that detail is initialized as JSX.Element
When React reloads, it would keep to set detail as JSX.Element, but JSX keeps changing. So, the re-render problem happens.
Now I change to
const [dialog, setDialog] = useState<{ id: number; detail: string; from: string }[]>([]);
and it figures out.
Share here and thanks for your concern.
If anything I realize wrong, feel free to let me know.

Related

Mapping JSON to MUI cards not returning any UI elements or errors

I have the following JSON which I want to map to MUI Cards. I am not getting any error messages but nothing is being displayed. The console.log(questionGroups) only displays the JSON after changing some unrelated code to cause a live reload.
const [questionGroups, setQuestionGroups] = useState("");
const fetchQuestionGroups= async () => {
setQuestionGroups(
await fetch(`API_LINK`).then((response) => response.json())
);
console.log(questionGroups);
};
useEffect(() => {
fetchQuestionGroups();
}, []);
...
<Box className={classes.cards}>
{questionGroups?.displaygroups?.IntakeQuestion?.map((group, groupIndex) => {
return (
<Card className={classes.card1}>
<CardHeader title={group.GroupName} />
</Card>
);
})}
</Box>
This is a sample of my JSON:
{
"displaygroups": {
"IntakeQuestions": [
{
"GroupId": 11,
"GroupName": "Group 1",
"SizingId": null,
"OwnerName": "Jane Doe",
"Questions": 0,
"Answered": null,
"Validated": null
}
]
}
}
Use && instead of ?
<Box className={classes.cards}>
{questionGroups &&
questionGroups.displaygroups &&
questionGroups.displaygroups.IntakeQuestions.map((group, groupIndex) => {
return (
<Card className={classes.card1}>
<CardHeader title={group.GroupName} />
</Card>
);
})}
</Box>
You need to set the state once the data is available.
const fetchQuestionGroups= async () => {
const data = await fetch(`API_LINK`)
const temp = response.json()
setQuestionGroups(temp);
console.log(questionGroups);
};

react-beautiful-dnd: Prevent flicker when drag and drop a lists

I'm using this react-beautiful-dnd library to be able to reorder lists. However, even though I'm able to drag and drop and re-order, there is a flicker when I try to reorder lists.
You can see in the video:
enter image description here
Here is my code:
I added sorting to the array by order before mapping.
const Board = () => {
const [currentId, setCurrentId] = useState(null);
const lists = useSelector((state) => state.lists);
const dispatch = useDispatch();
const classes = useStyles();
useEffect(() => {
dispatch(getLists());
}, [currentId, dispatch]);
const onDragEnd = (result) => {
const { destination, source, draggableId, type } = result;
if(!destination) return;
const droppableIdStart = source.droppableId;
const droppableIdEnd = destination.droppableId;
const droppableIndexStart = source.index;
const droppableIndexEnd = destination.index;
const newState = [...lists];
// drag lists
if(type === 'list') {
const dragList = newState.splice(droppableIndexStart, 1);
newState.splice(droppableIndexEnd, 0, ...dragList);
// update order list to be index
newState.forEach((list, index) => {
list.order = index;
dispatch(updateList(list._id , { ...list }));
});
}
return newState;
}
// Arranging lists by order
const newArrange = (a,b) => {
return (a.order - b.order);
}
lists.sort(newArrange);
return (
<>
<DragDropContext onDragEnd={onDragEnd} >
<div>
<h1>Board</h1>
<Droppable droppableId="all-lists" direction="horizontal" type="list">
{ provided => (
<div className={classes.listContainer} {...provided.droppableProps} ref={provided.innerRef} >
{ lists.map((list, index) =>
(user?.result?.googleId === list?.creator || user?.result?._id === list?.creator) ?
<List key={list._id} title={list.title} cards={list.cards} currentId={list._id} index={index} /> :
null
)}
{addListFlag && (
<InputItem
value={listData.title}
btnText={"Add list"}
type={"list"}
placeholder={"Enter a list title..."}
changedHandler={handleChange}
closeHandler={closeHandlerBtn}
addItem={submitHandler}
/>
)}
{!addListFlag && (
<AddBtn btnText={"Add another list"} type={"list"} handleClick={handleAddition} />
)}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
</DragDropContext>
</>
)
}
export default Board;
Sample of data:
{
_id: "6163cdd306d27b",
title: "a",
name: "first one",
order: "0",
cards:[
{
id: "0",
text: "a1",
_id: {_id: "61d0ca2c20d27e"}
},
{
id: "1",
text: "a2",
_id: {_id: "616541ec90630"}
},
{
id: "2",
text: "a3",
_id: {_id: "61606e609"}
}
]
}
Thank :)
It has probably todo with your getLists() call in the useEffect hook.
Is there an async function behind it? Do you get your lists from a server? If so, I suppose that useEffect is firing twice (once on drag end and once when it gets back the data from the backend or whatever getLists is doing), which leads to the flickering.
It would probably help to know what exactly getLists is.

how do I check for an empty object in javascript?

so I have a response from an API like this,
How do I check the empty object from the API inverse?
I have tried using lodash to check it but this did not work well in react native, I have not studied it further
this is my state
const [product, setProduct] = useState([])
const [loading, setLoading] = useState(true)
const getProduct = () => {
api.get(`/v1/snack/product/nearby?idKecamatan=${dataKecamatan.idKecamatan}`,
{ headers: { 'Authorization': 'Bearer' + AsyncStorage.getItem('app-token') } }
)
.then(res => {
setProduct(res.data)
setLoading(!loading)
console.log(res.data)
})
}
useEffect(() => {
navigation.addListener('focus', () => {
getProduct()
})
}, [navigation])
<View>
{Object.keys(product.Data).length === 0 ? (
<Text>data from 'Data' is empty</Text>
) : (
<View>
<Text>Data not empty</Text>
</View>
)}
</View>
if Data is not empty
{
"Data": {
"data": [
{
"idSnack": 1,
"codeSnack": "MCA69825829",
"nameSnack": "Taro",
"imageSnack": "localhost/snack-upload/public/media/product/130720xMDVDa_8hrNIx.jpg",
"price": "16500.00",
"rating": 0,
"productSold": 0,
"favStatus": 1,
"idAgen": 2
}
],
"metadata": {
"total": 2,
"count": 2,
"per_page": 20,
"current_page": 1,
"total_pages": 1,
"prev_page": "",
"next_page": ""
}
},
"Message": "SUCCESS"
}
if the data is blank the response is like this:
{
"Data": {},
"Message": "Tidak ada snack disekitarmu"
}
I want if data is empty to return like this
<View>
{Object.keys(product.Data).length === 0 ? (
<Text>gk ada</Text>
) : (
<View>
<Text>ada</Text>
</View>
)}
</View>
You check if Data.data exists -->
if (Data.data) {
// show data
} else {
// handle null response
}
Alternatively, you can check the length of Data keys: Object.keys(Data).length

Appending fetched data

I'm trying to build a treeview component in react where data for the tree is fetched based on the nodes expanded by the user.
Problem
I want to replace the code inside handleChange with data from my server, so that I append the data i fetch to the tree state. How can I achieve this with react?
The data i get can look like this:
{
"children": [
{
"id": "2212",
"parentId": "3321",
"name": "R&D",
"address": "homestreet"
},
{
"id": "4212",
"parentId": "3321",
"name": "Testing",
"address": "homestreet"
}
]
}
My Code
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import TreeView from "#material-ui/lab/TreeView";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
import TreeItem from "#material-ui/lab/TreeItem";
const useStyles = makeStyles({
root: {
height: 216,
flexGrow: 1,
maxWidth: 400
}
});
export default function FileSystemNavigator() {
const classes = useStyles();
const initialData = {
root: [
{
id: "1",
label: "Applications"
}
],
};
const [tree, setTree] = useState(initialData);
const handleChange = (event, nodeId) => {
setTimeout(() => {
const newTree = {
...tree,
[nodeId]: [
{
id: "2",
label: "Calendar"
},
{
id: "3",
label: "Settings"
},
{
id: "4",
label: "Music"
}
]
};
setTree(newTree);
}, 1000); // simulate xhr
};
const renderTree = children => {
return children.map(child => {
const childrenNodes =
tree[child.id] && tree[child.id].length > 0
? renderTree(tree[child.id])
: [<div />];
return (
<TreeItem key={child.id} nodeId={child.id} label={child.label}>
{childrenNodes}
</TreeItem>
);
});
};
return (
<TreeView
className={classes.root}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
onNodeToggle={handleChange}
>
{renderTree(tree.root)}
</TreeView>
);
}
If I am understanding correctly, you want to replace your "fake" setTimeout implementation of an API call with a real call using fetch.
In this case, it's as simple as calling fetch inside of the handleChange handler and updating your state with new items that you get back as a result.
function FileSystemNavigator() {
const initialData = {...}
const [tree, setTree] = React.useState(initialData)
const handleChange = (event, nodeId) => {
const handleResult = (data) => {
const items = data.children.map(item => {
return { id: item.id, label: item.name }
})
setTree({
root: [...tree.root, ...items]
})
}
const handleError = (error) => {
// handle errors appropriately
console.error(error.message)
}
fetch("https://api.myjson.com/bins/1aqhsc")
.then(res => res.json())
.then(handleResult)
.catch(handleError)
}
// ...
return (...)
}
This should do the trick.
Note that I've used your sample API endpoint that you've provided in the comments, so you will have to change the handleResult callback inside of the handleChange handler to make sure you're parsing out your new data appropriately.
If you'd like to see a quick example, I created a CodeSandbox with a button that can be clicked to fetch more data and display it in a list:
Demo
Let me know if you have any questions.

React-Native returning objects from nested array

I am new to React-Native and struggling to return objects from a nested array (Hopefully I am using the correct terminology).
I am grabbing my data from the tfl tube status api JSON is below:
[
{
"$type": "Tfl.Api.Presentation.Entities.Line,
Tfl.Api.Presentation.Entities",
"id": "bakerloo",
"name": "Bakerloo",
"modeName": "tube",
"disruptions": [],
"created": "2018-03-13T13:40:58.76Z",
"modified": "2018-03-13T13:40:58.76Z",
"lineStatuses": [
{
"$type": "Tfl.Api.Presentation.Entities.LineStatus,
Tfl.Api.Presentation.Entities",
"id": 0,
"statusSeverity": 10,
"statusSeverityDescription": "Good Service",
"created": "0001-01-01T00:00:00",
"validityPeriods": []
}
],
"routeSections": [],
"serviceTypes": [],
"crowding": {}
},
I am fetching the data using Axios.
state = { lineDetails: [] };
componentDidMount() {
axios.get('https://api.tfl.gov.uk/line/mode/tube/status')
.then(response => this.setState({ lineDetails: response.data }));
};
I am returning the data like this.
renderLineDetails() {
return this.state.lineDetails.map((details) =>
<TubeList
key={details.id}
details={details} />
)};
render() {
return (
<ScrollView>
{this.renderLineDetails()}
</ScrollView>
);
}
My TubeList component looks like:
const TubeList = ({ details }) => {
const { name, statusSeverityDescription } = details;
const { nameStyle, statusStyle } = styles;
return (
<TubeCard>
<CardSectionTitle>
<Text style={nameStyle}>{name}</Text>
</CardSectionTitle>
<CardSectionStatus>
<Text style={statusStyle}>{statusSeverityDescription}</Text>
</CardSectionStatus>
</TubeCard>
);
};
Is someone able to explain why statusSeverityDescription is not displaying in my list below.
Iphone Simulator image
Thank you.
Instead of statusSeverityDescription you have to use lineStatuses and map it for getting statuses.
TubeList:
const TubeList = ({ details }) => {
const { name, lineStatuses } = details;
const { nameStyle, statusStyle } = styles;
return (
<TubeCard>
<CardSectionTitle>
<Text style={nameStyle}>{name}</Text>
</CardSectionTitle>
{lineStatuses.map((status) =>
<CardSectionStatus>
<Text style={statusStyle}>{status.statusSeverityDescription}</Text>
</CardSectionStatus>
}
</TubeCard>
);
};
Thanks for all your comments. I have fixed the issue following Prasun Pal's comments. Below is my new code and screenshot of the working app.
renderLineDetails() {
return this.state.lineDetails.map((details) =>
<TubeList
key={details.id}
lineStatus={details.lineStatuses[0]}
lineName={details}
/>
)};
const TubeList = ({ lineName, lineStatus }) => {
const { statusSeverityDescription } = lineStatus;
const { name } = lineName;
const { nameStyle, statusStyle } = styles;
return (
<TubeCard>
<CardSectionTitle>
<Text style={nameStyle}>{name}</Text>
</CardSectionTitle>
<CardSectionStatus>
<Text style={statusStyle}>{statusSeverityDescription}</Text>
</CardSectionStatus>
</TubeCard>
);
};
iPhone screenshot of working app

Categories