I have a component that after a user has clicked the button, a message appears and should disappear after 3 seconds. I'm trying to use useEffect to enable the timeout, but can't get it working:
const { useState, useEffect } = React
const SectionHeader = (props) => {
const {title, button, link, type} = props;
const [copy, setCopy] = useState(false)
const [showMessage, setShowMessage] = useState(true);
useEffect(() => {
setTimeout(() => {
setShowMessage(false)
}, 3000)
}, [])
const copyToClipboard = (title) => {
navigator.clipboard.writeText(window.location.href + '#' + title.toLowerCase().replaceAll(" ", "-").replaceAll("'", ""))
setCopy(true)
}
return (
<div id={title.toLowerCase().replaceAll(" ", "-").replaceAll("'", "")}>
<b>{title}</b>
<div onClick={() => copyToClipboard(title)}>Copy to clipboard</div> {copy ? 'copied' : ''}
</div>
)
}
ReactDOM.render(<SectionHeader title="Test" />,
document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root">
useEffect execute the code written in its block only when the website is loaded for the first time or when the dependencies changes.
In your code you have not included any dependencies, so it'll only execute the code wrapped inside its block when the site will load for the first time.
useEffect(() => {
setTimeout(() => {
setShowMessage(false)
}, 3000)
}, [here should be some state which changes when a button got clicked])
Related
I have found this error while trying to build another React app. So I am only asking the main issue here in a demo app, I might not be able to change any rendering methods here since it is not the actual project.
Issue in simplified form -> I was building a app where two count will be shown and a + button will be there next to that count value. When the button is clicked the count should be increased by 1. Unfortunately when I try to click on the button the value is increasing only the first time. After that the value is not even changing. But when I am implementing the same using Class component its working as expected.
Functional Component
import React, { useState } from "react";
function Page(props) {
const [count, setCount] = useState(0);
const [content, setContent] = useState({
button: (value) => {
return <button onClick={() => handlePlus(value)}>+</button>;
},
});
function handlePlus(value) {
console.log("value=", value);
const data = count + 1;
setCount((count) => data);
}
return (
<div>
<span>Functional Component Count = {count}</span>
{content.button(10)} // 10 will be replaced with another variable
</div>
);
}
export default Page;
Class Component
import React, { Component } from "react";
class PageClass extends Component {
state = {
count: 0,
content: {
button: (value) => {
return (
<button onClick={() => this.handlePlus(value)}>+</button>
);
},
},
};
handlePlus = (value) => {
console.log("value=", value);
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<span>Class Component Count = {this.state.count}</span>
{this.state.content.button(10)} // 10 will be replaced with another variable
</div>
);
}
}
export default PageClass;
App.js
import "./App.css";
import Page from "./components/Page";
import PageClass from "./components/PageClass";
function App() {
return (
<div className="App">
<Page />
<PageClass />
</div>
);
}
export default App;
However, If I replace that content state variable with normal const variable type and it is working as expected.
Below is working when I am not using any hooks to render the button.
But this is not helpful for my case.
const content = {
content: () => {
console.log(count);
return <button onClick={() => handlePlus(value)}>+</button>;
},
};
I was trying to create some re-usable components and hence I wanted to have that function in state variable which return button tag, so that I can implements some other logic there.
The value will be missing since you're passing a hard-coded 10.
I'd recommend simplifying the handlePlus to just:
setCount(c => c + 1);
Then set the onclick like so:
<button onClick={handlePlus}>+</button>
And your code will work as expected as you can see in this snippet:
const { useState } = React;
const Example = () => {
const [count, setCount] = useState(0);
const [content, setContent] = useState({
content: (value) => {
return <button onClick={handlePlus}>+</button>;
},
});
function handlePlus(value) {
setCount(c => c + 1);
}
return (
<div>
<span>{count}</span>
{content.content(10)}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
That said, I'd recommend removing the button from the hook, and just render it yourself:
const { useState } = React;
const Example = () => {
const [count, setCount] = useState(0);
function handlePlus(value) {
setCount(c => c + 1);
}
return (
<div>
<span>{count}</span>
<button onClick={handlePlus}>+</button>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
See React documentation about the c => c + 1 syntax
Inside of render(), return(), I am trying to set a timeout but it's not working.
Am I doing something wrong?
{setTimeout(() => {
filtered.length && (
<FilterListContainer
containerHeight={this.state.filterContainerHeight}
>
<FilterListScroll>
<FilterList ref={this.filterListRef}>
{filtered.map((k) => (
<SidebarFilter
key={k}
type={k}
filter={this.props.body_search_filter[k]}
handleChange={this.handleFilterChange}
/>
))}
</FilterList>
</FilterListScroll>
</FilterListContainer>
);
}, 1)}
You've said you don't want that content to appear until "a bit later."
To do that, you'd want to have a state member saying whether to show the content, use that when rendering, and have the setTimeout that changes the state member's value.
For instance, here's an example using hooks:
const { useState, useEffect } = React;
const Example = () => {
const [showList, setShowList] = useState(false);
useEffect(() => {
const handle = setTimeout(() => {
setShowList(true);
}, 800); // Longer delay so you can see it
}, []);
return <div>
<div>Hi there</div>
{showList && <div>This is the list</div>}
</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.development.js"></script>
Suppose there's the following simple component. When I click on the button, the message will change to Clicked for 1 second and then goes back to -. However, when I spam the button, I want the title to be Clicked but it should go back to - after the last click of the button. Basically, I want each click to expand the timeout.
If this was a simple JS function, I would just clear the interval after each click and set another timeout. However, I'm not sure how to achieve the same result using react hooks.
import ReactDOM from 'react-dom';
import {useEffect, useState} from 'react';
import './index.css';
const Test = () => {
const [message, setMessage] = useState("-");
const buttonClick = () => {
setMessage("Clicked");
}
useEffect(() => {
if(message !== "-") {
const id = setTimeout(() => {
console.log("Running Interval");
setMessage("-");
}, 1000);
return () => {
console.log("Clearing Interval");
clearTimeout(id);
}
}
}, [message]);
return (
<article>
<header>
{message}
</header>
<button onClick={buttonClick}>button</button>
</article>
);
}
Put the timeout ID into a ref, and then you can call clearTimeout on it at the very beginning of the click handler.
const Test = () => {
const [message, setMessage] = React.useState("-");
const timeoutIdRef = React.useRef();
const handleClick = () => {
setMessage("Clicked");
clearTimeout(timeoutIdRef.current);
timeoutIdRef.current = setTimeout(() => {
setMessage("-");
}, 1000);
};
// cleanup, if desired
// React.useEffect(() => clearTimeout(timeoutIdRef.current), []);
return (
<article>
<header>
{message}
</header>
<button onClick={handleClick}>button</button>
</article>
);
}
ReactDOM.render(<Test />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
I'm making a Blog Web Page and the API I'm using to build it, it's already paginated so it returns 10 objects in every request I make.
But the client wants the page to have a "load more" button, in each time the user click on it, it will keep the already loaded data and load more 10 objects.
So far, I've made the button call more 10 new objects, everytime I clicked on it but I also need to keep the already loaded data.
This is my file so far:
MainPage.js
import React, { useState, useEffect} from 'react';
const MainPage = () => {
const [blogs, setBlogs] = useState('');
const [count, setCount] = useState(1);
useEffect(() => {
fetch("https://blog.apiki.com/wp-json/wp/v2/posts?_embed&categories=518&page="+count)
.then((response) => response.json())
.then((json) => {
console.log(json)
setBlogs(json)
})
}, [count])
const clickHandler = () => {
console.log(count);
return setCount( count+1)
}
return (
<div>
<p>All the recent posts</p>
{ blogs && blogs.map((blog) => {
return (
<div key={blog.id}>
<img width="100px" src={blog._embedded["wp:featuredmedia"][0].source_url}/>
<p>{blog.title["rendered"]}</p>
</div>
)
})
}
<button onClick={clickHandler}>LoadMore</button>
</div>
)
}
export default MainPage;
The idea is pretty simple. Just concatenate arrays using the Spread syntax as follows.
var first =[1, 2, 3];
var second = [2, 3, 4, 5];
var third = [...first, ...second];
So, do this thing when you're clicking the load more button.
Here I've come up with handling the whole thing:
Firstly, I will call a function inside the useEffect hook to load some blog posts initially. Secondly I've declared an extra state to show Loading and Load More text on the button.
Here is the full code snippet:
import React, { useState, useEffect } from "react";
const MainPage = () => {
const [loading, setLoading] = useState(false);
const [blogs, setBlogs] = useState([]);
const [count, setCount] = useState(1);
useEffect(() => {
const getBlogList = () => {
setLoading(true);
fetch(
"https://blog.apiki.com/wp-json/wp/v2/posts?_embed&categories=518&page=" +
count
)
.then((response) => response.json())
.then((json) => {
setBlogs([...blogs, ...json]);
setLoading(false);
});
};
getBlogList();
}, [count]);
return (
<div>
<p>All the recent posts</p>
{blogs &&
blogs.map((blog) => {
return (
<div key={blog.id}>
<img
width="100px"
src={blog._embedded["wp:featuredmedia"][0].source_url}
/>
<p>{blog.title["rendered"]}</p>
</div>
);
})}
{
<button onClick={() => setCount(count + 1)}>
{loading ? "Loading..." : "Load More"}
</button>
}
</div>
);
};
export default MainPage;
According to React documentation:
If the new state is computed using the previous state, you can pass a function to setState.
So you could append newly loaded blog posts to the existing ones in useEffect like this:
setBlogs((prevBlogs) => [...prevBlogs, ...json])
I would also set the initial state to an empty array rather than an empty string for consistency:
const [blogs, setBlogs] = useState([]);
This question already has answers here:
Which approach is good to update State of the Component in React
(2 answers)
Closed 3 years ago.
I was wondering what the difference is between both examples below.
In one example I use the previous state and in the other example I directly use the current value.
They both give me the same results.
In which cases should I use one way over the other?
Thanks in advance.
import React,{useState} from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div className="App">
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<br/>
<br/>
Count: {count2}
<button onClick={() => setCount2(0)}>Reset</button>
<button onClick={() => setCount2(count2 - 1)}>-</button>
<button onClick={() => setCount2(count2 + 1)}>+</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Because those calls to the state setter are in click handlers, your component is guaranteed to be re-rendered before another click is processed. For that reason, in most cases you don't have to use the callback version of the setter, you can directly use your existing state. (Even in concurrent mode.) (Note that if you handle the same click in more than one place [an element and a descendant of it, for instance], and you want both of those handlers to update the value, that's a different matter — see skyboyer's answer for an example of that.)
This is not true for all events (mousemove, for instance, does not have this guarantee), but it's true for click.
I got this information from Dan Abramov on twitter in this thread. At the time, events like click that had this guarantee were called "interactive" events. The name has since changed to "discrete" events. You can find a list in this source file in the React code.
Of course, not all state changes come directly from events. Suppose you have a click handler in your code that does a couple of ajax calls in series and, it happens, updates your value in response to completing each of them. The direct update version will be incorrect even if you've tried to be really thorough with useCallback; the callback version will be correct:
const {useState, useCallback} = React;
function ajaxGet() {
return new Promise(resolve => setTimeout(resolve, 10));
}
function Example() {
const [directValue, setDirectValue] = useState(0);
const [callbackValue, setCallbackValue] = useState(0);
const doThis = useCallback(() => {
setDirectValue(directValue + 1);
setCallbackValue(callbackValue => callbackValue + 1);
}, [directValue, callbackValue]);
const doThat = useCallback(() => {
setDirectValue(directValue + 1);
setCallbackValue(callbackValue => callbackValue + 1);
}, [directValue, callbackValue]);
const handleFirstFulfilled = useCallback(() => {
// ...
doThis();
// ...
return ajaxGet("something else");
}, [doThis]);
const handleSecondFulfilled = useCallback(() => {
// ...
doThat();
// ...
}, [doThat]);
const handleClick = useCallback(() => {
ajaxGet("something")
.then(handleFirstFulfilled)
.then(handleSecondFulfilled)
.catch(error => {
// ...handle/report error...
});
}, [handleFirstFulfilled, handleSecondFulfilled]);
const cls = directValue !== callbackValue ? "diff" : "";
return (
<div className={cls}>
<input type="button" onClick={handleClick} value="Click Me" />
<div>
Direct: {directValue}
</div>
<div>
Callback: {callbackValue}
</div>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
.diff {
color: #d00;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
(Disclaimer: That code may be utter rubbish. The point is to see the effect despite having tried to memoize everything. :-) )
For that reason, any time I'm setting a new value that's based on the previous value, I use the callback version unless it's a dedicated click handler or similar, in which case I may go direct.
Getting back to events, concurrent mode makes non-"discrete" events easier to stack up. In the current version of React on cdnjs.com (v16.10.2), I cannot get the following to have different numbers for directValue, callbackValue, and manualValue:
const {useState} = React;
// Obviously this is a hack that only works when `Example` is used only once on a page
let manualValue = 0;
const manualDisplay = document.getElementById("manualDisplay");
function Example() {
const [directValue, setDirectValue] = useState(0);
const [callbackValue, setCallbackValue] = useState(0);
const handleMouseMove = () => {
setDirectValue(directValue + 1);
setCallbackValue(callbackValue => callbackValue + 1);
manualDisplay.textContent = ++manualValue;
};
const different = directValue !== callbackValue || directValue !== manualValue;
document.body.className = different ? "diff" : "";
return (
<div onMouseMove={handleMouseMove}>
Move the mouse rapidly over this element.
<div>
Direct: {directValue}
</div>
<div>
Callback: {callbackValue}
</div>
</div>
);
}
const ex = <Example />;
if (ReactDOM.createRoot) {
document.body.insertAdjacentHTML("beforeend", "<div>Concurrent</div>");
ReactDOM.createRoot(document.getElementById("root")).render(ex);
} else {
ReactDOM.render(ex, document.getElementById("root"));
document.body.insertAdjacentHTML("beforeend", "<div>Legacy</div>");
}
.diff {
color: #d00;
}
<div id="root"></div>
<div>
Manual: <span id="manualDisplay">0</span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
Maybe that's just me not testing on enough platforms, but I can't get them to diverge in React's "legacy mode." But, using that same code with the experimental release with concurrent mode, it's fairly easy to get the directValue to lag behind the callbackValue and manualValue by waggling the mouse quickly over it, indicating that the event handler is running more than once between renders.
For your example there is no difference. But there are cases when that matters.
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(val + 1)}>
<span onClick={() => setVal(val + 1)}>{val}</span>
</div>);
will increment value only by 1 per click(0 -> 1 -> 2 -> 3). Live Example:
const {useState} = React;
function Example() {
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(val + 1)}>
<span onClick={() => setVal(val + 1)}>{val}</span>
</div>);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(oldVal => oldVal + 1)}>
<span onClick={() => setVal(oldVal => oldVal + 1)}>{val}</span>
</div>);
will increment value by 2 per click(0 -> 2 -> 4 -> 6). Live Example:
const {useState} = React;
function Example() {
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(oldVal => oldVal + 1)}>
<span onClick={() => setVal(oldVal => oldVal + 1)}>{val}</span>
</div>);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>