React; Keep focus in input when state change - reactjs

I am writing an editor where I would like to be notified for any change into the
document. So I created a tree of components where each node report to the parent
via a onChange(..) callback:
// Pseudo structure.
Root {
const [changed, setChanged] = useState(true)
function onChange(..) {
setChanged(true);
}
return (
<App>
<Status saved={!changed} />
<Editor onChange={onChange}>
<Text onChange={onChange} />
<Image onChange={onChange} />
</Editor>
</App>
);
}
Now I have to create a <Table> component where each cell is an editable input
that trigger the onChange callback. Because of the useState in Root, each
time we change one letter in the table, the whole structure is rendered.
But the rendering cause a loose of the focus, which is admittedly annoying to
type a text. My current solution is to save the current cell via useMemo in my
new Table component and request the focus when the component is rendered.
However, I still loose the caret position and I have the feeling this is a hacky
way of working (That's why I am reaching you).
How can I notify a change and update the state of my Root without losing the
focus from one input ?
Thanks

My suggestion
Keep stateless components as child.
Use keys for each cell to avoid unnecessary rerendering from parent.

Related

Does react re render everything when we go to a new link?

I am currently creating a react app for practice. I am curious does react render everything when we go to a new link? For eg. These are my routers
<Route exact path="/" component={AuthenticatedUser(Books)}></Route>
<Route exact path="/librarians" component={AuthenticatedUser(Librarians)}></Route>
And my Higher Order Component AuthenticatedUser is as follows:
function AuthenticatedUser(Component) {
return function AuthenticatedComponent({ ...props }) {
const classes = useStyles();
return confirmUserAuthencition() ? (
<>
<SideMenu />
<Header />
<div className={classes.appMain}>
<PageHeader></PageHeader>
<Component {...props}></Component>
</div>
</>
) : (
<Redirect to="/login" />
);
};
}
I am just curious, when I go from "/" link to "/librarians", do components SideMenu and Header rerender?
React re-renders on state change.
From: https://www.educative.io/edpresso/how-to-force-a-react-component-to-re-render
React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.
These changes can come from setState, useState, and/or forceUpdate calls
It depends on the element that redirects you to the new link. If you use react router's <Link to="/librarians"> then no, React will not re-render. However, if you use the standard html <a href="/librarians"> then it will.
No, if you're moving from / to /librarians path, your <SideMenu /> and <Header /> won't re-render. React uses virtual DOM to do the updates on actual DOM (virtual DOM is a copy of the actual DOM and it can do the updates without affecting actual DOM)
During reconcilation process, react compares virtual and actual dom and then do the updates on actual dom based on the nodes that are changed.
In your case, since you're not completely removing AuthenticatedUser component when redirection, it won't re-render <SideMenu /> and <Header /> components that are included in AuthenticatedUser component as childs. But AuthenticatedUser re-render itself since you're changing the passed Component prop.
In order identify this properly you can put a console.log in <SideMenu /> and <Header /> to check whether re-render themselves when moving from / to /librarians.
Since your HOC's return statement depends on the value of the confirmUserAuthencition(), we can't always say whether or not the and components will get re-rendered.
The DOM will stay unaffected as long as the user remains authenticated or unauthenticated. The two components need not be re-rendered each time this route is hit in this case.
React won't re-render the entire page unnecessarily. It will only re-render all components except the SideMenu and Header component.
You may find this article helpful in understanding how react re-renders - Article
It will re-render any component that has changed, as determined by shouldComponentUpdate() for each component
In your case, if you're navigating to the new page via menu navigation, it will re-render the final component, as well as the nav-menu. Depending on your implementation, it's quite likely that the it will re-render the whole AuthenticatedUser component.
Here's the component lifecycle docs:
https://reactjs.org/docs/react-component.html#shouldcomponentupdate

How to force one react component to not re-render?

I have a main react component (The entire page) that contains several other UI items in it. One of those UI items is an OpenLayers map. Another component is a text field that contains the latitude and longitude of the current mouse pointer position.
I want the text field to update whenever the mouse pointer moves on the map. In my code's current state, this works.
The problem is every time the mouse moves and I update the react state value for the latitude/longitude (which gets rendered in the text field as expected) the map re-renders. This looks terrible because the map flashes every time as it begins to load the map tiles.
So I have the mouse latitude/longitude position, but I can't get the map to not flash/stutter/strobe.
Is there a way to get a mouse event from the map component, and send it to another component on the same screen without causing the render method to redraw the map every time? Or is React the wrong tool for this project?
As a note, I'm using React Redux to handle some state values, such as the map object. All of my child components are React functional components, but the main component for the page is a React class component.
If it helps, I set the mouse listener on the map like this:
this.props.map.on('pointermove', event => {
this.setState({
mouseCoordinates: (event.coordinate),
});
});
This is my render method:
render() {
return (
<div className="main">
<p className={noMargPad}>
{this.state.mouseCoordinates}
</p>
<Map
center={fromLonLat(this.state.center)}
zoom={this.state.zoom}
>
<Layers>
<TileLayer source={osm()} zIndex={0} />
{this.state.showLayer1 && (
<VectorLayer
source={vector({
features: new GeoJSON().readFeatures(geojsonObject, {
featureProjection: get("EPSG:3857"),
}),
})}
style={FeatureStyles.MultiPolygon}
/>
)}
{this.state.showMarker && (
<VectorLayer source={vector({
features: this.state.features
})} />
)}
</Layers>
<Controls>
<FullScreenControl />
</Controls>
</Map>
</div>
);
}
One of the main advantages of using a tool like React is it will only refresh the components in the render method that have had an update to their dependent data. See this article for more information.
The fact that your map is rendering again indicates that the map, or one of its children is being passed a piece of data that keeps changing.
Based on your code, one possibility is the call to OSM(), since that's a constructor from the OpenLayers API. A new object/class instance in JavaScript could cause data re-render.

Best practice to normal re-render a value of a state/prop that is inside a useEffect without adding it as a dependency?

I'm trying to find a best practice to render a list of components inside the dom without adding many dependencies in the useEffect that would cause a completely re-render of the whole list every time.
useEffect(() => {
const indices = props.indices;
if(Object.keys(indices).length !== 0){
const indicesToRender = Object.keys(indices).map((key: Extract<keyof IIndices, string>, index) => (
<div key={key} className="p-field-radiobutton radio-indice">
<RadioButton inputId={key} name="indice" value={indices[key]} onChange={(e) => props.onCheck(e.value)} checked={props.source === indices[key]} />
</div>
))
setIndicesDom(indicesToRender);
}
}, [props.indices, props.source]);
return(
<div className="indices">
{indicesDom}
</div>
)
In this example I'm trying to render a list of RadioButtons, and to me, as a dependency of the useEffect, there should be only the props.indices that is the object I need to wait to be filled from the parent to create my components. However, as a property of the component RadioButton, there is a 'checked' property where I use my props.source to determine if the button should be checked or not. That props.source gets changed from the parent every time I click on a RadioButton so I would expect that it causes a normal re-render from react to change only the value of that props.source inside the component on the DOM so that the button gets visibly checked. However it doesn't cause the re-render and the only way I found is to add as a dependency of the useEffect also the props.source so that the useEffect re-run everytime the props.source gets changed. This also causes a re-render of the whole list and that would be a waste of performance because I only need to re-render the value of that property props.source and that would happen correctly if I would write the component inside the return() this way
return(
<div className="indices">
<RadioButton name="indice" value={"indice1"} onChange={(e) => props.onCheck(e.value)} checked={props.source === "indice1"} />
</div>
)

Updating react state in parent very slow

I'm developing a react application, and where I always thought the react state updates were really fast, I now found a problem.
I have a page view with a lot of elements on it, one of the elements is this one that gets loaded in the page:
<NotesCard notes={deal.notes} updateNotes={notes => {setDeal(prevState => ({...prevState, notes}))}} />
NotesCard is a child component that only renders a material-ui Card with another react component in:
export default function NotesCard(props) {
const {notes, updateNotes} = props;
return (
<Card className="Card">
<CardHeader
title="Notities"
/>
<CardContent>
<Notes notes={notes} onChange={updateNotes} />
</CardContent>
</Card>
);
}
Notes is the last component that renders a text field and just takes the props to the TextField:
function Notes(props) {
const {notes} = props;
function updateNotes(event) {
// props.deal.notes = event.target.value;
props.onChange(event.target.value);
}
return (
<div>
<FormGroup>
{notes !== null ?
<TextField
multiline
defaultValue={notes}
onChange={e => updateNotes(e)}
rows={3}
variant={'outlined'}
label={'Notities'}
/>
: 'Geen beschrijving...'}
</FormGroup>
</div>
);
}
Is there anything that I do wrong that creates a lot of lag? The page is a big page so it might have something to do with that, but I'd think that the updates performances would still be okay.
Your goal is to fix the slow render, only then you will want to take a look at number of rerenders if necessary.
Please install the react-dev-tools which contains an option to mark components when the are being rerendered. Alternativly you can also monitor the performance over a couple of seconds and investigate the rendering. This should help you understand what renders unnecessarily on your actions.
I see a potential problem with this one:
<Notes notes={notes} onChange={updateNotes} />
If you trigger onChange the parent state is mutated. This then causes ALL children to rerender. I would think that only the single will change and a change in this component wont effect other siblings. So try to move the state as close to where its used as possible. If you trigger onChange only the should be updated. This is something easy which can fix a ton of performance problems without using react features like Memo.

React form + localstorage won't update values

Currently I am working on a form with the use of react hook forms and I want to save input from a user in the localstorage. At this moment whenever I change the value of the input when loaded from localstorage the input wont change.
When I console.log the event from the onchange method I don't get a log so it didn't trigger the onchange and thats where my problem is. I want that when a user wants to change anything in the form that it should update again.
I have searched on google but I can't find any related problem and so no solution. Since I am a beginner in react I have no clue to solve it myself.
The onchange function is in a functional component same as the input components is only functional.
This is the onchange function that contains the input event.
const onChange = event => {
localStorage.setItem(event.target.id, event.target.value);
};
This is the input compontent
<Input key={index} field={item} formFunction={register({required:true})} checkChange={handleChange} onChange={e => onChange(e)} value={localStorage.getItem(item.label) || ''} errors={errors} selectCheck={handleCountryChange} touched={touched} />
And this is the input compontents code
return (
<div className={props.field.grid}>
<div className={props.field.inputClass}>
<input
type={props.field.type}
name={props.field.name}
className={props.field.class}
id={props.field.label}
data-save="persist"
onBlur={props.checkChange}
style={{ marginBottom: '5px' }}
onChange={props.onChange}
value={props.value}
/>
<label className="left" htmlFor={props.field.label}>{props.field.label}</label>
</div>
</div>
);
Your problem is you are using the local storage to try and update the state of the app so the render function will not get re called and display the new inputted. ( unless you can show more code and you are indeed updating that this.props.value )?
I would suggest looking up local state within component for react, it will make things 10x easier in the future:
React state
Functional component state
You are best creating a local state in your constructor if it is an class component e.g., same can be achieved if it is a functional component just slightly different.
this.state = {
inputVariable: ""
}
then when ever your change this variable(in your onchange function using set state):
setstate({
inputVariable: valueToUpdate
})
your input components value field should be populated with this.state.inputVariable, so as you change the value it will trigger on change and then update the state which will cause a re render of your UI.
if you additionally also to save it to local storage you can do so like you already have.

Resources