Hey i'm slightly new to react and wondering what i'm doing wrong...
What i'm trying to do:
- Navbar with a connection button
- onClick call CreateConnection
- add Result to localstorage
- add data to Navbar without refresh
Right now the useEffect doesn't recognize the new addition to localstorage so won't update until page refresh.
Navbar.js:
export const Navbar = () => {
const [connectionAddress, setConnectionAddress] = useState("");
useEffect(() => {
if (localStorage.getItem("address")) {
setConnectionAddress(localStorage.getItem("address"))
}
}, []);
return (
<ProfileNav
address={connectionAddress}
/>
<input type="submit" value="Connect" onClick={CreateConnection} />
)
}
CreateConnection.js
const CreateConnection = async () => {
try {
#Connection code - when this is finished:
window.localStorage.setItem('address', address[0]);
} catch (err) {
console.error(err);
}
}
ProfileNav.js
export const ProfileNav = ({ address }) => {
return (
<div>
<li>{address}</li>
</div>
)
}
export default ProfileNav;
If i simply add window.location.reload(true)
to CreateConnection.js it works but i want it to work without a refresh (possibly with useState
But if i try to put the useState in CreateConnection.js like so:
try {
const [connectionAddress, setConnectionAddress] = useState("");
#Connection code - when this is finished:
window.localStorage.setItem('address', address[0]);
setConnectionAddress(address[0])
} catch (err) {
console.error(err);
}
}
i get the error when i click on the button: Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
CodePudding user response:
As you've discovered, react hooks are only supposed to be called from React components or from other hooks. There's a few more rules that you should look into as well https://reactjs.org/docs/hooks-rules.html.
One option moving forward is to return the address from CreateConnection
, and pass that into setConnectionAddress
in the onClick
handler (or a callback) in your component. Another option (less recommended) would be to create a parameter for setState in CreateConnection
and pass setConnectionAddress
into CreateConnection
.
CreateConnection.js
We'll add the return, and also name this a bit more appropriately
// would recommend naming this with conventional camelCase
const createConnection = async () => {
try {
// Connection code - when this is finished:
window.localStorage.setItem('address', address[0]);
return address[0];
} catch (err) {
console.error(err);
}
}
Navbar.js
Here we a handler updateConnectionAddress
to update the connection when the button is clicked. We could also put that code in the jsx within onClick
's curly braces, but it's usually better to keep the jsx leaner.
export const Navbar = () => {
const [connectionAddress, setConnectionAddress] = useState("");
useEffect(() => {
if (localStorage.getItem("address")) {
setConnectionAddress(localStorage.getItem("address"))
}
}, []);
const updateConnectionAddress = async () = {
const newAddress = await createConnection();
setConnectionAddress(newAddress);
}
return (
<>
<ProfileNav address={connectionAddress} />
<input type="submit" value="Connect" onClick={updateConnectionAddress} />
</>
)
}
In the spirit of the single responsibility principle, I would likely consider moving the local storage code elsewhere from createConnection as well, but I think that's out of scope for this topic