How to fix infinite re-render? - javascript

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.

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;

ReactJS: setTimeout() not working inside return?

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>

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

React callback avoid re-render for child

I have trouble to avoid re-renders.
Have a look in my simplified example: https://codesandbox.io/s/gracious-satoshi-b3p1u?file=/src/App.js
Open the console. I have expensive and heavy code in the child hook which filters a big array (not in this lightweight example).
Every time "child has been rendered" get's printed into the console, my browser laggs and crashes if i do it too often.
There is no state in Child. I just use it to add a certain text piece (user can select from a big list of emojis and special chars) if i click to the textarea which needs a callback to communicate. I tried React.useMemo() and useMemo() but neither worked.
Try this. Add useMemo dependancies as needed,
export default function App() {
const [data, setData] = useState({ title: "default title", test: null });
const childs = useMemo(() => {
return (
<Child clickCallback={(x) => setData({ ...data, test: x })} />
)
}, []);
return (
<div>
<p>Title: {data.title}</p>
<p>Test: {data.test}</p>
<input onChange={(x) => setData({ ...data, title: x.target.value })} />
{childs}
</div>
);
}
You should use useCallback to memoize functions/event handlers and avoid creating them every time, and also for child components you should use memo to rerender only if props have changed
App.js
import React, { useCallback, useState } from "react";
import Child from "./Child";
export default function App() {
const [data, setData] = useState({ title: "default title", test: null });
const cb1 = useCallback(
(x) => setData({ ...data, title: x.target.value }),
[]
);
const cb2 = useCallback((x) => setData({ ...data, test: x }), []);
return (
<div>
<p>Title: {data.title}</p>
<p>Test: {data.test}</p>
<input onChange={cb1} />
<Child clickCallback={cb2} />
</div>
);
}
Child.js
import { memo } from "react";
export default memo(function Child(props) {
return (
<>
{console.log("child has been rendered")}
<button onClick={() => props.clickCallback("test")}>test</button>
<button onClick={() => props.clickCallback("test2")}>test2</button>
<button onClick={() => props.clickCallback("test3")}>test3</button>
<button onClick={() => props.clickCallback("test4")}>test4</button>
</>
);
});
check a working example here https://codesandbox.io/s/vibrant-curran-tv502

Usage of useCallback and setting new object state using previous state as argument

Consider this basic form fields component with a custom form hook to handle input changes:
import React, { useState, useCallback } from 'react';
const useFormInputs = (initialState = {})=> {
const [values, setValues] = useState(initialState);
const handleChange = useCallback(({ target: { name, value } }) => {
setValues(prev => ({ ...prev, [name]: value }));
}, []);
const resetFields = useCallback(() =>
setValues(initialState), [initialState]);
return [values, handleChange, resetFields];
};
const formFields = [
{ name: 'text', placeholder: 'Enter text...', type: 'text', text: 'Text' },
{ name: 'amount', placeholder: 'Enter Amount...', type: 'number',
text: 'Amount (negative - expense, positive - income)' }
];
export const AddTransaction = () => {
const [values, handleChange, resetFields] = useFormInputs({
text: '', amount: ''
});
return <>
<h3>Add new transaction</h3>
<form>
{formFields.map(({ text, name, ...attributes }) => {
const inputProps = { ...attributes, name };
return <div key={name} className="form-control">
<label htmlFor={name}>{text}</label>
<input {...inputProps} value={values[name]}
onChange={handleChange} />
</div>;
})}
<button className="btn">Add transaction</button>
</form>
<button className="btn" onClick={resetFields}>Reset fields</button>
</>;
};
Is there really any reason / advantage for me to use useCallback to cache the function in my custom hook? I read the docs, but I just coudln't grasp the idea behind this usage of useCallback. How exactly it memoizes the function between renders? How exactly does ti work, and should I use it?
Inside the same custom hook, you can see the new values state being updated by spreading the previous state and creating a new object like so: setValues(prev => ({ ...prev, [name]: value }));
Would there be any difference if I did this instead? setValues({ ...prev, [name]: value })
as far as I can tell, doesn't look like it has any difference right? I am simply accessing the state directly.. Am I wrong?
Your first question:
In your case it doesn't matter because everything is rendered in the same component. If you have a list of things that get an event handler then useCallback can save you some renders.
In the example below the first 2 items are rendered with an onClick that is re created every time App re renders. This will not only cause the Items to re render it will also cause virtual DOM compare to fail and React will re create the Itms in the DOM (expensive operation).
The last 2 items get an onClick that is created when App mounts and not re created when App re renders so they will never re render.
const { useState, useCallback, useRef, memo } = React;
const Item = memo(function Item({ onClick, id }) {
const rendered = useRef(0);
rendered.current++;
return (
<button _id={id} onClick={onClick}>
{id} : rendered {rendered.current} times
</button>
);
});
const App = () => {
const [message, setMessage] = useState('');
const onClick = (e) =>
setMessage(
'last clicked' + e.target.getAttribute('_id')
);
const memOnClick = useCallback(onClick, []);
return (
<div>
<h3>{message}</h3>
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={onClick} />
))}
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={memOnClick} />
))}
</div>
);
};
ReactDOM.render(<App />, 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>
Another example is when you want to call a function in an effect that also needs to be called outside of the effect so you can't put the function inside the effect. You only want to run the effect when a certain value changes so you can do something like this.
//fetchById is (re) created when ID changes
const fetchById = useCallback(
() => console.log('id is', ID),
[ID]
);
//effect is run when fetchById changes so basically
// when ID changes
useEffect(() => fetchById(), [fetchById]);
Your second question:
The setValues({ ...prev, [name]: value }) will give you an error because you never defined pref but if you meant: setValues({ ...values, [name]: value }) and wrap the handler in a useCallback then now your callback has a dependency on values and will be needlessly be re created whenever values change.
If you don't provide the dependency then the linter will warn you and you end up with a stale closure. Here is an example of the stale closure as counter.count will never go up because you never re create onClick after the first render thus the counter closure will always be {count:1}.
const { useState, useCallback, useRef } = React;
const App = () => {
const [counts, setCounts] = useState({ count: 1 });
const rendered = useRef(0);
rendered.current++;
const onClick = useCallback(
//this function is never re created so counts.count is always 1
// every time it'll do setCount(1+1) so after the first
// click this "stops working"
() => setCounts({ count: counts.count + 1 }),
[] //linter warns of missing dependency count
);
return (
<button onClick={onClick}>
count: {counts.count} rendered:{rendered.current}
</button>
);
};
ReactDOM.render(<App />, 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>

Categories

Resources