Invalid hook call error when trying to set state - javascript

I have a scenario where I am forced to call a trigger method to show a modal from two different places, one using a hotkey combination and another by clicking on a toolbar button. In order to do so I have the following code, where I call the triggerCustomLinkModal to set the state but then I am hit with the Invalid Hook call error.
import { useState, useCallback, useEffect } from "react"
import { Dialog } from "#blueprintjs/core"
const useLocalState = () => {
const [isShown, setIsShown] = useState(false)
const setState = useCallback((state) => {
setIsShown(state)
})
const getState = useCallback(() => {
return isShown
})
return {
setState,
getState
}
}
export const CustomLinkModalUI = () => {
const { getState } = useLocalState()
return (
<>
<Dialog isOpen={getState()} />
</>
)
}
export const triggerCustomLinkModal = () => {
const { setState } = useLocalState()
setState()
}

Expanding from Chris answer in the comments ( You can't use hooks outside React components. -> so you can't call useLocalState() inside triggerCustomLinkModal since triggerCustomLinkModal is not a React component ):
You don't really need the useCallback hook or even the functions itself. Aaccording to react docs :
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
This also means that using useCallback hook to set a state it doesn't really make sense (because useCallback role is just to return a memoized callback)
What you basically need is a state set up in the closest parrent component and pass the setIsShown as a prop as well as the isShown function.
Your current implementation, even if it weren't for the error, it wouldn't refer to the same state since on each useLocalState() you are initializing a fresh new state (so you are not pointing to the same state in CustomLinkModalUI and triggerCustomLinkModal)

Related

useState setter function is not updating state in react

I am using the useIsDirty hook in two components, CustomCodeEditor and EditorFooter, to track whether the code in the Editor has been modified. The hook returns an isDirty state and a setIsDirty function to update it. When I call setIsDirty(true) in the CustomCodeEditor component, the state is updated, but when I call setIsDirty(false) in the EditorFooter component, it doesn't seem to update the isDirty state. I believe this is because the EditorFooter component does not have access to the updated state. Anyone, please help me with this.
useIsDirty:
import { useEffect, useState } from "react"
const useIsDirty = () => {
const [isDirty, setIsDirty] = useState(false)
useEffect(() => {
const handleBeforeUnload = (event) => {
if (isDirty) {
event.preventDefault()
event.returnValue = ""
alert("You have unsaved changes, are you sure you want to leave?")
}
}
console.log("Diryt:", isDirty)
window.addEventListener("beforeunload", handleBeforeUnload)
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload)
}
}, [isDirty])
return { isDirty, setIsDirty }
}
export default useIsDirty
CustomCodeEditor
import Editor from "#monaco-editor/react"
import useIsDirty from "../../hooks/useIsDirty"
const CustomCodeEditor = () => {
const { isDirty, setIsDirty } = useIsDirty()
console.log("isDirty:", isDirty)
return (
<div className="bg-[#1e1e1e] h-full">
<Editor
onChange={(value) => {
updateCode(value || "")
setIsDirty(true) // updating state
}}
/>
</div>
)
}
export default CustomCodeEditor
EditorFooter
import Button from "../reusable/Button"
const EditorFooter = () => {
const { setIsDirty } = useIsDirty()
const handleSave = async () => {
setIsDirty(false)
}
return (
<div>
<Button
onClick={handleSave}
>
Save
</Button>
<Button
onClick={handleSave}
>
Submit
</Button>
</div>
)
}
export default EditorFooter
Hooks are not singleton instances.. when you use useIsDirty somewhere.. it always create new instance, with unrelated states to other ones. If you want to share this state you need to use Context
const IsDirtyContext = createContext(undefined);
const IsDirtyProvider = ({ children }): ReactElement => {
const [isDirty, setIsDirty] = useState(false)
return <IsDirtyContext.Provider value={{isDirty, setIsDirty}}>{children}</IsDirtyContext.Provider>;
};
and then you should wrap your commponent tree where you wanna access it with IsDirtyProvider
after that, you can even create your custom hook that will just return that context:
const useIsDirty = () => {
return useContext(IsDirtyContext)
}
Looking at your question, it looks like you are trying to use the same state in both components. However, the state doesn't work like that. A new instance is created whenever you make a call to useIsDirty from a different component.
If you want to use the state value across two components. You can do that using one of the following ways.
1 - Use a parent and child hierarchy.
Steps
Create a parent component and wrap the two components inside the parent component.
Manage the state in the parent component and pass it using props to the child component.
Create a function in child components that will execute a function from the parent component. The parent component function will hold the code to update the state based on whatever value you receive from the child component.
Now you should be able to share your state between both components.
2 - Use the context api.
If you are not familiar with what context api is, below is a brief explanation.
Context api helps you share data between components, without the need of passing them as a prop to each and every component.
You can use createContext and useContext hooks from context api.
createContext is used to create a new context provider.
useContext hook is used to manage the state globally.
You can get the value from context using this function.
Whenever the state is updated the value will be reflected globally.
Note - Any component that needs to use the value inside the useContext should be wrapped inside the useContext provider component.
Steps to create a context provider.
To create a context you just need to use the react hook createContext
Create a context using below code
const isDirtyContext = createContext();
Wrap your components in the context provider
import {IsDirtyContext} from './path/filename'
<IsDirtyContext.Provider value={[isDirty, setIsDirty]}>{children}</IsDirtyContext.Provider>
If your context is in a separate file, then you can import it into any child component using the import statement.
import {IsDirtyContext} from './path/filename'
Use the context
const [isDirty] = useContext(IsDirtyContext);
Now the isDirty state value is available globally in all components.
Hope this information helps you. Please upvote if this helps you understand and solve the problem.

