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.
Related
Hi guys in react i want to do something like render 2 times a component in same render function like this
const Main()=>{
const [names, setNames] = useState([]);
const [selected, setSelected] = useState(-1);
return(
<Component1 name={names[selected].name}
setName={setNames}/>
<Component1 name={names[selected].name}
setName={setNames}/>
)
}
This where i passed setNames from Main() to Component1 to handle state
and pass names from Main() then map it in select of Component1
<StyledSelect
ml="10px"
onChange={(e) => {
setName(e.target.value);
value={name}>
{names.map(({ name}) => {
return (
<option key={name} value={name}>
{name}
</option>
);
})}
my component has an html select that has names in it, when i choose one it trigger a it in database and retrieve name data (age, location), the problem is when i select name on my second Component1, it will also select the same name and retrieve same name data on my first Component1, and i want them to be independent. How ?
I tried to put key like that
return(
<Component1 key="compare" name={name}/>
<Component1 key="compared" name={name}/>
)
but nothing changes
Thanks
This question is worded incorrectly (this has nothing to do with state and everything to do with props and how React renders) and is missing information, but here are a few pieces of information that might help:
Keys don't change anything on the front-end; they are a tool React uses to improve performance on the back-end. Click here to read the docs for more information on keys and how React uses them.
React components are JavaScript functions and can therefore only return 1 thing. The code sample you shared would not work in React (or JavaScript). In order to return multiple items, it is possible to wrap them in an array or a React Fragment. Consider this example:
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
If you want to make the dropdowns different in appearance and/or functionality, the name prop will need to be different. Right now, both instances of Component1 are being given the same name property, which is why they probably look and behave identically.
(i would like can comment but i can't)
i am not sure (i must see the complete code) but i think that:
return(
<Component1 key="compare" name={name}/>
<Component1 key="compared" name={name}/>
)
must be
return(
<Component1 key="compare" name={name1}/>
<Component1 key="compared" name={name2}/>
)
this way they don't share the same variable
Where is it that you're making the call and setting the name? Since you're passing the same prop name to both of them they will be the same.
Either you set name inside Component1 itself or you make a different state for each Component1.
I understand that the special 'key' prop when used with child components that are created dynamically from arrays helps React to uniquely identify components and render updates efficiently. But I would like to know when and why would it be necessary to use the key prop for a 'non-dynamic' component.
My application uses a Reducer and useContext hook to manage state for a Functional Component A. The state object has a maximum 3 levels of nesting. Component A updates state and passes part of the state object as props to two instances of a child component B. B uses these props to render a switch component and 2 input components. Here's the simplified code for this hierarchy.
Component A:
const A: FC = () => {
// ....
// graphql queries to get data and update state using reducer
// ...
return (
<B
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
<B
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
);
};
Component B:
const B: FC = props => {
const { value1, value2, enabled} = props; // there are other props as well
return (
<>
<div className={someClassLogic}>
<Switch
onChange={onValueChange}
isChecked={enabled}
disabled={disabled}
/>
</div>
<div className={someClassLogic} >
<Input
input={value1}
disabled={disabled}
/>
</div>
<div className={someClassLogic}>
<Input
input={value2}
disabled={disabled}
/>
</div>
</>
);
};
A tablerow click event is used to update the state and the component B displays the 'settings' of this selected item which the user can mutate using the component B.
Here's the problem I'm facing- when the state is updated by a user action (selecting a row from a table, not shown in the snippet), I can see that both A and B receive the new data in the react developer tools and by printing to the console. But, a render does not take place to show the new data. I would like to understand why this is the case.
After looking up this issue, I figured I need a key prop while instantiating component B (the answers don't clearly explain why). With the following addition, the values did render correctly. Why is a key necessary here and why does it only work when the key contains all props that can change values? If I only use the uniqueId as the key, the value1 and value2 do not render correctly again. If I have many changing-props, do I have to them to add the key as well? Isn't there a less clumsy approach?
Updated Component A:
const A: FC = () => {
return (
<B
key={`${data.a.uniqueId}-
${data.a.b.value1}-
${data.a.b.value2}
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
<B
key={`${data.a.uniqueId}-
${data.a.b.value1}-
${data.a.b.value2}
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
);
};
Also, I noticed that although clicking on a table row rendered the correct value in component B now, but clicking on a row that is not changed by the user so far would cause the previously rendered values to remain on the Input1 and Input2 components (instead of blank). So I had to add keys to the Inputs as well with the enabled state attached to it which fixed this issue.
Updated Component B:
const B: FC = props => {
const { value1, value2, enabled} = props; // there are other props as well
return (
<>
<div className={someClassLogic}>
<Switch
onChange={onValueChange}
isChecked={enabled}
disabled={disabled}
/>
</div>
<div className={someClassLogic} >
<Input
key={`value1-${enabled}`}
input={value1}
disabled={disabled}
/>
</div>
<div className={someClassLogic}>
<Input
key={`value2-${enabled}`}
input={value2}
disabled={disabled}
/>
</div>
</>
);
};
Here again, why is a key needed? Doesn't react figure out that the props have changed and automatically render again?
I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.
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.
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.