ReactJS: setTimeout() not working inside return? - javascript

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>

Related

Passing components as state in React (Tab functionality)

Is it possible to pass other components through a state? I'm trying to make a tab function like a web browser, and if the user clicks the tab, a component shows up.
In my app.js I have -
const[chosenTab, setChosenTab] = useState("")
return (
<>
<Main chosenTab = {chosenTab}/>
</>
);
In Main.js -
const Main = ({chosenTab}) => {
return (
<>
{chosenTab}
</>
)
}
With the code below, the logic works to display the name of the tab/other component, but doesn't work if I replace {chosenTab} with <{chosenTab}/> to pass it as a component rather than just html.
I don't think this would work as you've structured it - I'd be welcome to have someone prove me wrong though since that would be a neat trick.
Now if I had to solve this problem, I'd simply use a object to hold what I need:
const tabMap = {
"string1": <component1 />,
"string2": <component2 />,
"string3": <component3 />
}
const Main = ({chosenTab}) => {
return (
<>
{tabMap[chosenTab]}
</>
)
}
Even further, let's say you wanted to pass in custom props, you could make tabMap a function to do that.
You can pass component reference itself as a tab.
const TabA = () => <div>Tab A</div>
const TabB = () => <div>Tab B</div>
const Main = ({ ChosenTab }) => {
retur <ChosenTab />
}
const App = () => {
const [chosenTab, setChosenTab] = useState(() => TabA);
const changeTab = (tab) => setChosenTab(() => tab);
return <Main ChosenTab={chosenTab} />
}
export default App;
Or you can store your tabs in object, Map or Array and set state accordingly
const tabs = {
A: TabA,
B: TabB
}
const App = () => {
const [chosenTab, setChosenTab] = useState(() => tabs.A);
const changeTab = (tabKey) => setChosenTab(() => tabs[tabKey]);
return <Main ChosenTab={chosenTab} />
}
export default App;

Set timeout for message with hooks

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])

How to render a component when state and props are changed?