React component re-rendering many times

i have a react component thats keep re-rendering idk why but i think the reason is the data fetching
data code :
export function KPI_Stock_Utilisation() {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
setKpi_stock_utilisation((existingData) => {
return response.data;
});
});
}, []);
console.log('data get')
return kpi_stock_utilisation;
}
this log displayed many times , and the log in the component too
component code :
import React from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import { useEffect } from "react";
export default function WarehouseUtilisChart(props) {
let kpi_stock_utilisations =KPI_Stock_Utilisation();
let Stock_utilisation = (kpi_stock_utilisations.length / 402) * 100;
console.log('component render')
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
im new with react i tried useEffect inside the componenets but its not working
Calling the react custom hook KPI_Stock_Utilisation several times will for sure render more than once.
in your case I suggest you use useEffect in the same component as I will show you.
import React,{useEffect,useRef} from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import axios from 'axios';
export default function WarehouseUtilisChart(props) {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
const stock_utilisation= useRef(0);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
stock_utilisation.current = (response.data.length / 402) * 100;
setKpi_stock_utilisation(response.data);
});
//this will guarantee that the api will be called only once
}, []);
//you should see this twice, one with the value 0, and another one, the calculated data
console.log('component render',stock_utilisation.current)
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
To note, if you call this component from more than one location, for sure it will render several times - keep that in mind.
On the other hand, all your variables should always start with a lower case and try to name your variables like this: instead of kpi_stock_utilisation change it to kpiStockUtilisation for a better coding practice
You got into infinite loop.
Its hard to explain why it doesn't work as expected, but I can try.
First of all, useEffect with empty array of dependencies works like componentDidMount and fires only after (!) first render.
So you have some value returned from your let kpi_stock_utilisations =KPI_Stock_Utilisation(); then it rendered, after this your useEffect fires a request and set state, setting of state trigger re-render and new value to return, this new value trigger your parent component to return let kpi_stock_utilisations =KPI_Stock_Utilisation(); might run again.
If you are trying to create a custom hook for fetching some info, follow rules of hooks
I hope it helped you

React Hook "useState" or React Hook "useEffect" is called in function which is neither a React function

