So after making this function work I started to create a loop that would give me feedback from the backend after SSR, I wanted to use hooks so I made it a functional component and started to write but the hook (even with nothing in it) is throwing 2 errors. Invalid Hook Call and A cross origin error was thrown.
I tried changing the file name to jsx, moving the file out of the folder I had because there was a second node modules in there (I thought it was using two versions of React), I also read somewhere just to clear local storage and it was just a in development using localhost problem.
*Edit So i've found that its not even calling the fn: reactToPdfUtils.savePDFNOW(sourceElement, true, undefined, cb) its stopping here
//reactToPdf.js
import React, {useEffect} from 'react';
import { savePDF } from '#progress/kendo-react-pdf';
import { drawDOM, exportPDF } from '#progress/kendo-drawing';
var ClassInstancesStore = require('../libs/goggles/reflux/classInstances-store');
var ClassInstancesActions = require('../libs/goggles/reflux/classInstances-actions');
export const savePDFNOW = (sourceElement, willSaveToDB, pageTemplate, cb) => {
//this hook broke the program as soon as i put it in even with nothing inside
useEffect(() => {
//Functionthat gets called after sending the pdf to the backend
// function onClassInstancesStoreChange(opInfo){
// var e = cloneDeep(opInfo);
// if (e.op === 'Call::StorePassportPDFToDisk') {
// if(e.error){
// console.log(e.ret)
// setPdf({ pdfErrors: e.ret })
// } else {
// console.log(e.ret)
// setPdf({ inProgress: true })
// alert('Successfully created: ' + e.ret.fileName)
// // onSubmit()
// }
// }
// };
// let listeners = [];
// listeners.push(ClassInstancesStore.listen(onClassInstancesStoreChange));
// return function cleanup() {
// _.each(listeners, function(listener) {listener();}); //NOTE: destroy listeners
// }
}, [])
try {
//do all the my functions that make my pdf perfect
} catch (error) {
//snap something went wrong all my awesome error handling
}
};
//previewer.jsx
var React = require('react');
var _ = require('underscore');
var reactToPdfUtils = require('../../../../../components/reactToPdf.js');
handleSave = (sourceElement) => {
reactToPdfUtils.savePDFNOW(sourceElement, true, undefined, cb)
function cb(sendDataContent){
if(sendDataContent.err){
console.log(sendDataContent.message)
} else {
console.log('sucess')
}
}
};
My understanding of the code is that the function handleSave will call the external hook savePDFNOW. If this is what happens, then this will break regardless of the useEffect logic.
The reason for that is that hooks that are extracted outside of the component require their name to start with use
So to allow the hook to run you change its name to useSavePDFNOW.
That being said, I believe this is not a valid use case for useEffect, think of useEffect as componentDidMount/Update. This is relevant to component render cycle rather than event listeners. It makes more sense to do away with the useEffect and keep it a regular function.
A few more things, if you are using the latest react version you don't need to import react. Also it's recommended to use const/let instead of var as well.
Related
I'm learning graphQL and react-relay library.
In these 2 sections:
Rendering Queries: introduce usePreloadedQuery.
Fetching Queries for Render: introduce useQueryLoader.
For short, I will say 1st-query instead of usePreloadedQuery, 2nd-query for useQueryLoader.
Question-1
The 1st-query will use a graphQL and it's generated flow type, query the server, then return the data. That's OK to me.
The 2nd-query seems to do the same thing? What's the difference except the library API/syntax?
Question-2
Here's the sample code in the 2nd section:
import type {HomeTabQuery as HomeTabQueryType} from 'HomeTabQuery.graphql';
import type {PreloadedQuery} from 'react-relay';
const HomeTabQuery = require('HomeTabQuery.graphql')
const {useQueryLoader} = require('react-relay');
type Props = {
initialQueryRef: PreloadedQuery<HomeTabQueryType>,
};
function AppTabs(props) {
const [
homeTabQueryRef,
loadHomeTabQuery,
] = useQueryLoader<HomeTabQueryType>(
HomeTabQuery,
props.initialQueryRef, /* e.g. provided by router */
);
const onSelectHomeTab = () => {
// Start loading query for HomeTab immediately in the event handler
// that triggers navigation to that tab, *before* we even start
// rendering the target tab.
// Calling this function will update the value of homeTabQueryRef.
loadHomeTabQuery({id: '4'});
// ...
}
// ...
return (
screen === 'HomeTab' && homeTabQueryRef != null ?
// Pass to component that uses usePreloadedQuery
<HomeTab queryRef={homeTabQueryRef} /> :
// ...
);
}
The line-1 use import type {HomeTabQuery as HomeTabQueryType} from 'HomeTabQuery.graphql'. And the line-4 use const HomeTabQuery = require('HomeTabQuery.graphql').
I don't understand, aren't these 2 lines do the same thing?
This code was being used in my project which took the page to top whenever route changed and also when you click on a link of the same route you are on. I referred this answer to write the code below. The problem with the linked answer was that it didn't take the page to top if you click on a link which has the same route as you are on currently. So I modified it and wrote it like this.
import React, {useEffect} from 'react'
import { useLocation } from 'react-router-dom'
const Scroll2Top = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
});
return null;
}
export default Scroll2Top
But when I remove the useLocation hook which is not even being used my code stops working. Why is this happening ?
Another similar example I came across is
// not being used but stops working if I remove this
let history = useNavigate();
useEffect(() => {
let termsInput = document.querySelector("#terms > input");
let claimInput = document.querySelector("#claim > input");
if (window.location.href.includes("#terms")) {
termsInput.checked = true;
claimInput.checked = false;
} else if(window.location.href.includes("#privacy")) {
termsInput.checked = false;
claimInput.checked = false;
}
else if (window.location.href.includes("#claim")) {
claimInput.checked = true;
termsInput.checked = false;
}
});
I have no clue why this happens and I was not able to find similar question anywhere on stackoverflow.
Its because you're not providing any dep array for useEffect it means it will run on every render of that component, and when you use useLocation hook it will be called everytime you move between pages which will cause rerender of that component, but if you remove that useLoaction hook there is no factor to cause rerender on that component so useEffect won't run.
An Example I have linked below, that shows the problem I have.
My Problem
I have these two functions
const updatedDoc = checkForHeadings(stoneCtx, documentCtx); // returns object
documentCtx.setUserDocument(updatedDoc); // uses object to update state
and
convertUserDocument(stoneCtx, documentCtx.userDocument);
// uses State for further usage
The Problem I have is, that convertUserDocument runs with an empty state and throws an error and then runs again with the updated state. Since it already throws an error, I cannot continue to work with it.
I have tried several different approaches.
What I tried
In the beginning my code looked like this
checkForHeadings(stoneCtx, documentCtx);
// updated the state witch each new key:value inside the function
convertUserDocument(stoneCtx, documentCtx.userDocument);
// then this function was run; Error
Then I tried the version I had above, to first put everything into an object and update the state only once.
HavingconvertUserDocument be a callback inside of checkForHeadings, but that ran it that many times a matching key was found.
My current try was to put the both functions in seperate useEffects, one for inital render and one for the next render.
const isFirstRender = useRef(true);
let init = 0;
useEffect(() => {
init++;
console.log('Initial Render Number ' + init);
console.log(documentCtx);
const updatedDoc = checkForHeadings(stoneCtx.stoneContext, documentCtx);
documentCtx.setUserDocument(updatedDoc);
console.log(updatedDoc);
console.log(documentCtx);
isFirstRender.current = false; // toggle flag after first render/mounting
console.log('Initial End Render Number ' + init);
}, []);
let update = 0;
useEffect(() => {
update++;
console.log('Update Render Number ' + update);
if (!isFirstRender.current) {
console.log('First Render has happened.');
convertUserDocument(stoneCtx.stoneContext, documentCtx.userDocument);
}
console.log('Update End Render Number ' + update);
}, [documentCtx]);
The interesting part with this was to see the difference between Codesandbox and my local development.
On Codesandbox Intial Render was called twice, but each time the counter didn't go up, it stayed at 1. On the other hand, on my local dev server, Initial Render was called only once.
On both version the second useEffect was called twice, but here also the counter didn't go up to 2, and stayed at 1.
Codesandbox:
Local Dev Server:
Short example of that:
let counter = 0;
useEffect(()=> {
counter++;
// this should only run once, but it does twice in the sandbox.
// but the counter is not going up to 2, but stays at 1
},[])
The same happens with the second useEffect, but on the second I get different results, but the counter stays at 1.
I was told this is due to a Stale Cloruse, but doesn't explain why the important bits don't work properly.
I got inspiration from here, to skip the initial render: https://stackoverflow.com/a/61612292/14103981
Code
Here is the Sandbox with the Problem displayed: https://codesandbox.io/s/nameless-wood-34ni5?file=/src/TextEditor.js
I have also create it on Stackblitz: https://react-v6wzqv.stackblitz.io
The error happens in this function:
function orderDocument(structure, doc, ordered) {
structure.forEach((el) => {
console.log(el.id);
console.log(doc);
// ordered.push(doc[el.id].headingHtml);
// if (el.children?.length) {
// orderDocument(el.children, doc, ordered);
// }
});
return ordered;
}
The commented out code throws the error. I am console.loggin el.id and doc, and in the console you can see, that doc is empty and thus cannot find doc[el.id].
Someone gave me this simple example to my problem, which sums it up pretty good.
useEffect(() => {
documentCtx.setUserDocument('ANYTHING');
console.log(documentCtx.userDocument);
});
The Console:
{}
ANYTHING
You can view it here: https://stackblitz.com/edit/react-f1hwky?file=src%2FTextEditor.js
I have come to a solution to my problem.
const isFirstRender = useRef(true);
useEffect(() => {
const updatedDoc = checkForHeadings(stoneCtx.stoneContext, documentCtx);
documentCtx.setUserDocument(updatedDoc);
}, []);
useEffect(() => {
if (!isFirstRender.current) {
convertUserDocument(stoneCtx.stoneContext, documentCtx.userDocument);
} else {
isFirstRender.current = false;
}
}, [documentCtx]);
Moving isFirstRender.current = false; to an else statement actually gives me the proper results I want.
Is this the best way of achieving it, or are there better ways?
For a school project my group is building a table that's filled with city-data via a database-call.
The skeleton of the table-component is as such:
import React, { useState } from 'react'
function Table(props) {
const [ cities, setCities ] = useState([])
const [ pageNum, setPageNum ] = useState(0)
useEffect(()=> { // this sets up the listener for the infinite scroll
document.querySelector(".TableComponent").onscroll = () => {
if (Math.ceil(table.scrollHeight - table.scrollTop) === table.clientHeight) showMoreRows()
}
//initial fetch
fetchData(0)
},[])
async function showMoreRows() {
console.log("Show more rows!")
await fetchData(pageNum)
}
async function fetchData(page) {
// some code, describing fetching
// EDIT2 start
console.log(pageNum)
// EDIT2 end
const jsonResponse = await {}// THE RESPONSE FROM THE FETCH
if(page) {
setCities([...cities, ...jsonResponse])
console.log("page is true", page)
setPageNum(pageNum + 1)
} else {
console.log("page is false", page) // this always runs and prints "page is false 0"
setCities([...cities, ...jsonResponse])
setPageNum(1)
}
}
return <div className="TableComponent"> { pageNum }
<!-- The rest of the component -->
</div>
}
The table features an "infinite-scrolling"-feature, so when you scroll to the bottom of the page it prints "Show more rows!" and runs fetchData(pageNum) to get more data. At this point, after the initial fetch, the pageNum-variable should be 1 or more, but for some reason the function acts as if it is 0. I put the pageNum-variable on display in the JSX, and I can see that it is 1, but it still prints out "page is false 0" when ever I run it.
When I try to google the issue, it seems the only similar thing could be that I try to read a useState-variable too soon after using setPageNum (before the redraw), but that isn't the case here as far as I can see. I give it plenty of time between tries, and it always says pageNum is zero.
Any ideas as to what I am doing wrong, and how this makes sense in any way?
Thanks for any help!
EDIT: Just tried the code I wrote over, and it seemed to work - however the full code I have doesn't work. Anyone have any ideas about problems related to this, even if the above code might work?
EDIT2: I added a console.log(pageNum) to the fetchData-function, and tested a bit, and it seems that whatever I put into the initial value in useState(VALUE) is what is being printed. That makes NO sense to me.
Help.
EDIT3: Added await, already had it in real code
EDIT4: I've tried at this for a while, but realized as I am using react that I could move the scroll-listener I have down to the JSX-area, and then it worked - for some reason. It now works. Can't really mark any answers as the correnct ones, but the problem is somewhat solved.
Thanks all who tried to help, really appreciate it.
Your staleness issues are occurring because React is not aware of your dependencies on the component state.
For example, useEffect ensures that value of showMoreRows from the scope of the initial render will be called on every scroll. This copy of showMoreRows refers to the initial value of pageNum, but that value is "frozen" in a closure along with the function and won't change when the component state does. Hence the scroll listening won't work as it needs to know the current state of pageNum.
You can resolve the issues by using callbacks to "hookify" showMoreRows and fetchData and declare their dependence on the component state. You must then declare the dependence of useEffect on these callbacks and use a clean-up function to handle the effect being invoked more than once.
It would look something like this (I haven't tried running the code):
import React, { useState } from 'react'
function Table(props) {
const [ cities, setCities ] = useState([])
const [ pageNum, setPageNum ] = useState(0)
useEffect(()=> {
// Run this only once by not declaring any dependencies
fetchData(0)
}, [])
useEffect(()=> {
// This will run whenever showMoreRows changes.
const onScroll = () => {
if (Math.ceil(table.scrollHeight - table.scrollTop) === table.clientHeight) showMoreRows()
};
document.querySelector(".TableComponent").onscroll = onScroll;
// Clean-up
return () => document.querySelector(".TableComponent").onscroll = null;
}, [showMoreRows])
const showMoreRows = React.useCallback(async function () {
console.log("Show more rows!")
await fetchData(pageNum)
}, [fetchData, pageNum]);
const fetchData = React.useCallback(async function (page) {
// some code, describing fetching
// EDIT2 start
console.log(pageNum)
// EDIT2 end
const jsonResponse = await {}// THE RESPONSE FROM THE FETCH
if(page) {
setCities([...cities, ...jsonResponse])
console.log("page is true", page)
setPageNum(pageNum + 1)
} else {
console.log("page is false", page) // this always runs and prints "page is false 0"
setCities([...cities, ...jsonResponse])
setPageNum(1)
}
}, [setCities, cities, setPageNum, pageNum]);
return <div className="TableComponent"> { pageNum }
<!-- The rest of the component -->
</div>
}
This might not totally solve the problem (it's hard to tell without more context), but useEffect runs every render, so things like that 'initial' fetchData(0) are going to run every update, which would probably give you the result from page = 0 every time in that conditional in fetchData.
It's hard to say without more context, but I have one guess.
Try using
setCities(value => [...value, ...jsonResponse])
instead of
setCities([...cities, ...jsonResponse])
Also make sure you use await for resolving promises for requests like:
const jsonResponse = await ...
You can console log it to check if they are not pending and that you get the right property if it's a nested object.
I'm new to react/flux architecture, and I'm missing something...I think. I have two Stores, SubjectsStore.js and WorkDoneStore.js with an AppActions which does the dispatch (code snippets all below). I'm under the impression that any Store that registers with the AppDispatcher will get notice of the event, and it is incumbent on each store to handle the proper action types. There doesn't seem to be any other way of controlling which Store gets called. In my case, I've gotten as far as getting one the SubjectStores registration to be called, but my WorkDoneStore is not getting called. What am I overlooking / doing wrong.
AppActions.js
import AppDispatcher from './AppDispatcher.js';
import WorkDoneConstants from '../constants/WorkDoneConstants.js';
import SubjectConstants from '../constants/SubjectConstants.js';
var AppActions = {
addWorkDoneItem:function(item){
console.log("In app actions addWorkDone");
console.log(WorkDoneConstants.WORKDONE_INSERT);
AppDispatcher.dispatch({
actionType:WorkDoneConstants.WORKDONE_INSERT,
item:item
})
}
}
module.exports = AppActions;
SubjectsStore.js
var AppDispatcher = require('../dispatcher/AppDispatcher');
var SubjectConstants = require('../constants/SubjectConstants');
var EventEmitter = require('events').EventEmitter;
...
AppDispatcher.register(function(action) {
var text;
console.log("why am I in the subjectStore?");
console.log(action.actionType);
console.log(action.item);
switch(action.actionType) {
case SubjectConstants.SUBJECT_CREATE:
text = action.text.trim();
...
WorkDoneStore.js
...
AppDispatcher.register(function(action) {
var text;
console.log("In WorkDoneStore");
console.log(action);
switch(action.actionType) {
case WorkDoneConstants.WORKDONE_INSERT:
item = action.item;
if (item.subject !== '') {
create(item);
WorkDoneStore.emitChange();
}
break;
...
My component
...
handleSubmit: function(e){
e.preventDefault();
var item = {
subject:this.state.subject,
workDone:this.state.workDone,
minutes:this.state.totalMinutes,
startStop:this.state.startStop,
};
console.log("before AppActions.");
AppActions.addWorkDoneItem(item);
},
...
In looking through my Webpack output I noticed that the WorkDoneStore.js wasn't getting included. By forcing it to be included via a call to it, it's now working.