Loading mxgraph in react useEffect method problem - reactjs

I try to show a mxgraph object after I upload a file to a server. The server runs ANTLR and returns the parsed data as JSON Object. I use Typescript to generate an Object out of my JSON result. This Object is set to a variable with react useState.
Now my problem:
after I set the diagram variable with useState, the useEffect is fired up. In the useEffect my goal is to show the mxgraph. The Graph is shown, but it creates a new component every time the useEffect is Called and the graph appears in a new div
My question is, is there a way crating the graph in the useEffect method and only showing it in the intended div?
Component Header
const [diagram, setDiagram] = useState<IDiagram>();
const divGraph = React.useRef<HTMLDivElement>(null);
The api call
const onSubmit = async (e: any) => {
e.preventDefault();
const formData = new FormData();
formData.append("file", file);
try {
const res = await axios.post("/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
//create a TypeScript Object out of result
var diag = diagramCreator.createDiagram(res.data);
//sets the diagram variable with useState
setDiagram(diag);
} catch (err) {
console.log(err);
}
};
The useEffect
useEffect(() => {
if (!mxClient.isBrowserSupported()) {
mxUtils.error("Browser is not supported!", 200, false);
}
else {
const graph = new mxGraph(divGraph.current);
graph.setConnectable(true);
graph.setHtmlLabels(true);
mxEvent.disableContextMenu(divGraph.current);
if(typeof diagram !== 'undefined' ){
var mxGraphCreator = new MxGraphCreator(graph,diagram);
graph.getModel().beginUpdate();
mxGraphCreator.start();
graph.getModel().endUpdate();
}
}
});
the 'HTML' part
return(
<Fragment>
...
<div
className="graph-container"
ref={divGraph}
id="divGraph">
</div>
</Fragment>
);
here you see the actual result:
my goal is to show the orange diagram in the top
turquoise area
If you need more information please ask. Thanks

Related

how to display variable value in HTML tag in react

I am working in react project and i need to display book title in HTML element
i created react function component which uses axios to get book from mongo DB
import React from 'react';
import axios from 'axios';
import { useState } from 'react';
export default function Main1() {
var [books,setData]=useState([]);// must be in react function component
var book="";
// use axios get async function
//--------------------------------------------------------------------------------------------
--------------------
const clicked= async () => {// async function started
console.log('clicked');
try {
const response = await axios.get('http://localhost:5000/');// get response from server
console.log(response.data);
//books=response.data;// add data to array
// bks=response.data;
books=response.data;
console.log(books);
} catch (err) {
// Handle Error Here
console.error(err);
};
book=books.find(book=>book.isbn===document.getElementById("p2").innerText);
console.log(book._id);
try {
const responseB = await axios.get('http://localhost:5000/'+book._id);// get response
from server
console.log(responseB.data);
//books=response.data;// add data to array
// bks=response.data;
book=responseB.data;
} catch (err) {
// Handle Error Here
console.error(err);
};
document.getElementById("p1").innerText=book;
// console.log(book.title);
// document.getElementById("p1").innerText=book.title;
// let bk=bks.find(isbn='20');
//console.log(bk.title);
//document.getElementById('p1').innerText=books[0].title;
};
// use axios get async function
//--------------------------------------------------------------------------------------------
--------------------
return (
<div>
<button onClick={clicked}>
hi
</button>
<p id='p2'>
20
</p>
<p>
{book}
</p>
</div>
)
};
the problem when ever i press the button i got the book variable displayed correctly in console log but it cannot be displayed in following tag
{book}
You need to set the state again.
Example
export default function Main1() {
const [books,setData]=useState(['']);// must be in react function component
/* var book=""; You can set on usestate as empty string*/
const clicked= async () => {// async function started
console.log('clicked');
try {
const response = await axios.get('http://localhost:5000/');// get response from server
console.log(response.data);
//console.log(books); // removing it will give you empty object because you are not assigning anything variable
setData(response.data); // set the variable that you get
} catch (err) {
// Handle Error Here
console.error(err);
};

How to access data from custom react hook

Preface: I'm fairly new to React (Coming over from Angular). I know things a similar but different.
I have referenced the following SO threads to no avail in my situation:
React not displaying data after successful fetch
Objects are not valid as a React child. If you meant to render a collection of children, use an array instead
Currently, I'm trying to get my data to display from an API I developed. I'm used to the Angular approach which would call for a ngFor in the template for most data showcase situations.
I'm having trouble wrapping my mind around what I have to do here in order to display my data. The data is expected to be an array of objects which I would then parse to display.
I also receive the following error: Error: Objects are not valid as a React child (found: object with keys {data}). If you meant to render a collection of children, use an array instead.
I've searched high and low for a solution but sadly, nothing I've seen has worked for me. (All of the answers on SO are using the class-based version of React, of which I am not).
You can see my data output in the following screenshot:
I am also including my custom hook code and the component that is supposed to render the data:
CUSTOM DATA FETCH HOOK
interface Drone{
id: number;
name: string;
model: string;
price: number;
}
export function useGetData(urlpath:string) {
const [droneData, setData] = useState<any>()
async function handleDataFetch(path:string){
const result = await fetch(`https://drone-collections-api-jc.herokuapp.com${path}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-access-token': 'Bearer API-TOKEN'
}
})
const response = await result.json();
setData(response)
}
useEffect( () => {
handleDataFetch(urlpath)
})
return droneData
}
THE DRONE COMPONENT
import { useGetData } from '../../custom-hooks'
export const Drones = () => {
let data = useGetData('/drones')
console.log(data)
// const DisplayDrone = ( ) => {
// return (
// Array.prototype.map( data => {
// <div>{ data.name }</div>
// })
// )
// }
return (
<div>
<h1>Hello Drones</h1>
</div>
)
}
Also, for more context, the current code can be found at this repo: https://github.com/carter3689/testing-drone-frontend
Please, help me understand what I'm missing. Many Thanks!
There are several locations that needed to be fixed
In fetchData.tsx
export function useGetData(urlpath: string) {
const [droneData, setData] = useState<any>([]);
async function handleDataFetch(path: string) {
const result = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
...
});
const response = await result.json();
setData(response);
}
useEffect(() => {
handleDataFetch(urlpath);
}, []);
Explanation:
you need a "blank" array for looping through. I guess that the error causes by the fact that at the start, before the data is fetched, there is nothing to loop through. It's same as doing undefined.map(), which is obviously fail.
You need a dependencies array for useEffect. Right now your code will do an infinite loop since everytime it get data, it update the state, thus re-run the useEffect and repeat. Add dependencies array limit when that useEffect will run
In Drones.tsx
return (
<div>
{data.map(item => <div>{item.name}</div>}
</div>
)
Not much to say here. I don't use Angular so I'm not sure why you use Array.prototype.map, but in React you can loop through your variable directly. I also have a CodeSandbox link for your project (I use public API)

How to get onUploadProgress value in an await function from axios?

I'm relatively new to react and having trouble getting the progress value "progressEvent" of axios in the onUploadProgress callback,
I have two files, one for the api call and one for my react component:
Here is a sample of my api.js
function uploadImage(file) {
return axios.post('/api/media_objects', file, {
onUploadProgress: progressEvent => {
let percentComplete = progressEvent.loaded / progressEvent.total
percentComplete = parseInt(percentComplete * 100);
console.log(percentComplete);
}
}).then(response => response.data.id);
}
and my try/catch from the component
try {
const upload = await xxxAPI.uploadImage(formData);
} catch (error) {
console.log(error);
}
How can i retrieve in the "try" the "percentComplete" ?
Thanks !
Generally, I'd advise using some kind of state management (redux/mobx) for controlling this flow. Not to handle it directly from a React component. So the component will trigger a kind of action and the upload process will be handled outside.
But, for a very simple solution, you'd need something like this:
function uploadImage(file, updateProgress) {
return axios.post('/api/media_objects', file, {
onUploadProgress: progressEvent => {
let percentComplete = progressEvent.loaded / progressEvent.total
percentComplete = parseInt(percentComplete * 100);
console.log(percentComplete);
updateProgress(percentComplete);
}
}).then(response => response.data.id);
}
const MyComponent = () => {
const [progress, setProgress] = useState(0);
const onUpload = useCallback(() => {
myApi.uploadImage(data, setProgress);
},[]);
return <div>
<span>Uploaded: {progress}</span>
<button onClick={onUpload}>Upload</button>
</div>;
};
the progress value is stored in the component's state so it can be updated and rendered.
I would also put the try/catch in the API method rather than in the component.
On a more general note. I'd advise using a library such as react-uploady to manage the upload for you. There's are a lot of edge cases and functionality you need to handle typically when uploading files and a small 3rd party like Uploady takes care of it for you: Preview with progress for file uploading in React

FileReader - how to update local state after store is updated?

I'm playing around with a food recognition api.
I have a component with a local state called ingredients.
In the component, I have an input tag that accepts a file image upload and calls cameraHandler method onChange. The method uses FileReader to convert the image into Base64
Once the FileReader is finished encoding the image, the method calls a redux action fetchIngredientsFromImage to post the base64 image into a route to trigger to trigger an API call (to analyze the ingredients in the image).
The response is sent back to the front end, and used to update store.
So basically, the API call is successful, I get the data I need, and store is updated successfully. Great.
But what I also need to do, is update my local ingredients state. But I don't know how to wait for store to be updated before calling setState.
I've tried componentDidUpdate with if(this.props !== prevProps) methodToUpdateLocalState(), but this doesn't work because for some reason the component won't re-render after store is updated.. Turns out that everything inside componentDidUpdate runs first, and store is updated afterwards. I feel like also isn't necessary (probably).
I also tried .then the awaited readers inside cameraHandler, but .then is undefined.
I'd appreciate any input I could get. Really at a loss here, because I have the data, and I just need to somehow grab it so I can setState.
Component
class RecipesSearch extends Component {
state = {
ingredients: [], //need to update this after store is updated, but how?
};
cameraHandler = async (event) => {
const { fetchIngredientsFromImage } = this.props;
const file = event.target.files[0];
const reader = new FileReader();
await reader.readAsDataURL(file);
reader.onloadend = async () => {
const imgBase = reader.result.replace(/^data:image\/(.*);base64,/, '');
await fetchIngredientsFromImage(imgBase); //.then here is undefined
};
};
render(){
<input
className="form-check-input"
type="file"
name="camera"
accept="image/*"
onChange={this.cameraHandler}
/>
}
Actions
const fetchIngredientsFromImage = (imgBase) => async (dispatch) => {
const { data } = await axios.post(`/api/camera/`, { imgBase });
return dispatch(setIngredientsFromCamera(data)); //successfully updates store
};
as a workaround I made an axios.post call inside cameraHandler. Not proud of it, because I'd like to utilize store and keep it consistent with my other methods, but for the time being it'll do I guess.
cameraHandler = async (event) => {
// const { loadIngredientsFromImage } = this.props;
const file = event.target.files[0];
const reader = new FileReader();
await reader.readAsDataURL(file);
reader.onloadend = async () => {
const imgBase = reader.result.replace(/^data:image\/(.*);base64,/, '');
await axios
.post(`/api/camera/`, { imgBase })
.then((response) => this.setState({ ingredients: response.data }));
};
};

Can't render data from API being passed down as props (ReactJS)

I'm really stuck in trying to render some data being passed down as props. I'll include some code and definitions below, but if you feel that I need to include some further code snippets, please let me know (I'm really struggling to find what's causing the error, so I may have missed out the causal issue!).
I first take data from an API which is then used to populate a UserList component via useState (setUsers(data):
useEffect(() => {
async function getUserList() {
setLoading(true);
try {
const url =
"API URL";
const response = await fetch(url);
const data = await response.json();
setUsers(data);
} catch (error) {
throw new Error("User list unavailable");
}
setLoading(false);
}
getUserList();
}, []);
If a user is clicked in the UserList, this changes the selectedUser state of the parent Home component to be the specific user's unique_ID via:
onClick={() => setSelectedUser(unique_ID)}
If the selectedUser changes, the Home component also does a more updated data fetch from the API to get all information relevant to the specific user via their unique_ID:
useEffect(() => {
async function getSelectedUserData() {
try {
const url = `API URL/${selectedUser}`;
const response = await fetch(url);
const data = await response.json();
setSelectedUserData(data);
} catch (error) {
throw new Error("User data unavailable");
}
}
getSelectedUserData();
}, [selectedUser]);
The specific user data is then passed down as props to a child UserInformation component:
<UserInformation selectedUser={selectedUser} selectedUserData={selectedUserData} />
At this point, I can see all the data being passed around correctly in the browser React Developer Tools.
The UserInformation component then gets the data passed via props:
import React, { useEffect, useState } from "react";
function UserInformation({ selectedUser, selectedUserData }) {
const [currentUser, setCurrentUser] = useState({ selectedUserData });
useEffect(() => {
setCurrentUser({ selectedUserData });
}, [selectedUser, selectedUserData]);
return (
<div>
<p>{selectedUserData.User_Firstname}</p>
<p>{currentUser.User_Firstname}</p>
</div>
);
}
export default UserInformation;
And here is where I get stuck - I can't seem to render any of the data I pass down as props to the UserInformation component, even though I've tried a few different methods (hence the <p>{selectedUserData.User_Firstname}</p> and <p>{currentUser.User_Firstname}</p> to demonstrate).
I'd really appreciate any help you can give me with this - I must be making an error somewhere!
Thanks so much, and sorry for the super long post!
I managed to solve this (thanks to the help of Mohamed and Antonio above, as well as the reactiflux community).
import React from "react";
function UserInformation({ selectedUserData }) {
const currentUserRender = selectedUserData.map(
({ User_Firstname, User_Lastname }) => (
<div key={unique_ID}>
<p>{User_Firstname}</p>
</div>
)
);
return (
<div>
{selectedUserData ? currentUserRender : null}
</div>
);
}
export default UserInformation;
As selectedUserData was returning an array instead of an object, I needed to map the data rather than call it with an object method such as {selectedUserData.User_Firstname}.
const currentUserRender = selectedUserData.map(
({ User_Firstname, User_Lastname }) => (
<div key={unique_ID}>
<p>{User_Firstname}</p>
</div>
)
);
The above snippet maps the selected data properties found inside selectedUserData ({ User_Firstname, User_Lastname }), with the whole map being called in the return via {selectedUserData ? currentUserRender : null}.
Hopefully my explanation of the above solution is clear for anyone reading, and a big thanks again to Mohamed and Antonio (as well as a few others in the reactiflux Discord community) for helping!
You're trying to set the current user to an object with key "selectedUserData".
So if you want to access it you've to access it by this key name so change this line currentUser.User_Firstname to currentUser.selectedUserData.User_Firstname

Resources