TL;DR: My GOAL is to separate the API functions, and import them when I need them. And then call them under a componentDidMount scenario. Also, I've been told that async and await shall be used with, since: getCurrentPosition is an asynchronous function.
All the hints that you need to solve your problem are in the error code
Hooks or custom hooks are meant to be used within functional components
A custom hook is a hook that can be called like a function. It however must be prefixed with use to let react know that it is a custom hook
According to the above condition, your Weather component is a class component which either you need to convert to Functional component or avoid using geolocation as a custom hook
Secondly, since geoLocation is meant to be a custom hook you must call it useGetLocation
import React from 'react';
import { useGetLocation } from './getlocation';
const Weather = (props) => {
const geoLocation = useGetLocation();
useEffect(() => {
document.title = "Weather";
}, []);
return(
<>
<h1>Weather</h1>
<h2>{React.version}</h2>
</>
);
}
export default Weather;
import {useState, useEffect} from 'react';
export const useGetLocation = () => {
const [position, setPosition] = useState({});
const [error, setError] = useState(null);
const successHandler = ({coords}) => {
setPosition({
latitude: coords.latitude,
longitude: coords.longitude
});
};
const errorHandler = (error) => { setError(error.message); };
useEffect(() => {
if (!navigator.geolocation) {
setError("Geolocation might not be supported.");
return;
}
navigator.geolocation.getCurrentPosition(
successHandler,
errorHandler);
return () => {}
}, []);
return [position, error];
};
Working demo
Firstly, you have to call the hook inside of a React functional component, and not a class.
The docs:
Hooks ... let you use state and other React features without writing a class.
and
Only Call Hooks from React Functions
Secondly, change getGeolocation to useGeolocation
The docs:
A custom Hook is a JavaScript function whose name starts with ”use” ...
If a function doesn't start with "use", React won't treat it as a hook and won't allow you to call hooks inside it
You can't invoke a hook inside a class. Hooks can only be called inside stateless components. If you want to have access to that information you should either transform it in a HOC or transform the class of the componentDidMount in a function.
https://reactjs.org/docs/hooks-faq.html#what-can-i-do-with-hooks-that-i-couldnt-with-classes

How to use useEffect hook properly with array dependency. I passed state from redux store and still my component renders infinitely

