Have a function return JSX, then setState - javascript

I'm new to React (and programming in general) and have come across an issue. I have created this component and using react-copy-to-clipboard package, onCopy I would like to copy a string, set copied to true and then after a couple of seconds set copied to false. When copied is true I want a div with some text to be displayed and when copied is set to false, I want it to disappear.
I have tried using setTimeout without success, as you can see in the code below and I suppose it's not working as I wish as the JSX doesn't re-render when the copied state is false again. I thought of using promises but couldn't figure out how to do that when I want to return JSX.
import React, { useState, useCallback } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import styled from 'styled-components';
const ClipBoard = () => {
const [copied, setCopied] = useState(false);
const onCopy = useCallback(() => {
setTimeout(setCopied(false), 2000)
setCopied(true);
console.log(copied)
}, [copied]);
return (
<div className="app">
<section className="section">
{copied ? <div><p>Copied</p></div> : null}
<CopyToClipboard onCopy={onCopy} text="Text I want to copy">
<button type="button">
<span className="sr-only">
E-mail
</span>
Text I want to copy
</button>
</CopyToClipboard>
</section>
</div>
);
}
export default ClipBoard;

Issue
The code is immediately invoking the state update to enqueue the copied state value to false.
setTimeout(
setCopied(false), // <-- immediately called!
2000
);
setTimeout expects a callback function that will be invoked when the timeout expires.
Solution
Pass an anonymous function to setTimeout to be called when the timeout expires. Since React state updates are enqueued and asynchronously processed, the console.log(copied) will only log the unupdated copied state value closed over in callback scope from the current render cycle. If you want to log state values use the useEffect hook. Also, you should consider the edge case where the component unmounts prior to the timeout expiring and clear any running timers.
Full Example:
const ClipBoard = () => {
const [copied, setCopied] = useState(false);
const timerRef = useRef(); // React ref for timer reference
useEffect(() => {
// Return cleanup function to clear timer when unmounting
return () => {
clearTimeout(timerRef.current);
};
}, []);
useEffect(() => {
// Effect to log any copied state updates
console.log(copied);
}, [copied]);
const onCopy = useCallback(() => {
timerRef.current = setTimeout( // save timer reference
() => setCopied(false), // function callback to update state
2000,
);
setCopied(true);
}, []); // empty dependency array
return (
<div className="app">
<section className="section">
{copied && <div><p>Copied</p></div>}
<CopyToClipboard onCopy={onCopy} text="Text I want to copy">
<button type="button">
<span className="sr-only">
E-mail
</span>
Text I want to copy
</button>
</CopyToClipboard>
</section>
</div>
);
}

Related

Invalid hook call. Hooks can only be called inside of the body of a function component. - When using state

I am building a Blog App in react and I am trying to send argument to another functional component and Also trying to use useState to set state when call, But When I click on button to send argument then, Argument is successfully working (sending to component) but State is not setting through useState.
It is showing error every time I define useState.
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
I have tried many times but it is still not working.
App.js
function App() {
const sendArgument = () => {
AnotherComponent("argument_1")
}
return (
<div>
<button type="button" onClick={sendArgument}>Send Argument</button>
</div>
)
}
function AnotherComponent(arg_1) {
// Error shows when I define useState here...
const [isOpen, setIsOpen] = useState(false);
// Argument is successfully showing
console.log(arg_1)
// Set to true when fucntional component calls from another component
setIsOpen(true)
const setBooleanTrue = () => {
setIsOpen(false)
}
return (
<div>
<button type="button" onClick={setBooleanTrue}>Set state</button>
</div>
)
}
It is keep showing that error, and not setting state, when I remove useState then error is not showing.
Any help would be much Appreciated. Thank You in Advance
We cannot call a hook inside a nested function which is what is happening since AnotherComponent is inside the sendArgument function.
You can render anotherComponent inside the return function of App Component itself. Define the state in the App component itself and send it to AnotherComponent as prop.
https://reactjs.org/docs/hooks-rules.html
I think you can use the state inside the nested component/function.
I don't have a exact context of your code but based on your code snippet
Here i have refactored your code.
import { useState } from "react";
export default function App() {
const [argToSend, setArgToSend] = useState();
const sendArgument = () => {
setArgToSend("Some Argument");
};
return (
<div>
<button type="button" onClick={sendArgument}>
Send Argument
</button>
<AnotherComponent arg={argToSend} />
</div>
);
}
function AnotherComponent({ arg }) {
// Another component's own state
const [isOpen, setIsOpen] = useState(false);
const setBooleanTrue = () => {
setIsOpen(!isOpen);
};
return (
<div>
<h4>
Argument received from Another component:-
<span style={{ color: "red" }}>{arg}</span>
</h4>
<h3>{isOpen ? "True" : "False"}</h3>
<button type="button" onClick={setBooleanTrue}>
Set state
</button>
</div>
);
}
As suggested by Parth you should send data from a component to another as props
Also I would suggest you to use any code formatter
Hope this is helpful :)

