I am learning ReactJS. I would like to use functional component. In my component, I have a textbox and a clear button.
My concern is - whenever I type a char from my keyboard to enter info for the text box, I see the search account called!!! in my console output. If i type 5 chars, I would see this 5 times - it basically means the whole component is re-rendered & methods re-defined.rt?
Is it not bad practice and affect the performance? Is there any other alternative?
import React, { useState, useContext } from 'react';
import AccountContext from . './accountContext'
const SearchAccounts = () => {
const [text, setText] = useState('');
const onChange = (evt) => setText(evt.target.value);
console.log('search account called!!!');
// some methods are defined here
onSubmit = () => {...}
onClear = () => {...}
return (
<div>
<form onSubmit={onSubmit} className="form">
<input
type="text"
name="text"
value={text}
onChange={onChange}
placeholder="Search Accounts..."
/>
<input type="submit" value="Search" className="...." />
</form>
<button
className="...."
onClick={onClear}
style={getClearStyle()}
>Clear</button>
</div>
);
}
export default SearchAccounts;
Re-renders aren't necessarily expensive, and you have to accept that your components will re-render frequently in order propagate the changes in your data to the UI. Your example is very cheap, since the component is small and does not render any additional components in its return function - this is the ideal way to compose React components that have to re-render often.
You have to remember also that your JSX is not trashing and appending all HTML elements to the DOM every time the component re-renders. Only the difference between the last render and the current one is being applied, which is what allows React and other front end frameworks to create smooth and fast UIs when built at scale.
If and when you do reach a bottleneck in your components, you can use memoisation techniques (React.memo in a functional component context, shouldComponentUpdate in a class context) to prevent regularly rendering components from affecting the performance of their component children. It's usually best to do this towards the end of a project or unit of code, and only as a final measure since memoisation escapes built in React optimisation and can actually cause more problems than it solves if you use it inappropriately. A well structured component tree and flux-based state solution will alleviate most performance issues.
My concern is - whenever I type a char from my keyboard to enter info for the text box, I see the search account called!!! in my console output. If i type 5 chars, I would see this 5 times - it basically means the whole component is re-rendered & methods re-defined.rt?
Yes, this is expected and aligns with the mental model of React.
In React, you'll hear people say "the view is a function of state" — meaning that whatever data you inside of variables in react state should directly determine the rendered output, sometimes refered to as "one-way data flow"
The consequence of doing this however is that, in order to determine what changed, you need to render the react component.
This is fine!
Remember what powers React — the virtual dom. Internally, react creates a light-weight representation of the browser's DOM so that the above one-way data flow is efficient.
I see a separate concern here though regarding the amount of time you see the console.log output.
What you're doing there is a "side-effect" in React, and whenever you have a side-effect, you should wrap in in useEffect. useEffect will ensure your side-effect only runs when the values you feed to its dependency array changes.
const { useEffect, useState } = React;
const { render } = ReactDOM
function MyComponent() {
const [value, setValue] = useState('world');
useEffect(() => {
console.log('this should only render once');
}, []);
useEffect(() => {
console.log(`this will render on every value change: the value: ${value}`);
}, [value])
return (
<label>
hello, <input value={value} onChange={e => setValue(e.target.value)} />
</label>
);
}
render(<MyComponent />, document.querySelector('#root'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
See here for more on how useEffect works and here if you want a deep dive.
Related
Background
So I have a simple example, a Form component (Parent) and multiple Input components (Children). Each individual Input component will have an useState hook to initialize and manage its value's state.
Issue
As with most forms, I would like to submit all of the data to a backend for processing. However, the issue is that I cannot retrieve the state of value from each Child Input component.
// app.jsx
import Form from "./Form";
export default function App() {
return <Form />;
}
// Form.jsx
import React from "react";
import Input from "./Input";
const handleSubmit = (e) => {
e.preventDefault();
console.log("Wait, how do I retreive values from Children Inputs?");
};
const Form = () => {
console.log("Form render");
return (
<form onSubmit={handleSubmit}>
Sample Form
<Input initial="username" name="user" />
<Input initial="email" name="email" />
<button type="submit">Submit</button>
</form>
);
};
export default Form;
// Input.jsx
import React from "react";
import useInputValue from "./useInputValue";
const Input = ({ name, initial }) => {
const inputState = useInputValue(initial);
console.log(`${name}'s value: ${inputState.value}`);
return <input {...inputState} />;
};
export default Input;
Plausible Solution
Of course, I can lift the Input states up to the Form component, perhaps in an obj name values. However, if I do that, every time I change the Inputs, the Form will re-render, along with all of the Inputs.
To me, that is an undesirable side-effect. As my Form component gets bigger, this will be more costly to re-render all inputs (inside the form) every time one input changes.
Because of that, I would like to stick with my decision of having each individual input manage its own state, that way if one input changes, not all other input will re-render along with the Parent component.
Question
If each of the Child components manages its own state, could the Parent component access the value of that state and do actions (like form submission)?
Update
Many answers and comments mentioned that this is premature optimization and the root of all known evil; which I agree with, but to clarify, I am asking this question with a simple example because I wanted to find a viable solution to my current and more complex project. My web app has a huge form (where all states are lifted to the form level), which is getting re-rendered at every change in its inputs. Which seemed unnecessary, since only one input is changed at a time.
Update #2
Here is a codesandbox example of the issue I am having. There are two forms, one with all states managed by individual Child input components, and the other has all states lifted up in the form level. Try entering some values and check the console for log messages. One can see that with all states lifted to the form level, every change will cause both inputs to be re-rendered.
I think yes, you can share state. Also there are 3 options:
I recommend you to use such library as Formik. It will help you in your case.
You can share state using useState() hook as props.
Use such tools as Redux Toolkit (if we are speaking about memoisation), useContext() and etc.
If the thing you want is getting final values from input, assign ref to each input and access using emailRef.current.value in the submit function.
import { useState, useRef, forwardRef } from 'React';
const Input = forwardRef((props, ref) => {
const [value, setValue] = useState('');
return <input ref={ref} value={value} onChange={(e) => {setValue(e.target.value)}} {...props} />;
});
const App = () => {
const emailRef = useRef(null);
const submit = () => {
const emailString = emailRef.current.value
};
return (
<>
<Input ref={emailRef} />
<button onClick={submit}>Submit</button>
</>
);
};
If the parent needs to know about the childs state, you can
move the state up. This is resolved by passing down a setState function that the child calls (the states data is available in the parent)
use a context https://reactjs.org/docs/context.html
use a state management library e.g. redux
In your simple case I'd go with 1)
Why does my HomeTypes component unmount and remount every time I use setDrawerOpen?
Does a state change inside a component always cause that component to unmount and then re-mount?
import React, { useEffect, useState } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import Drawer from '../../components/DrawerComponent/Drawer';
function HomeTypes() {
const [drawerOpen, setDrawerOpen] = useState(null);
useEffect(() => {
console.log('Home Types Mounted');
return () => {
console.log('Home Types Unmounted');
};
});
return (
<div className={`dashboard-page`}>
<h1 className="dashboard-page-title">
Home Types
<button
className={`btn bright primary`}
onClick={() => {
setDrawerOpen(true);
}}>
<FontAwesomeIcon icon={['fas', 'plus']} />
<span>add home type</span>
</button>
</h1>
<Drawer
drawerOpen={drawerOpen}
closeDrawer={() => {
setDrawerOpen(false);
}}
title="Add Home Type"
drawerContent="Hello World"
/>
</div>
);
}
export default HomeTypes;
You're missing a dependencies array in your useEffect, that might be causing the behavior
useEffect(() => {
console.log('Home Types Mounted');
return () => {
console.log('Home Types Unmounted');
};
}. []); // this array right here
documentation see the Note in orange below: Using the Effect Hook
The hook useEffect will run every time the component renders.
to run the effect once on mount (end cleanup on dismount) run it with a second parameter [] in this form:
useEffect(() => {
// Side Effect
return () => {
// Cleanup
}
}, [])
Edit: Better to just quote the docs
Note
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders. Learn more about how to deal with functions and what to do when the array changes too often.
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often. Also, don’t forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.
We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.
Can't find any recent official info if any of the three options below is allowed?
constructor(props) {
this.state = {
item: <SomeItem />,
item1: () => <SomeItem />,
item2: SomeItem,
};
}
I found this answer but it references an old link from web archive which says:
What Shouldn’t Go in State?
...
React components: Build them in render()
based on underlying props and state.
But that link doesn't say why that is a bad idea, if it will introduce bugs, etc.
This is a really good question.
The reason that putting components in state is advised against is just that it goes fundamentally against the React model, which is that a component provides a render method (which is a pure function) that the React engine uses to automatically update the DOM to reflect the values of the component's props and state.
The output of that render, i.e. the React Element, is supposed to be used directly by the React engine. The contract is that your app, and all its components, generate a bunch of Elements in a pure way for the React engine to manage.
By doing things like introducing side effects in render, or putting the Elements in state, you're essentially breaking the 'pure' contract and it may give unpredictable results, which may or may not be considered bugs in your application. The specifics of the bugs may even change with different versions of React, with different engine implementations. The point is that you're breaking the React contract, so whilst it may work in some cases, it also may not in others or even the same cases as React itself changes. The behaviour is not guaranteed.
React has built-in ways to cache renders based on prop values, like React.memo, that the engine provides and understands, and are part of the contract. If you want to cache render output for performance reasons, this is the way to do it.
Indeed, this is exactly why such functions are provided by the React API rather than just letting you do it yourself.
At the end of the day, React component instances are just objects, and you can store objects in state, so it shouldn't cause any trouble if you avoid pitfalls. One such pitfall is that if you're creating handlers to put on their props, those handlers will close over the context in which they're created, which may lead to some unexpected outcomes. Here's an example of that:
const {useState, Fragment} = React;
function Thingy({onClick}) {
return <div onClick={onClick}>A</div>;
}
// STALE CLOSURE
function Example() {
const [value, setValue] = useState(0);
const [comp, setComp] = useState(
<Thingy onClick={() => { console.log("A: value = " + value); }} />
);
const handler = () => {
setValue(v => {
++v;
console.log("B: value = " + v);
return v;
});
};
return <Fragment>
{comp}
<div onClick={handler}>B</div>
</Fragment>;
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
This is the classic stale closure thing. It's probably a bit easier to do accidentally using functional components and hooks (as I did there) rather than class components, but it's definitely possible to do with class components as well.
But if you're not doing that (either not creating functions for the component you're storing, or creating ones that don't use anything they close over that may change), it should be fine.
But look at React.memo, which may be a better answer depending on what your reason for wanting to put component instances in state is.
You can do something like this, if I understand you right
const Title = () => {
return <h1>Hello CodeSandbox</h1>;
};
class App extends React.Component {
state = {}
constructor(props) {
super(props)
this.state = {
item: function() {
return <Title />;
}
};
}
render() {
return (
<div className="App">
{this.state.item()}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
export default App;
You can do it, but it's a bit strange. A React element is an object like any other. In this case, it will be the result of a method call:
// `<SomeItem/>` compiles to
React.createElement(SomeItem, null);
// A similar `React.createElement("div", null)` becomes
const el = {
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: "div",
_owner: null,
}
It's strange because it's unnecessary (and a little confusing). You can just generate the element (including any state or props updates) whenever you need it.
There's also a risk that you break one of the core guarantees of React: elements are immutable. Storing the element like this gives you a chance to mutate it and thus confuse React.
If you need many copies of the same element then it may be slightly more performant to keep it like this, especially if it is expensive to generate.
I have a large number of React Elements in an array. When I type in an associated text field, the React elements get filtered depending on whether the text within each one contains the typed text or not.
Typing in the text field becomes sluggish when filtering a large number of elements like this. Presumably this is because everything is happening on a single thread and the filtering logic is hogging the thread while it is executing and thus slowing down UI input.
I'm presuming there must be a way to solve this in an asynchronous way in Javascript so the UI doesn't get blocked by background computational logic ?
I've been trying a couple of async-await based approaches found online but nothing has worked yet.
I've created a cut down Code Sandbox of my issue here :
import React, { useState, useEffect } from "react";
import "./styles.css";
import { Button } from "#material-ui/core";
export default function MyFilterTextComponent(props) {
const [filterText, setFilterText] = useState("");
const [filteredElements, setFilteredElements] = useState(props.dataToFilter);
const { dataToFilter } = props;
useEffect(() => {
setFilteredElements(
dataToFilter.filter(currWord => {
return currWord.includes(filterText);
})
);
}, [dataToFilter, filterText]);
const handleFilterTextChange = event => {
setFilterText(event.target.value);
};
return (
<div className="App">
<label for="filterText">Filter text:</label>
<input
type="text"
id="filterText"
name="filterText"
onChange={handleFilterTextChange}
/>
<br />
<br />
{filteredElements.map(element => {
return <Button>{element}</Button>;
})}
</div>
);
}
Searching an array of words, checking if each word contains a search key is potentially an O(n^3) operation.
I tried throttling/debouncing the state update functions (i.e. only trigger setFilterText every 250ms and setFilteredElements every 500ms) but this doesn't really mitigate the O(n^3) search complexity, it only delays it. I did notice significant improvement when using a div/span (even a regular button) over the MUI Button though (even without throttling/debouncing). This implies a large overhead for the Material-UI Button component.
How can i optimize 'form' rendering. For each key pressed, the component is rendered
Any idea to solve or improve it?
const Example = () => {
const [inputForm, setInputForm] = useState('');
const inputHandler = event => {
setInputForm(event.target.value);
};
console.log('Rendering');
return (
<div>
<div>
<span>Text: {inputForm}</span>
<input value={inputForm} onChange={inputHandler} />
</div>
</div>
);
};
log of component rendering
Thanks guys!
Try to use Uncontrolled Components, from the documentation:
https://pt-br.reactjs.org/docs/uncontrolled-components.html
As #andergtk mentioned, if you want to avoid rendering the input on each key press you'll have to resort to an input that React does not control.
To write an uncontrolled component, instead of writing an event
handler for every state update, you can use a ref to get form values
from the DOM.
but the rendering on every key press that you notice is expected in the case of a controlled input where React has control over the value of the field and should not worry you
A controlled input is more "powerful" because you let React sync your data with your input value
more on this subject in the docs or in this article (there are lots of other resources on this): https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/
improving your case is not about stop rendering, every time input value is changed it needs to be re-rendered, if not changes wouldn't apply
to improve performance you may:
useCallback to the event handler if you gonna do calculations there
maybe split label out of the component returning only input element
useState may be declared outside Input component to make sense
Input props: { value, setValue } ,. then setup callback
react renders its components based on props or state changes, so always something changes on screen react has to re-render that component