I am using useEffect hook and getting a list of users data with fetch call using function getStoreUsers which dispatches an action on response and stores shopUsers(which is an array) inside the redux store.
In array dependency, I am writing [shopUsers]. I don't know why it is causing infinite rendering.
Here is how I am using useEffect hook:
useEffect(() => {
const { getStoreUsers, shopUsers } = props;
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch(() => {
setLoading(false);
});
}, [shopUsers]);
I want to re-render component only when data inside shopUsers array changes.
If I write shopUsers.length inside array dependency. It stops to re-render.
But, let's suppose I have have a page which opens up when the user clicks on a userList and updates user data on next page. After the update I want the user to go back to the same component which is not unmounted previously. So, In this case array length remains the same, but data inside in of array index is updated. So shopUsers.length won't work in that case.
You can make a custom hook to do what you want:
In this example, we replace the last element in the array, and see the output in the console.
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { isEqual } from "lodash";
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const App = () => {
const [arr, setArr] = useState([2, 4, 5]);
const prevArr = usePrevious(arr);
useEffect(() => {
if (!isEqual(arr, prevArr)) {
console.log(`array changed from ${prevArr} to ${arr}`);
}
}, [prevArr]);
const change = () => {
const temp = [...arr];
temp.pop();
temp.push(6);
setArr(temp);
};
return (
<button onClick={change}>change last array element</button>
)
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Live example here.
Your effect is triggered based on the "shopUsers" prop, which itself triggers a redux action that updates the "shopUsers" prop and thats why it keeps infinitely firing.
I think what you want to optimize is the rendering of your component itself, since you're already using redux, I'm assuming your props/state are immutable, so you can use React.memo to re-render your component only when one of its props change.
Also you should define your state/props variable outside of your hooks since they're used in the scope of the entire function like so.
In your case, if you pass an empty array as a second param to memo, then it will only fire on ComponentDidMount, if you pass null/undefined or dont pass anything, it will be fired on ComponentDidMount + ComponentDidUpdate, if you want to optimise it that even when props change/component updates the hook doesn't fire unless a specific variable changes then you can add some variable as your second argument
React.memo(function(props){
const [isLoading, setLoading] = useState(false);
const { getStoreUsers, shopUsers } = props;
useEffect(() => {
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch((err) => {
setLoading(false);
});
}, []);
...
})

implementation of component did mount in stateless component

Im new to react, i'm trying to implement/learn stateless component, Im having difficulty in using component will mount in stateless component.
My code
const Terms = (actions, commonReducer) => {
componentDidMount() {
actions.userActions()
}
return (
<div className="jobUpdate">
<form onSubmit={(e) => {
e.preventDefault(); actions.userInput(document.getElementById('enteredVal').value)
}}>
<input type="text" id="enteredVal" />
<button type="submit"></button>
</form>
</div>
);
};
I know the stateless component does not have life cycle hooks, but wanted alternate approach to preform component did mount in stateless component.
Any help with this is much appreciated. Thanks in advance
You could always wrap the component in another component, using a pattern known as higher-order components.
A higher-order component (HOC) is a function that takes a component and returns a new component.
Perhaps the most widely used example of this technique is react-redux, which uses the connect() method to create components connected to the redux store.
Instead of creating your own HOC, there are also libraries out there that can do it for you, such as react-redux-lifecycle.
However, what you are attempting with this component is not a very common pattern -- it is much more common to instead keep the handling of business and data in a container component, and leave presentational components to inherit store actions and data from props. Check out Dan Abramov's Presentational and Container Components for a very good overview on how and why to break down components into these two categories!
Starting in React 16.8, you can accomplish the same kind of functionality using a useEffect hook.
In your specific example, we'd have something like this:
import React, { useEffect } from 'react';
// other imports and declarations
function Example() {
// Similar to componentDidMount
useEffect(() => {
// This function will be run on component mount
myAction();
}, []); // The second argument of [] tells react to only perform the effect on mount
return (
<div>
... your component
</div>
);
}
export default Example;
The docs do a great job of explaining this, and I'd encourage you to read up on it. Keep in mind that it's not exactly the same thing going on behind the scenes, and so the patterns will not be a one-to-one correspondence; but these patterns should help with the majority of your cases.
Just know the following basic ideas:
The first argument of a useEffect hook is a "side effect" function. It is always run after the first component's render, and then conditionally afterwards.
This "side effect" function can return a "cleanup" function. The "cleanup" function is run just before the next time the "side effect" function is run. It will always be run before unmounting the component.
The second, optional, argument of a useEffect hook is an array of dependencies.
If any value in the dependency changes, the "side effect" function will be run after the next render.
Anyway, in the meantime, here's a few patterns to emulate class component behavior.
componentDidMount + componentDidUpdate
useEffect(() => {
console.log("This line will be run after each render ");
});
componentDidUpdate when a given value changes
// get myValue from component props
const { myValue } = props;
useEffect(() => {
console.log("This line will be run after each render where myValue changed");
}, [myValue]);
componentDidUpdate when a given value changes, pt. 2
// get myValue from component props
const { myValue } = props;
const myCondition = myValue === "yes";
useEffect(() => {
console.log('This line will be run after each render where the returned value of the statement `myValue === "yes"` changes from false to true or true to false ');
}, [myCondition]);
componentDidUpdate when a given value changes, pt. 3
// get myValue from component props
const { myValue, myValue2 } = props;
useEffect(() => {
console.log("This line will be run after each render where myValue OR myValue2 changed");
}, [myValue, myValue2]);
componentDidMount
useEffect(() => {
console.log("This line will be run only after the first render ");
}, []);
componentWillUnmount
useEffect(() => {
// nothing will be run as a side effect...
return () => {
// but this will be run as clean up
console.log("This line will be run just before the component unmounts");
};
}, []);
componentDidUpdate (without componentDidMount)
import React, {useEffect, useRef} from 'react';
export default function MyComponent() {
// create a reference value which does not trigger a re-render when changed
const isMounted = useRef(false);
useEffect(() => {
if( isMounted.current === false ){
// on mount, set the ref to true
isMounted.current = true;
} else {
// the component is already mounted
console.log("This line will be run after each render except the first ");
}
});
return (<div />);
}
Hope this will be useful to someone.
Throw a dumb work around using HOC (high order component)
const withLifecycles = (MyStatelessComp) => class extends React.PureComponent {
static propTypes = {}
static displayName = "withPure(xxx)"
state = {}
componentDidMount() {}
render() {
return <MyStatelessComp {..this.state, ...this.props} />
}
}
then use it as
MyStatelessWithLifecycles = withLifecycles(props => {
...
return <Bla />
}
Though not sure what's the reason for a stateless component to have the lifecycles there, it's meant to be pure and simple (presentation only).

Categories

Resources