I need to show the props value (which is a simple string). Each time I get new search results, I'm sending in the props. At the very first render the props will always be undefined.
Edit:
Header.jsx
function Header() {
const [searchString, setString] = useState('');
const onChangHandler = (e) => {
setString(e.target.value);
};
const activeSearch = () => {
if (searchString.length > 0) {
<Home searchResults={searchString} />;
}
};
return (
<div>
<input
placeholder='Search here'
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
</header>
</div>
);
}
I searched for previous stackoverflow questions and reactjs.org but found no answer.
Home.jsx
import React, { useEffect, useState } from 'react';
function Home({ searchResults }) {
const [itemSearchResults, setResults] = useState([]);
const [previousValue, setPreviousValue] = useState();
// What function will re-render when the props are first defined or changed ?
useEffect(() => { // Doesn't work
setResults(searchResults);
}, [searchResults]);
return (
<div>
<h3>Home</h3>
<h1>{itemSearchResults}</h1>
</div>
);
}
export default Home;
App.js
function App() {
return (
<div className='App'>
<Header />
<Home />
<Footer />
</div>
);
}
I'm sending the input string only to check if the props will change at the child component ("Home").
Any experts here know what's the problem?
Why it doesn't work?
It's because the Home component is never used, even if it's included in the following snippet:
const activeSearch = () => {
if (searchString.length > 0) {
<Home searchResults={searchString} />;
}
};
The activeSearch function has a couple problems:
it is used as an event handler though it uses JSX (outside the render phase)
it doesn't return the JSX (would still fail inside the render phase)
JSX should only be used within the render phase of React's lifecycle. Any event handler exists outside this phase, so any JSX it might use won't end up in the final tree.
The data dictates what to render
That said, the solution is to use the state in order to know what to render during the render phase.
function Header() {
const [searchString, setString] = useState('');
const [showResults, setShowResults] = useState(false);
const onChangHandler = (e) => {
// to avoid fetching results for every character change.
setShowResults(false);
setString(e.target.value);
};
const activeSearch = () => setShowResults(searchString.length > 0);
return (
<div>
<input
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
{showResults && <Home query={searchString} />}
</div>
);
}
useEffect to trigger effects based on changing props
And then, the Home component can trigger a new search request to some service through useEffect.
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let discardResult = false;
fetchResults(query).then((response) => !discardResult && setResults(response));
// This returned function will run before the query changes and on unmount.
return () => {
// Prevents a race-condition where the results from a previous slow
// request could override the loading state or the latest results from
// a faster request.
discardResult = true;
// Reset the results state whenever the query changes.
setResults(null);
}
}, [query]);
return results ? (
<ul>{results.map((result) => <li>{result}</li>))}</ul>
) : `Loading...`;
}
It's true that it's not optimal to sync some state with props through useEffect like the article highlights:
useEffect(() => {
setInternalState(externalState);
}, [externalState]);
...but in our case, we're not syncing state, we're literally triggering an effect (fetching results), the very reason why useEffect even exists.
const { useState, useEffect } = React;
const FAKE_DELAY = 5; // seconds
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let queryChanged = false;
console.log('Fetch search results for', query);
setTimeout(() => {
if (queryChanged) {
console.log('Query changed since last fetch, results discarded for', query);
return;
}
setResults(['example', 'result', 'for', query])
}, FAKE_DELAY * 1000);
return () => {
// Prevent race-condition
queryChanged = true;
setResults(null);
};
}, [query]);
return (
<div>
{results ? (
<ul>
{results.map((result) => (
<li>{result}</li>
))}
</ul>
) : `Loading... (${FAKE_DELAY} seconds)`}
</div>
);
}
function Header() {
const [searchString, setString] = useState('');
const [showResults, setShowResults] = useState(false);
const onChangHandler = (e) => {
// to avoid fetching results for every character change.
setShowResults(false);
setString(e.target.value);
};
const activeSearch = () => setShowResults(searchString.length > 0);
return (
<div>
<input
placeholder='Search here'
value={searchString}
onChange={(e) => onChangHandler(e)}
/>
<button onClick={activeSearch}>Search</button>
{showResults && <Home query={searchString} />}
</div>
);
}
ReactDOM.render(<Header />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Better solution: Uncontrolled inputs
Another technique in your case would be to use an uncontrolled <input> by using a ref and only updating the search string on click of the button instead of on change of the input value.
function Header() {
const [searchString, setString] = useState('');
const inputRef = useRef();
const activeSearch = () => {
setString(inputRef.current.value);
}
return (
<div>
<input ref={inputRef} />
<button onClick={activeSearch}>Search</button>
{searchString.length > 0 && <Home query={searchString} />}
</div>
);
}
const { useState, useEffect, useRef } = React;
const FAKE_DELAY = 5; // seconds
function Home({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
let queryChanged = false;
console.log('Fetch search results for', query);
setTimeout(() => {
if (queryChanged) {
console.log('Query changed since last fetch, results discarded for', query);
return;
}
setResults(['example', 'result', 'for', query])
}, FAKE_DELAY * 1000);
return () => {
// Prevent race-condition
queryChanged = true;
setResults(null);
};
}, [query]);
return (
<div>
{results ? (
<ul>
{results.map((result) => (
<li>{result}</li>
))}
</ul>
) : `Loading... (${FAKE_DELAY} seconds)`}
</div>
);
}
function Header() {
const [searchString, setString] = useState('');
const inputRef = useRef();
const activeSearch = () => {
setString(inputRef.current.value);
}
return (
<div>
<input
placeholder='Search here'
ref={inputRef}
/>
<button onClick={activeSearch}>Search</button>
{searchString.length > 0 && <Home query={searchString} />}
</div>
);
}
ReactDOM.render(<Header />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Passing the state around
[The following line] brings the Home component inside the Header component, which makes duplicate
{searchString.length > 0 && <Home query={searchString} />}
In order to make the Header component reusable, the quickest way would be to lift the state up.
// No state needed in this component, we now receive
// a callback function instead.
function Header({ onSubmit }) {
const inputRef = useRef();
const activeSearch = () => {
// Uses the callback function instead of a state setter.
onSubmit(inputRef.current.value);
}
return (
<div>
<input ref={inputRef} />
<button onClick={activeSearch}>Search</button>
</div>
);
}
function App() {
// State lifted up to the parent (App) component.
const [searchString, setString] = useState('');
return (
<div className='App'>
<Header onSubmit={setString} />
{searchString.length > 0 && <Home query={searchString} />}
<Footer />
</div>
);
}
If that solution is still too limited, there are other ways to pass data around which would be off-topic to bring them all up in this answer, so I'll link some more information instead:
Thinking in React
What's the right way to pass form element state to sibling/parent elements?
Passing data to sibling components with react hooks?
Application State Management with React
How can I update the parent's state in React?
Top 5 React state management libraries in late 2020 (Redux, Mobx, Recoil, Akita, Hookstate)
if your props are passed as searchResults, then change the props to,
function Home({ searchResults}) {...}
and use
useEffect(() => { // code, function },[searchResults]) ).

why is React button not calling function on click

when I changed the disabled of button from true to false, it messed my onClick, why is that, and how to solve it?
const Component = () => {
const refrence = React.useRef(null)
setTimeout(() => {
refrence.current.disabled = false
}, 1000);
const handleClick = () => console.log('clicked')
return (
<div>
<button onClick={handleClick} ref={refrence} disabled>click me</button>
</div>
)
}
ReactDOM.render(
<Component />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script><div id="root"></div>
Issue
The issue here is that the React ref is mutating the DOM, but React isn't made aware of this and so Component isn't rerendered with a working button allowing the onClick event to go through. This is the main reason why direct DOM manipulations are considered anti-pattern in React.
Additionally, you are setting a timeout to occur every render, though this would have minimal effect it's still unnecessary and would be considered an unintentional side-effect.
Solution
Use component state and update state in a mounting useEffect hook to trigger a rerender.
const Component = () => {
const [isDisabled, setIsDisabled] = useState(true);
useEffect(() => {
setTimeout(() => {
setIsDisabled(false);
}, 1000);
}, []);
const handleClick = () => console.log('clicked');
return (
<div>
<button onClick={handleClick} disabled={isDisabled}>click me</button>
</div>
)
}
const Component = () => {
const [isDisabled, setIsDisabled] = React.useState(true);
React.useEffect(() => {
setTimeout(() => {
setIsDisabled(false);
}, 1000);
}, []);
const handleClick = () => console.log('clicked');
return (
<div>
<button onClick={handleClick} disabled={isDisabled}>click me</button>
</div>
)
};
ReactDOM.render(
<Component />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to fix infinite re-render?

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Found the answers to my question, but, nevertheless, I can’t understand how to fix this?
const { useState } = React,
{ render } = ReactDOM
function App() {
const [visible, setVisible] = useState(false);
const [visible2, setVisible2] = useState(false);
const arr1 = {
company: [
{
id: "random1",
companyName: "Apple"
},
{
id: "random2",
companyName: "Samsung"
}
]
};
const onDataHandle = () => {
return arr1.company.map(items => {
return (
<div>
<span key={items.id}>
{items.companyName}
<span onClick={onHandleVisible}>Details</span>
</span>
<br />
</div>
);
});
};
const onHandleVisible = () => {
setVisible(!visible);
};
const onHandleVisible2 = () => {
setVisible2(!visible2);
};
return (
<div className="App">
<button onClick={onHandleVisible2}>Show</button>
{visible && onDataHandle()}
</div>
);
}
render (
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
I understand that this is due to the endless re-rendering, but what are the solutions?
You have multiple problems in your code logic (that is not entirely clear, I must say):
Show button onClick triggeres onHandleVisible2 callback which sets visible2 state variable to true, but there's nothing in your code depending on that state variable, so nothing happens
The block {visible && onDataHandle()} is supposed to trigger onDataHandle() (which never happens for above reason - visible stays equal to false), but onDataHandle() (even though attempts to return some JSX) will not add anything to render within <App /> as it is not a ReactJS component
(the minor one) if your intention with onDatahandle() was to return some component, wrapping up your span with extra <div> doesn't make much sense.
With all the above issues fixed, you would get something, like:
const { useState } = React,
{ render } = ReactDOM
function App() {
const [visible, setVisible] = useState(false);
const [visible2, setVisible2] = useState(false);
const arr1 = {
company: [
{
id: "random1",
companyName: "Apple"
},
{
id: "random2",
companyName: "Samsung"
}
]
};
const Data = () => (
<div>
{
arr1.company.map(items => (
<span key={items.id}>
{items.companyName}
<span onClick={onHandleVisible}>Details</span>
{visible && <span>There go {items.companyName} details</span>}
<br />
</span>))
}
</div>
)
const onHandleVisible = () => {
setVisible(!visible);
};
const onHandleVisible2 = () => {
setVisible2(!visible2);
};
return (
<div className="App">
<button onClick={onHandleVisible2}>Show</button>
{visible2 && <Data />}
</div>
);
}
render (
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Note one important issue stays unsolved in above code block: you used single variable (visible) for your entire app, so if you decide to control visibility of details for each item independently, you won't be able to do that with your current approach. But that's a totally different issue which you may post as a separate question.

Categories

Resources