Hi guys!
I'm working on the upload image feature into Cloudinary using ReactJS from the client side, then I met a problem.
Description
I added a script tag inside a component with some lines of code inside React.useEffect function. Inside the script tag, I had the data of Cloudinary return when it fetches successfully, and I wanted to get the data by using setState inside the script content.
But I cannot get the data.
Question
How can I use setState inside a string? Or is there any way to get the data that was returned inside a string?
Here is my code.
React.useEffect(() => {
const scriptCloudinaryCdn = document.createElement('script');
scriptCloudinaryCdn.src =
'https://upload-widget.cloudinary.com/global/all.js';
scriptCloudinaryCdn.async = true;
const widgetScript = document.createElement('script');
// I wanted to get the data that was returned from this
const widgetContent = ` var myWidget = cloudinary.createUploadWidget({
cloudName: 'cloudname',
uploadPreset: 'upload_preset'}, (error, result) => {
if (!error && result && result.event === "success") {
// this is the data from Cloudinary, I wanted to carry this with setState
console.log('Done! Here is the image info: ', result.info);
}
}
)
document.getElementById("upload_widget").addEventListener("click", function(){
myWidget.open();
}, false);`;
widgetScript.text = widgetContent;
document.body.appendChild(scriptCloudinaryCdn );
document.body.appendChild(widgetScript);
return () => {
document.body.removeChild(scriptCloudinaryCdn );
document.body.removeChild(widgetScript);
};
}, []);
Thank you guys very much!
You can create some global event-emitter instance (for example using this library), subscribe to an event in react component, and then emit the event from widget's success callback.
If you do not want to use global emitter, you can use DOM event emitter instead, like so:
const [uploadedImageData, setUploadedImageData] = useState(null);
useEffect(() => {
const onImageUplaoded = (event) => {
setUploadedImageData(event.detail);
};
document.getElementById("upload_widget").addEventListener('imageuploaded', onImageUploaded);
return () => {
document.getElementById("upload_widget").removeEventListener('imageuploaded', onImageUploaded);
}
}, []);
and inside of your widget's success callback you can do:
uploadPreset: 'upload_preset'}, (error, result) => {
if (!error && result && result.event === "success") {
const event = new CustomEvent('imageuploaded', { detail: result.info });
document.getElementById("upload_widget").dispatchEvent(event);
}
}
Of course you can think about improvements; this is just one of ideas how to solve your problem.
Related
I'm working with WebSocket and having an issue with a function showing incorrect data, All my code used to work with a Class-based component, I'm only trying to convert a class component that actually calls the connect method of Websocket to a functional-based component. The Websocket is class-based for instance.
So I have a WebSocket which on socket New Message sets callbacks.
socketNewMessage(data) {
console.log(data);
const parsedData = JSON.parse(data);
const command = parsedData.command;
if (command === "userChatGroups") {
this.callback[command](parsedData.chatGRoups);
}
if (command === "new_userChatGroups") {
this.callback[command](parsedData.chatGRoup);
}
}
and here are the callbacks defined -
addCallbacks(userChatGroups, newUserChatGroup) {
this.callback["userChatGroups"] = userChatGroups;
this.callback["new_userChatGroups"] = newUserChat;
}
and then websocket finally return -
const WebSocketInstance = Websocketservice.getInstance();
export default WebSocketInstance;
Now the class-based component which I'm trying to convert to functional based -
let's call this component Chats-
this calls the connect method and a Websocket instance is returned.
useEffect(() => {
if (loggedInUserDetail) {
WebSocketInstance.connect(loggedInUserDetail[0].id);
}
}, [loggedInUserDetail]);
I have a helper function which checks the status of websocket -
function waitForSocketConnection(callback) {
setTimeout(() => {
if (WebSocketInstance.state() === 1) {
console.log("connection is secure");
callback();
return;
} else {
console.log("waiting for connection");
waitForSocketConnection(callback);
}
}, 10);
}
and I check the status and map my callbacks - here is the problem-
useEffect(() => {
waitForSocketConnection(() => {
WebSocketInstance.addCallbacks(
setChatGroups,
addNewChatGroup
);
});
}, [loggedInUserDetail]);
I have a state to manage CHatGroups -
const [groups, setGroups] = useState([]);
the setChatGroups (which initially loads all the groups the users are associated with works fine and sets the state of chatsGroups) as -
const setChatGroups = useCallback((userChatGroups) => {
setGroups(userChatGroups); //whatever data it recieved from the websocket.
}, []);
but the function addNewChatGroup always shows groups value as an empty array [] (it was updated earlier with setChatGroups). If I manually check the value of groups it is an array of n length, but in addNewChatGroup function, it always shows an empty array with the initial value [].
const addNewChatGroup = useCallback(
(newCHatGroup) => {
console.log(groups); **error -> this is always empty array**
// here I have to update. add the received value with the previous
},
[groups] // callbacks used hoping this will bind
);
In the class-based component I used to set callbacks on the constructor and used to bind with this, but I'm not able to do it here, can anyone help what I'm missing?
I'm sure it is a binding issue. maybe. May I know the reason for this binding failure?
Well if I understood correctly your problem, it could be link to multiple things.
The problem actually is I don't have a clear view on all your components, maybe you can try to paste a sandbox link or something like that with a "simple structure".
I tried to reproduced a typescript version, I don't know if it could help:
class MyWebSocket {
private static _instance: MyWebSocket;
public callbacks: any = {};
public connected: boolean = false;
public socketNewMessage(data: any): void {
const parsedData = JSON.parse(data);
console.log('new message received:', parsedData);
const command = parsedData.command;
if (command === "new_userChatGroups") {
this.callbacks[command](parsedData.newGroupAdded);
}
}
public addCallbacks(elements: {command: string, func: Function}[]) {
console.log('adding callbacks...', elements);
elements.forEach(element => {
this.callbacks[element.command] = element.func;
});
}
public connect(): void {
setTimeout(() => this.connected = true, 1100);
}
public static getInstance(): MyWebSocket {
return this._instance || (this._instance = new MyWebSocket());
}
}
class SocketUtils {
static waitForSocketConnection(callback: any): void {
const waitingInterval = setInterval(() => {
if (MyWebSocket.getInstance().connected) {
console.log('socket is connected! processing callback...');
clearInterval(waitingInterval);
callback();
return;
} else {
console.log('socket is not connected after 1sec, waiting...');
}
}, 1000);
}
}
class Chat {
groups: string[] = ['group of JStw'];
new_userChatGroups(group: string) {
this.groups.push(group);
}
}
class Main {
constructor() {
const myChat = new Chat();
MyWebSocket.getInstance().connect();
// waiting connections.
SocketUtils.waitForSocketConnection(() => {
console.log('waitForSocketConnection is triggered, adding callbacks...');
// adding callbacks
MyWebSocket.getInstance().addCallbacks([{command: 'new_userChatGroups', func: myChat.new_userChatGroups.bind(myChat)}]);
});
// Waiting 5min to dispatch an message
setTimeout(() => {
// testing eventing after getting connection
MyWebSocket.getInstance().socketNewMessage(JSON.stringify({command: 'new_userChatGroups', newGroupAdded: 'group of Ranu Vijay'}));
}, 5000);
setTimeout(() => {
console.log('program finished, results of chat groups:', myChat.groups);
}, 10000);
}
}
new Main();
Output:
I'm more specialized on functional component by using react so without a link to investigate all your code, it will be complex to help.
I don't know if you are using a library, but there is this one: https://www.npmjs.com/package/react-use-websocket which seems to be really ultra simple to use without managing socket connection/disconnection.
For me if I had to implement it, I would say:
Component ChatGroup which is using websocket hook const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl); and contains the state groups, setGroups.
Component Chat which can use the sendMessage from props of ChatGroup component and call it from this component if a join a group.
Then your "parent" component is managing the state and is controlling the data.
Problem
The event to send data to renderer is triggered on a file change with chokidar. When the file watcher is triggered and the process sends the event to the renderer.
My problem is that when the EmitterEvent is triggered I input the current state of my useState() variable but only the initial state is passed to my function.
Edited: my problem is that I can not pass the updated data variable to the updateData(newData) function which is called from the emitter inside the preload.js.
Question
How can I pass the state variable data to the call ?
Is there a way that I can change my preload.js in order for the api.receive function to return a string in order not to have to pass a function to the emitter ? (please check the updateData(newData) function for more info)
Is there a better way to achieve this ?
This could also help me to initialize the data for the first render.
preload.js
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["file", "save"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["file", "save"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
}
electron.js
function ReadNodesFileToIpc(path) {
fs.readFile(path, (error, data) => {
win.webContents.send("file", data);
});
}
Code in component that receives data
function MyForceGraphComponent(){
const [data, setData] = useState({ nodes: [{ id: 0, otherinfo: [] }], links: [] });
var isDataInittiallized = 0;
...
function updateData (newData, data) {
if (data.nodes.length !== 1){
// do stuff
setData({ nodes: data.nodes, links: data.links });
}else{
if (!isDataInittiallized){
setData({ nodes: newData.nodes, links: newData.links });
isDataInittiallized = 1;
}
}
}
...
useEffect(() => {
...
window.api.receive("file", (bytesArray) => {
var newData = JSON.parse(String.fromCharCode.apply(null, bytesArray));
updateData(newData); // Check function bellow
});
...
}, []);
}
updateData(newData) (a function inside my components fuction)
isDataInittiallized is a variable inside the component that the change got passed to the emitter
data is my variable from the useState() function that the change did NOT got passed to the emitter even though the setData() previously changed the data successfully. So the length remains 1 and it contains the same elements from when it was firstly initialized .
Other info
Have tried to play with passing the data variable to the receive function without any success.
Most probably when the emitter is getting set the function passed (the one that does JSON.parse) is getting passed along and never changed after.
Since the issue is related to stale data inside the updateData function, I suggest to make the following updates:
// Simplified for the sake of brevity
function MyForceGraphComponent() {
const [data, setData] = useState({ nodes: [{ id: 0 }] })
// isDataInitialized needs to be tracked just as any other state
const [isDataInitialized, setIsDataInitialized] = useState(false)
// Wrap `updateData` in `React.useCallback` to prevent stale data
const updateData = useCallback(
(nextData) => {
// skip the update, no need to update the state with existing data
if (isDataInitialized) return;
setData({ nodes: nextData.nodes })
setIsDataInitialized(true) // set to `true` to prevent future updates
},
[isDataInitialized, setIsDataInitialized]
)
const handleReceivedData = useCallback(
(bytesArray) => {
const nextData = JSON.parse(...)
updateData(nextData)
},
[updateData]
)
useEffect(() => {
window.api.receive('file', handleReceivedData);
}, [handleReceivedData])
}
Have a look at this example that mimics what you're trying to do:
CodeSandbox
There some error in you code that i found. When you call updateData(newData), data is null, data.nodes will not work. you can modify you code, "if (data
&& data.nodes && data.nodes.length !== 1)"
wondering if anyone can assist me in this matter. I'm following the documentation for https://rnfirebase.io/firestore/usage. it does not work for my use case for some reason.
I just need to set the data, which it works and then read it back so i can push it onto my state and i'll render it.
I just can't read the data back properly. This addItemFunction is trigger when when user click on a button to add.
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
//RETURN SNAPSHOT NOT WORKING
console.log('user_added');
firestore().collection(userEmail).doc(final).onSnapshot(documentSnapshot =>{
console.log("here" + documentSnapshot.data());
});
}
Thanks for your time.
If you are using react with hooks I would suggest you put the onSnapshot listener in a useEffect hook:
useEffect(() => {
const unsubscribe = firestore
.collection(collectionName)
.doc(docId)
.onSnapshot(
(documentSnapshot) => {
const document = documentSnapshot.data();
console.log(document)
},
(error: Error) => {
throw error;
}
);
return () => unsubscribe();
}, [ docId, collectionName]);
this approach will separate concerns and the snapshots will run every time there is a change on the document, then where I put the console.log you could set the document to state.
Another approach will be to use get() instead of onSnapshot like:
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
console.log('user_added');
firestore().collection(userEmail).doc(final).get().then(() => {
console.log("here" + documentSnapshot.data());
})
}
}
}
this approach will not subscribe to changes and it will return the new updated document every time you call the addItemFunction
I am new to react native. I am trying to save signature Image but the function is not even calling. I think its Icon on press issue but when I do console.log on onPress then Its working fine. I think something wrong in function. please help. thanks
here is code.
import SignatureCapture from 'react-native-signature-capture';
this.ref = React.createRef();
this.sign = React.createRef();
saveSign = () => {
this.refs['sign'].saveImage();
};
_onSaveEvent = (result) => {
this.checkAndroidPermission(result);
};
checkAndroidPermission = async (result) => {
if (Platform.OS === 'ios') {
save(result);
} else {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
// Once user grant the permission start downloading
this.save(result);
} else {
if (Platform.OS === 'android') {
ToastAndroid.show('Storage Permission denied.', ToastAndroid.SHORT);
} else {
AlertIOS.alert('Storage Permission denied.');
}
}
} catch (err) {
// To handle permission related exception
console.warn('tryerr', err);
}
}
};
save = async (result) => {
const paths = `${RNFetchBlob.fs.dirs.DCIMDir}/${new Date().getTime()}.png`; // where u need to put that
try {
RNFetchBlob.fs
.writeFile(paths, result.encoded, 'base64') //data.base64 is your photo with convert base64
.then((value) => {
RNFetchBlob.fs
.scanFile([{ path: paths }]) //after save to notify gallry for that
.then(() => {
const file = {
uri:
Platform.OS == 'android'
? 'file://' + paths //path to your file
: paths,
name: 'sign.png', // which go on server on your file name
type: 'image/png', // e.g. 'image/jpg'
};
this.setState({ base64: file });
console.log('scan file success');
console.log('this is fuckiiing file' + JSON.stringify(this.state.base64));
})
.catch((err) => {
console.log('scan file error');
});
})
.catch((e) => console.log(e.message));
} catch (error) {
console.log('fileerror', error.message);
}
};
please ignore this.
I am new to react native. I am trying to save signature Image but the function is not even calling. I think its Icon on press issue but when I do console.log on onPress then Its working fine. I think something wrong in function. please help. thanks I am new to react native. I am trying to save signature Image but the function is not even calling. I think its Icon on press issue but when I do console.log on onPress then Its working fine. I think something wrong in function. please help. thanks
Ok solution is here. just remove this line
saveImageFileInExtStorage={true}
So a few things first, this is very little information to work off, without seeing your views. I'd suggest reading up on how refs work, it's helpful to understand it a little better.
When using refs. You need to use the following syntax to access the object it's pointing to this.refs.current.
Also you create a const ref = React.createRef(); but in the saveSign function you use this.refs maybe a spelling mistake with that variable.
If you are using a Class-based Component using const ref = React.createRef(); is fine. But for a Functional-based Component you should rather use const ref = React.useRef();. Both will work but createRef in a Functional-based Component might cause some undesirable things to happen. useRef is a hook and will work better.
Now some changes I'd make based on my experience, your current code will probably work because it's based on the example from that package but there are better ways of doing it:
I had a look at the example code for the package you are using, which I'm assuming you are using it.
I'd recommend trying to replace the ref inside the <SignatureCapture /> component with this: ref={ref} then inside the saveSign() function calling this.ref.current.saveImage(); rather than this.refs["sign"].saveImage();.
For the <TouchableHighlight> component inside render, rather write your onPress call like this:
onPress={saveSign} and rewriting your saveSign() function like this:
saveSign = () => {
this.ref.current.saveImage();
}
Otherwise, I suggest just debugging why your functions are not being called. It might be an issue with binding some of those functions if you are using a Class-based component.
For my sample application I was trying to check whether internet connection is there or not, I was using a reusable hook for this as shown below:
function useNetwork() {
const [isOnline, setNetwork] = useState(window.navigator.onLine);
const updateNetwork = () => {
setNetwork(window.navigator.onLine);
};
useEffect(() => {
window.addEventListener("offline", updateNetwork);
window.addEventListener("online", updateNetwork);
return () => {
window.removeEventListener("offline", updateNetwork);
window.removeEventListener("online", updateNetwork);
};
});
return isOnline;
}
The problem is it's working in Chrome, but when I checked in Safari when I tried turning off the wifi still isOnline returns true, I tried in the console of safari also, as window.navigator.isOnline it returns true.
Reading through different questions, here Danilo recommends to send a httpRequest and check it.
So should I send a get request to any site like google.com so I can know the status and set my onLine value as true. Because I think I need to poll this, since the above hook takes care when the dom is changed.
How can I achieve this, or is there any better way? How can I implement this in the above reusable hook.
you can do something like this,and have an api end point/or call any reliable api
const useNetworkStatus = () => {
const [isOnline, setStatus] = useState();
useEffect(() => {
(async () => {
try {
window.networkPoll = setInterval(async () => {
const networkStatus = await fetch(/*your api*/);
if (networkStatus.code != 200) {
setStatus(false);
}
if (networkStatus.code == 200) {
setStatus(true);
}
}, 5000);
}
catch (error) {
setStatus(false);
}
})();
}, []);
};