re-render useState for each object in an array

When I click the MultipleComponent button, all logs in the function return null.
The second time I click it, it returns the previous values.
How can I get the current status in each log within the map function?
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { observer } from "mobx-react-lite";
function App() {
const [component, setComponent] = useState([]);
useEffect(() => {});
const newArray = [1, 2, 3];
const Test = observer(() => {
return (
<div>
<p>Test</p>
</div>
);
});
const Test2 = observer(() => {
return (
<div>
<p>Test2</p>
</div>
);
});
const Test3 = observer(() => {
return (
<div>
<p>Test3</p>
</div>
);
});
function MultipleComponent() {
newArray.map(async (x) => {
if (x === 1) {
setComponent((ps) => [...ps, Test]);
console.log(component);
} else if (x === 2) {
setComponent((ps) => [...ps, Test2]);
console.log(component);
} else {
setComponent((ps) => [...ps, Test3]);
console.log(component);
}
});
}
return (
<div>
{component.map((Input, index) => (
<Input components={component} key={index} />
))}
<button onClick={() => setComponent([...component, Test])}>
Single Component
</button>
<button onClick={() => MultipleComponent()}>Multiple Component</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
codensadbox: https://codesandbox.io/s/react-hooks-useeffect-forked-shxvl6
When I click the MultipleComponent button, all logs in the function return null.
The second time I click it, it returns the previous values.
React state updates are asynchronous and do not update any values in the current scope. They trigger a render, and then your component executes again with that new state.
See this for more info: The useState set method is not reflecting a change immediately
How can I get the current status in each log within the map function?
You can't get the state that has been changed until the next render, but you don't need to because you have the value that you set. Just use that if you need to. For example:
setComponent((ps) => {
const newState = [...ps, Test];
console.log(newState);
return newState;
});
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
Your effect has no dependencies, which means that it will run after every render. Just pass an array of dependencies to the effect in order to only execute it when those change. If you pass an empty array, then it will only every execute once.
useEffect(() => console.log('I run only once'), []);
See the docs on useEffect for more: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Hi as per the documentation "calling the set function does not change state in the running code". If you need to use the next state, you can save it in a variable before passing it to the set function.
Here is a link of your updated code.
https://codesandbox.io/s/react-hooks-useeffect-forked-m7ipwb?file=/src/index.js
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
By default, Effects run after every render. You can tell React to skip unnecessarily re-running the Effect by specifying an array of dependencies as the second argument to the useEffect call. Start by adding an empty [] array

LocalStorage not updated with the latest value

I am new to react and I am experimenting with incrementing a value and storing it into local storage. I was able to write down the following code bellow, however, the last instance of the incremented number is not updated the local storage value. For example, if I press "+1" twice and the number is 10, the dom is updated twice and shows the number 12, but the value stored on local storage is 11. Why is this happening?
import { useState } from 'react'
function About () {
const localStorageValue = parseInt(localStorage.getItem('value'))
const [value, setValue] = useState(localStorageValue);
function remove() {
setValue((add) => add - 1)
localStorage.setItem('value', parseInt(value))
}
function add() {
setValue((add) => add + 1)
localStorage.setItem('value', parseInt(value))
}
return (
<>
<h1>About page</h1>
<button onClick={remove}>-1</button>
{value}
<button onClick={add}>+1</button>
</>
)
}
export default About;
Use the refresh button and it will reflect the changes
Issue here is asynchronous behaviour of setState i.e. it will update the state value with a bit of delay and without stopping for complete execution of setState it will proceed to next line of code i.e. localStorage.setItem().
In your case setState is setValue
So to overcome this you can need some kind of callback function behaviour to achieve this with useState hook so you can use useEffect for the same as follows:
import { useState, useEffect } from "react";
function About() {
const localStorageValue = parseInt(localStorage.getItem("value"));
const [value, setValue] = useState(localStorageValue);
useEffect(() => {
localStorage.setItem("value", parseInt(value));
}, [value]); // only rerun if value changes
function remove() {
setValue((add) => add - 1);
}
function add() {
setValue((add) => add + 1);
}
return (
<>
<h1>About page</h1>
<button onClick={remove}>-1</button>
{value}
<button onClick={add}>+1</button>
</>
);
}
export default About;
This will ensure that it will run everytime after successive state update.
PS. There's no need to use parseInt while storing as it will be stored as string only.

React hook stale closure: setState only triggers twice in callback function of a subscription

I' trying to build a toast message API for React. My goal is to provide a fireNotification() api that can be called anywhere in the app and have React render the toast component.
I built this simple notification manager with sub/pub pattern and hope to be able to subscribe to new notifications in a useEffect hook
const notifications = [];
const listeners = new Set();
function subscribe(callback) {
listeners.add(callback);
}
function publish() {
listeners.forEach((cb) => {
cb(notifications);
});
}
export function fireNotification(content) {
notifications.push(content);
publish();
}
export default function App() {
const [state, setState] = React.useState();
React.useEffect(() => {
subscribe((updated) => {
setState(updated);
});
}, []);
// state will be logged correctly 2 times
// won't be updated after that
console.log("state", state);
return (
<div className="App">
<button onClick={() => {fireNotification('test')}}>fire</button>
</div>
);
}
codesandbox
However, fireNotification() will only trigger setState twice
From the 3rd time onward, the state is not updated at all.
I'm able to make state update work by changing setState(updated) to setState([...updated]), but not sure why it works.
Can someone explain why setState(updated); only triggers twice? Thanks!
You need to provide data to watch for changes, to the useEffect
React.useEffect(() => {
subscribe((updated) => {
setState(updated);
});
}, [updated]);
Other wise the useEffect will run only twice

Why does my onClick handler not log out the latest version of state and how can I troubleshoot this?

Sandbox
import React, { useEffect, useState } from "react";
export default function App() {
let [button, setButton] = useState(null);
let [num, setNum] = useState(5);
function revealState() {
console.log(num);
}
function changeState() {
setNum(Math.random());
}
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, []);
return (
<>
{button}
<button onClick={changeState}>Change state</button>
</>
);
}
Clicking on the 'Log state button' successfully logs num state. Clicking on the 'Change state button' successfully changes num state. Re-clicking the 'Log state button' doesn't log the updated value of state - it logs the old one.
Why is this? My guess is that, since useEffect runs only the once, it references only the first revealState function which references only the first num variable. Because it's not in the component's return statement it doesn't get 'refreshed'.
Whatever the cause of the problem, what're some work-arounds? Some of the requirements are:
the tag can't be rendered directly in the return statement.
we have to have the useEffect that's there and it needs to have a dep array of some sort (its undesirable for it to fire every-time the function component is re-executed).
In the real project, some important changes to the tags useEffect's callback renders might get made - therefore it's impractical to re-run the useEffect by putting something like num in its dep array.
IMO, the neatest solution is to simply add the updated event listener, every time the page is rendered:
useEffect(() => {
el.onclick = onClickHandler
});
The event listener always has access to the latest state (and props). IMO, this solution is more scalable than previously-mentioned solutions - if my event listener has to track the latest versions of multiple state & props, this could get messy. With this, all I need to do is add extra listeners in into this one useEffect callback. Thoughts?
import React, { useEffect, useState, useCallback } from "react";
export default function App() {
let [button, setButton] = useState(null);
let [num, setNum] = useState(5);
const revealState = useCallback(() => {
console.log(num);
}, [num])
function changeState() {
setNum(Math.random());
}
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, [revealState]);
return (
<>
{button}
<button onClick={changeState}>Change state</button>
</>
);
}
you can listen to the revealState in useEffect. which gets initialized only when num is changed achieved using useCallback. so whenever you click the button the num is changed which initializes the revealState function and not initialized on other rerenders
you have to add num as dependency to useEffect:
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, [num]);
After more clarification on your problem it seems you need to watch over both the num and the HTML state. Combining both Alan and kishore's code together is the solution. Since the useEffect is only watching the num in Alan's answer so if any changes to the tag will not cause it to rerun. kishore also mentioned another fix which is to use the useCallback but what needs to be watch is the button and not the num. Like this:
const updateButton = useCallback(function (newButton) {
setButton(newButton);
}, [button])
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
updateButton(el)
}, [num]);
This will tell useEffect to watch num and will return a new button only when button state is changed.

Categories

Resources