Basically, as you can see in the code, I only want to run one of these API requests. If origCompId is passed, it runs duplicateInstance, otherwise, it runs addNewInstance. origCompId is a query parameter so it takes a second for it to update. This causes addNewInstance to run because origCompId begins as null. I feel like the logic is simple here, but for some reason, I’m just not seeing the solution.
I tried adding the hasRendered ref, as you can see, but that didn’t prevent it from happening. The problem is, that I need this hook to run when the app first loads. I can’t separate these API calls. If the user is loading the app for the first time, it’ll run addNewInstance and load their initial data. If the user duplicates their existing app, it’ll have origCompId and run duplicateInstance.
Any help would be much appreciated!
import { useEffect, useRef } from "react";
import { addNewInstance, duplicateInstance } from "../util/services";
const useAddInstanceToDB = (instance, compId, origCompId) => {
const hasRendered = useRef<boolean | null>(null);
useEffect(() => {
const controller = new AbortController();
if (hasRendered.current) {
if (instance && compId && origCompId) {
duplicateInstance(instance, controller, compId, origCompId);
} else if (instance && compId) {
addNewInstance(instance, controller, compId);
}
}
return () => {
controller.abort();
hasRendered.current = true;
};
}, [instance, compId, origCompId]);
};
export default useAddInstanceToDB;
I think you can set the initial props to null / undefined so that non of the statement will run before your api completes
useAddInstanceToDB(undefined, undefined, undefined);
const useAddInstanceToDB = (instance, compId, origCompId) => {
useEffect(() => {
const controller = new AbortController();
if (origCompId) {
if (instance && compId && origCompId !== -1) { // will not run when hook is inited
duplicateInstance(instance, controller, compId, origCompId);
} else if (instance && compId && origCompId === -1) { // also will not run when hook is inited, only run when instance and compId is defined
addNewInstance(instance, controller, compId);
}
}
return () => {
controller.abort();
};
}, [instance, compId, origCompId]);
};
setOrigCompId to -1 if new instance is needed, or any value other than -1 if duplicate is needed
Possible to have better solutions and I am open for that
You want to run your side effect exactly when your component gets information about origCompId whether it exists or not. Right now your component just can't know when the data is loaded - so you can't do anything. So you need to tell it to your component - you can set origCompId = -1 if it not exists (as suggested by previous author), but I advise you to create a new variable that will tell your component that all the data has loaded.
Related
Anyone know the cause of this error?
warn - Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works
TypeError: Cannot read properties of null (reading 'length')
at eval (webpack-internal:///./node_modules/next/dist/client/dev/error-overlay/hot-dev-client.js:262:55)
I have tried commenting out any components running in pages and creating a new NextJS-ts project from scratch but the error persists.
Yeah, seems like a next 13 bug. It doesn't seem to be breaking anything that I can tell.
It is probably the following const hasUpdates = Boolean(updatedModules.length); just needs to be const hasUpdates = Boolean(updatedModules && updatedModules.length);
I checked this directory:"./node_modules/next/dist/client/dev/error-overlay/hot-dev-client.js:262:55" in v "13.0.1" and v"13.0.3". In both this function is defined same:
function tryApplyUpdates(onHotUpdateSuccess) {
if (!module.hot) {
// HotModuleReplacementPlugin is not in Webpack configuration.
console.error('HotModuleReplacementPlugin is not in Webpack configuration.');
// window.location.reload();
return;
}
if (!isUpdateAvailable() || !canApplyUpdates()) {
(0, _client).onBuildOk();
return;
}
function handleApplyUpdates(err, updatedModules) {
if (err || hadRuntimeError || !updatedModules) {
if (err) {
console.warn('[Fast Refresh] performing full reload\n\n' + "Fast Refresh will perform a full reload when you edit a file that's imported by modules outside of the React rendering tree.\n" + 'You might have a file which exports a React component but also exports a value that is imported by a non-React component file.\n' + 'Consider migrating the non-React component export to a separate file and importing it into both files.\n\n' + 'It is also possible the parent component of the component you edited is a class component, which disables Fast Refresh.\n' + 'Fast Refresh requires at least one parent function component in your React tree.');
} else if (hadRuntimeError) {
console.warn('[Fast Refresh] performing full reload because your application had an unrecoverable error');
}
performFullReload(err);
return;
}
const hasUpdates = Boolean(updatedModules.length);
if (typeof onHotUpdateSuccess === 'function') {
// Maybe we want to do something.
onHotUpdateSuccess(hasUpdates);
}
if (isUpdateAvailable()) {
// While we were updating, there was a new update! Do it again.
// However, this time, don't trigger a pending refresh state.
tryApplyUpdates(hasUpdates ? undefined : onBeforeHotUpdate, hasUpdates ? _client.onBuildOk : onHotUpdateSuccess);
} else {
(0, _client).onBuildOk();
if (process.env.__NEXT_TEST_MODE) {
afterApplyUpdates(()=>{
if (self.__NEXT_HMR_CB) {
self.__NEXT_HMR_CB();
self.__NEXT_HMR_CB = null;
}
});
}
}
// in here its call handleApplyUpdates
}
this is just definition of the function. But difference is when call it: In v"13.0.1", this is how they call
module.hot.check(/* autoApply */ true).then((updatedModules)=>{
// without any check "updatedModules" is just passed assumet it is an array
handleApplyUpdates(null, updatedModules);
}, (err)=>{
handleApplyUpdates(err, null);
});
in v"13.0.3", they made a type checking first
module.hot.check(/* autoApply */ false).then((updatedModules)=>{
if (typeof onBeforeHotUpdate === 'function') {
const hasUpdates = Boolean(updatedModules.length);
onBeforeHotUpdate(hasUpdates);
}
return module.hot.apply();
}).then((updatedModules)=>{
handleApplyUpdates(null, updatedModules);
}, (err)=>{
handleApplyUpdates(err, null);
});
Probably it was a bug in your next.js version. if you use v"13.0.3", it should work
Instead of using export default function Page(){}
Change this to an es6 function const Page = () => {}
And add export default Page to the bottom.
I did this for the Layout as well.
This worked for me somehow :)
I ran into this same issue. I was also following the tutorial with the exception of creating an about page instead of an index. When I used a regular function in about.js I received the same error. The function looked like this:
function AboutPage() {
return <div>The About Page</div>;
}
export default AboutPage;
When I changed it to an arrow function the error went away. The arrow function looked like this:
const AboutPage = () => {
return <div>The About Page and Stuff</div>;
}
export default AboutPage;
I'm not sure why next threw an error on the regular function, but changing it to an arrow functions seems to have done the trick.
I am trying to find an item from a collection, from the code below, in order to update my react component, the propertState object isnt empty, it contains a list which i have console logged, however I seem to get an underfined object when i console log the value returned from my findProperty function... I am trying update my localState with that value so that my component can render the right data.
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState()
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
return (<>{property.propertyName}</>)
}
export default PropertyComponent
There are a few things that you shall consider when you are finding data and updating states based on external sources of data --useParams--
I will try to explain the solution by dividing your code in small pieces
const PropertyComponent = () => {
const { propertyId } = useParams();
Piece A: Consider that useParams is a hook connected to the router, that means that you component might be reactive and will change every time that a param changes in the URL. Your param might be undefined or an string depending if the param is present in your URL
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
Piece B: useSelector is other property that will make your component reactive to changes related to that selector. Your selector might return undefined or something based on your selection logic.
const[property, setProperty] = useState()
Piece C: Your state that starts as undefined in the first render.
So far we have just discovered 3 pieces of code that might start as undefined or not.
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
Piece D: Here is where more problems start appearing, you are telling your code that in every render a function findProperty will be created and inside of it you are calling the setter of your state --setProperty--, generating an internal dependency.
I would suggest to think about the actions that you want to do in simple steps and then you can understand where each piece of code belongs to where.
Let's subdivide this last piece of code --Piece D-- but in steps, you want to:
Find something.
The find should happen if you have an array where to find and a property.
With the result I want to notify my component that something was found.
Step 1 and 2 can happen in a function defined outside of your component:
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
NOTE: I took the liberty of modify your code by simplifying a little
bit your find function.
Now we need to do the most important step, make your component react at the right time
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState({ propertyName: '' }); // I suggest to add default values to have more predictable returns in your component
/**
* Here is where the magic begins and we try to mix all of our values in a consistent way (thinking on the previous pieces and the potential "undefined" values) We need to tell react "do something when the data is ready", for that reason we will use an effect
*/
useEffect(() => {
// This effect will run every time that the dependencies --second argument-- changes, then you react afterwards.
if(propertyId, propertyState.properties) {
const propertyFound = findProperty(propertyId, propertyState.properties);
if(propertyFound){ // Only if we have a result we will update our state.
setProperty(propertyFound);
}
}
}, [propertyId, propertyState.properties])
return (<>{property.propertyName}</>)
}
export default PropertyComponent
I think that in this way your intention might be more direct, but for sure there are other ways to do this. Depending of your intentions your code should be different, for instance I have a question:
What is it the purpose of this component? If its just for getting the property you could do a derived state, a little bit more complex selector. E.G.
function propertySelectorById(id) {
return function(store) {
const allProperties = propertiesStateSelector(store);
const foundProperty = findProperty(id, allProperties);
if( foundProperty ) {
return foundProperty;
} else {
return null; // Or empty object, up to you
}
}
}
Then you can use it in any component that uses the useParam, or just create a simple hook. E.G.
function usePropertySelectorHook() {
const { propertyId } = useParams();
const property = useSelector(propertySelectorById(propertyId));
return property;
}
And afterwards you can use this in any component
functon AnyComponent() {
const property = usePropertySelectorHook();
return <div> Magic {property}</div>
}
NOTE: I didn't test all the code, I wrote it directly in the comment but I think that should work.
Like this I think that there are even more ways to solve this, but its enough for now, hope that this helped you.
do you try this:
const found = propertyState.properties.find(element => element.propertyId === propertyId);
setProperty(found);
instead of all function findProperty
A test in our frankly very complicated React app is taking ~1000 times longer to run than expected, and I'm trying to pin down where/why.
I started by manually calling console.time('name of test') in the test file, and then manually dotting console.timeLog('name of test', 'did a thing') around the application in all the places that were called and seemed likely to cause slow downs.
I noticed a lot of these places were inside React hooks - they aren't slow themselves, but were helping me see how long it took for coffee to get there.
I decided I needed to write a monkey patch in a Jest setupFilesAfterEnv file for logging when React hooks callback are called, and what with (for this example, I'll use useEffect)
const React = require('react');
let timeTestName = null;
let doTimeLog = false;
let prevUseEffect;
beforeAll(() => {
({ timeTestName } = global);
const prevUseEffect = React.useEffect;
React.useEffect = (cb, deps) => {
if(doTimeLog && timeTestName && Array.isArray(deps) && !__filename.includes('node_modules')){
console.timeLog(timeTestName, `Use Effect called with ${JSON.stringify(deps, null, 2)}`): // log useEffect being called with timer
}
prevUseEffect(cb, deps);
}
});
beforeEach(() => {
const { testPath } = expect.getState();
if(testPath.endsWith(`${timeTestName}.test.js`)) {
doTimeLog = true;
console.time(timeTestName); // start timer
} else {
doTimerLog = false;
}
});
afterEach(() => {
doTimerLog = false;
console.log(testToTimeName); // end timer
});
afterAll(() => {
React.useEffect = prevUseEffect;
})
However what I really want as well is the variable names in the dependency list, which I cannot get (at least not without changing non-test code).
One thought I had was using a Jest transformer to make all the arrays into objects so I preserve the variable names as keys; something like:
module.exports = {
process(sourceText) {
return {
code: `convertSquareToCurly(sourceText)`,
};
},
};
module.exports = {
transform: {
'matchDepList':
'<rootDir>/deplistTransformer.js',
},
};
So during tests only my useEffects become:
useEffect(() => foo(a, b, c), { a, b, c})
(I will handle this new object in my monkey patch)
I'm reasonably confident I can make the above work.
Then I in my monkey patch I can call console.log(Object.entries(deps)); and prevUseEffect(cb, Object.values(deps));.
However if I'm concerned that calling Object.values will cause the hook's callback to always be called.
I haven't been able to try the transformer yet, but I don't want to waste time writing it if passing Object.values won't work in place of the untransformed dependency list.
Is there another way to monkey patch and get variable names, or would this work?
I am using useEffect in react to listen to redux(easy-peasy) state change, but I want to listen to 1st value change only.
Because when my page loads the state has a default value and then API call is made and hence data changes but the API is a polling API, hence it keeps getting the data again and again in a short interval of time. But one of my requirement is to listen only to the 1st API data.
This is what I tried:
1st Approach with empty dependency
useEffect(() => {
// my code
},[])
In this case, I get the default value of my state and not the 1st API response.
2nd Approach with state property in the dependency
useEffect(() => {
// my code
},[myState])
In this case, I keep getting the updated response from the API
both of these approaches didn't work for me. So please suggest a better solution.
You can do so using a ref variable and comparing the state with initial state (which could be null, undefined, empty object depending on your implementation):
const hasRun = useRef(false)
useEffect(() => {
if (!hasRun.current && myState !== initialState) {
hasRun.current = true
// run my code
}
},[myState])
A ref variable won't participate in re-rendering.
What I usually do with this is to have a "previous" state.
You can use this hook for to do that one:
const usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]); // only re-run if value changes
// return previous value (happens before update in useEffect)
return ref.current;
}
You can then do:
// I usually create a distinct initial state
const [var, setVar] = useState(null);
const prevVar = usePrevious(var);
useEffect(() => {
if (var !== prevVar && var !== null) {
// logic here
}
}, [prevVar, var]);
Yers you can simplify this by not using the usePrevious hook, but the hook is quite handy and can be used to check the previous value and the current one.
I'm trying to create a basic "Toast" like service in my React app using Alt.
I've got most of the logic working, I can add new items to the array which appear on my view when triggering the add(options) action, however I'm trying to also allow a timeout to be sent and remove a toast item after it's up:
onAdd(options) {
this.toasts.push(options);
const key = this.toasts.length - 1;
if (options.timeout) {
options.timeout = window.setTimeout(() => {
this.toasts.splice(key, 1);
}, options.timeout);
}
}
On add, the toast appears on my page, and the timeout also gets triggered (say after a couple of seconds), however manipulating this.toasts inside of this setTimeout does not seem to have any effect.
Obviously this is missing the core functionality, but everything works apart from the setTimeout section.
It seems that the timeout is setting the state internally and is not broadcasting a change event. It might be as simple as calling forceUpdate(). But the pattern I use is to call setState() which is what I think you might want in this case.
Here is an example updating state and broadcasting the change event.
import alt from '../alt'
import React from 'react/addons'
import ToastActions from '../actions/ToastActions'
class ToastStore {
constructor() {
this.toasts = [];
this.bindAction(ToastActions.ADD, this.add);
this.bindAction(ToastActions.REMOVE, this.remove);
}
add(options) {
this.toasts.push(options);
this.setState({toasts: this.toasts});
if (options.timeout) {
// queue the removal of this options
ToastActions.remove.defer(options);
}
}
remove(options) {
const removeOptions = () => {
const toasts = this.toasts.filter(t => t !== options);
this.setState({toasts: toasts});
};
if (options.timeout) {
setTimeout(removeOptions, options.timeout);
} else {
removeOptions();
}
}
}
module.exports = alt.createStore(ToastStore, 'ToastStore');