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
Related
**I'm getting react Error while trying to change parent component styles onMouseEnter event. How could I change the styles via child component button?
The error is - Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
That seems odd for onMouseLeave={() => setHovered(false)} is an arrow function.
https://codesandbox.io/s/trusting-moon-djocul?
**
// App js
import React, { useState, useEffect } from "react";
import Categories from "./Categories";
import ShopPage from "./components/Products";
export default function App() {
const [data, setData] = useState(Categories);
useEffect(() => {
setData(data);
}, []);
return (
<div className="wrapper">
<ShopPage products={data} filterResult={filterResult} />
</div>
);
}
// Shopping page
const ShopPage = ({ products }) => {
return (
<>
<div>
<Products products={products} />
</div>
</>
);
};
// Parent component, the main part goes here
const Products = ({products}) => {
const [hovered, setHovered] = useState(false);
const [style, setStyle] = useState(false);
if (hovered) {
setStyle({
// inline styles
});
} else {
setStyle({
// inline styles
});
}
return (
<>
<Product setHovered={setHovered} style={style} products={products}/>
</>
);
};
export default Products;
// Child component
const Product = ({ setHovered, style, products }) => {
return (
<div className={styles.items}>
{products.map((value) => {
return (
<>
<div style={style}>
<button
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
Add to Cart
</button>
</div>
</>
);
})}
</div>
);
};
export default Product;
The issue is you are setting setHovered state in component, the simple solution could be to use it in useEffect and add required dependency.
If we talk about your code so you can easily do this by using the state in child component instead of passing through props.
I have updated your code below:
https://codesandbox.io/s/nameless-cookies-e6pwxn?file=/src/components/Product.js:141-151
I want to filter data and implement in search bar. In Hook/index.js component I am fetching and filtering data inside useEffects. Then I am passing props in App.js. Afterwards I have a Searchbar component, where I am listening to the input and here it must work. I get undefined.
Hook/index.js component
import React, { useState, useEffect } from "react";
import "./hook.scss";
export default () => {
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [search, setSearch] = useState("");
const fetchData = () => {
fetch("https://restcountries.eu/rest/v2/all")
.then((res) => res.json())
.then((result) => setData(result))
.catch((err) => console.log("error"));
};
useEffect(() => {
const searchResult =
data && data.filter((item) => item.name.toLowerCase().includes(search));
setSearch(searchResult);
}, []);
useEffect(() => {
fetchData();
}, []);
return [data, error];
};
App.js
import React, { useState }from "react";
import Header from "./components/Header";
import SearchBar from "./components/SearchBar";
import Flag from "./components/Flag";
import useCountries from "./Hooks";
import CountryList from "./components/CountryList";
import "./App.scss";
export default function App() {
const [data, error] = useCountries();
return (
<div className="App">
<SearchBar /> // {/*this throws an error <SearchBar data={data}/> */}
<Header />
{data &&
data.map((country) => (
<div className="CountryList" key={country.name}>
<Flag flag={country.flag} />
<CountryList
population={country.population}
name={country.name}
region={country.region}
/>
{country.languages.map((language, languageIndex) => (
<CountryList key={languageIndex} language={language.name} />
))}
</div>
))}
<useCountries />
</div>
);
return [data, error]
}
Searchbar component
import React, {useState} from "react";
import "./SearchBar.scss";
export default function SearchBar({data}) {
const [search, setSearch] = useState("");
function handleChange(e) {
setSearch(e.target.value);
}
return (
<div className="SearchBar">
<input
className="input"
type="text"
placeholder="search country ..."
value={data}
onChange={handleChange}
/>
{data && data.filter((item) => item.name.toLowerCase().includes(search))}
</div>
);
};
You are sending data variable to input instead of search variable.
In JS filter return array and DOM cannot display array since it is not html or jsx so you need to convert array to jsx with map. with map you can return array or jsx
<div className="SearchBar">
<input
className="input"
type="text"
placeholder="search country ..."
value={search} // change here
onChange={handleChange}
/>
<ul>{(data || []).filter((item) => item.name.toLowerCase().includes(search)).map(e=>(<li key={e.name}>{e.name}</li>))}</ul> /change here
</div>
Your new .filter() Array contains Objects inside it! You need to .map() it before return as Objects are not valid as a React child.
{ data?.filter((item) => item.name.toLowerCase().includes(search)).map((element =>
<>
/* Your code goes here! */
</>) }
Explanation:
Array.prototype.filter() returns a new Array and in your case your Array is filled with Objects, like this:
{data && data.filter((item) => item.name.toLowerCase().includes(search))}
// The code above returns an Array just like below.
const array = [ {name: 'Brazil' /*...others properties*/}, {name: 'USA' /*...others properties*/}, {name: 'England' /*...others properties*/} ];
When you return array, React reject to mount your Objects, cus it can't understand what to do. That's why you map it, to have access to each Object inside it.
import React, { useState } from "react";
import Child from "./Child";
import "./styles.css";
export default function App() {
let [state, setState] = useState({
value: ""
});
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
import React from "react";
function Child(props) {
return (
<input
type="text"
placeholder="type..."
onChange={e => {
let newValue = e.target.value;
props.handleChange(newValue);
}}
value={props.value}
/>
);
}
export default Child;
Here I am passing the data from the input field to the parent component. However, while displaying it on the page with the h1 tag, I am able to see the latest state. But while using console.log() the output is the previous state. How do I solve this in the functional React component?
React state updates are asynchronous, i.e. queued up for the next render, so the log is displaying the state value from the current render cycle. You can use an effect to log the value when it updates. This way you log the same state.value as is being rendered, in the same render cycle.
export default function App() {
const [state, setState] = useState({
value: ""
});
useEffect(() => {
console.log(state.value);
}, [state.value]);
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
Two solution for you:
- use input value in the handleChange function
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
use a useEffect on the state
useEffect(()=>{
console.log(state.value)
},[state])
Maybe it is helpful for others I found this way...
I want all updated projects in my state as soon as I added them
so that I use use effect hook like this.
useEffect(() => {
[temp_variable] = projects //projects get from useSelector
let newFormValues = {...data}; //data from useState
newFormValues.Projects = pro; //update my data object
setData(newFormValues); //set data using useState
},[projects])
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>
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.