I would like to prevent component re-rendering using React. I've read some guides but I'm still having trouble getting my code to work.
The CreateItem component creates an input form from the json object. When the input states change, React re-renders all components. I would avoid this situation as it causes some problems.
I have used React.memo but my code still doesn't work. Is this a good way to implement this code? How can I correct my code? Thank you
function MyComponent() {
return(
<div className="row">
{Array.from(new Map(Object.entries(json))).map((data) => (
<CreateItem obj={data} />
))}
</div>
);
}
//function CreateDiv(props) {
const CreateDiv = React.memo((props) => {
console.log("rendering ");
return (
<form name="myForm" onSubmit= {formSubmit}>
<div className="row">
{Array.from(new Map(Object.entries(props.obj[1]))).map((data) => (
<>
{(() => {
return(
<div className="col-sm-2">
<CreateItem obj={data[1]} />
</div>
)
})()}
</>
))}
</div>
</form>
);
});
--- EDIT ---
CreateItem uses CreateCheckBoxComponent function to create my custom checkbox with default status from json value.
CreateCheckBoxComponent code is follwing:
function CreateCheckBoxComponent(props) {
if(parseInt(props.obj.defaultValue) === 5)
setChecked(false);
else
setChecked(true);
return(
<FormCheck
label={props.obj.simbolName}
name={props.obj.idVar}
type="checkbox"
checked={checked}
onChange={handleCheckBoxChange}
sm={10}
/>
);
}
HandleCheckBoxChange works fine and changes state, but when I click on checkbox to change the flag, CreateCheckBoxComponent is re-render and
it sets the default state again. I would like to avoid this problem and I think preventing re-rendering can be a solution..
React.memo only prevents own rerendering.
You have considered the following things.
If the children are using React.memo but the parent re-renders
the children will render also.
React.memo prevents re-rendering if the component's state changes. but if the prop changes, the component re-renders.
Note: make sure when you render elements/Components with the map function or any iteration always provide a unique key to them.
For more information click here
Related
I have a react page that has several components on it with a state that shows a modal. I dont want all the components in the app to re render when the modal shows.
const CustomersPage = () => {
const [showModal, setShowModal] = useState(false);
const dataSource = [...omitted data]
return (
<>
<Modal
visible={showModal} />
<div>
<div>
<div>
<Button type="primary" onClick={() => setShowModal(true)}>
Create
</Button>
</div>
<CustomForm />
</div>
<CustomList dataSource={dataSource} />
</div>
</>
);
};
When the showModal value changes, the components CustomForm component and the CustomList component re renders but I dont want them to re render every time the modal shows because the list can have over 100 components. How can I do this?
Edit.
const CustomList = ({ dataSource }) => {
return (
<div>
{dataSource?.map(i => (
<CustomCard
id={i.id}
...other props
/>
))}
</div>
);
};
const CustomCard = ({
... props
}) => {
return (
<>
<Card
...omitted properties
</Card>
</>
);
};
You can wrap the List and Form components in a React.memo component.
This way they will only re-render when their props change their identity.
See:
https://scotch.io/tutorials/react-166-reactmemo-for-functional-components-rendering-control
You can avoid unnecessary re-rendering with memo and hooks like useMemo and useCallback if you are using FC. Or if your are in CC the your create your component pure component that prevent unnecessary render.
Make your function component memo by wrapping component with Reaact.memo, then this will help to check and render component if there is any changes down there in your this child component. Despite all hooks like useCallback and useMemo are also helpfull for optimization.
There are tons of the articles are there about the use cases of these hooks, go and have look at them. They are really helpful.
Thanks
Hi I have some sort of the following code:
class First extends Component {
constructor(props){super(props)}
myfunction = () => { this.card //do stuff}
render() {
return(
<Component ref={ref => (this.card = ref)} />
)}
}
Why is it not possible for me to access the card in myfunction. Its telling me that it is undefined. I tried it with setting a this.card = React.createRef(); in the constructor but that didn't work either.
You are almost there, it is very likely that your child Component is not using a forwardRef, hence the error (from the React docs). ref (in a similar manner to key) is not directly accesible by default:
const MyComponent = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// ☝️ now you can do <MyComponent ref={this.card} />
ref is, in the end, a DOMNode and should be treated as such, it can only reference an HTML node that will be rendered. You will see it as innerRef in some older libraries, which also works without the need for forwardRef in case it confuses you:
const MyComponent = ({ innerRef, children }) => (
<button ref={innerRef}>
{children}
</button>
));
// ☝️ now you can do <MyComponent innerRef={this.card} />
Lastly, if it's a component created by you, you will need to make sure you are passing the ref through forwardRef (or the innerRef) equivalent. If you are using a third-party component, you can test if it uses either ref or innerRef. If it doesn't, wrapping it around a div, although not ideal, may suffice (but it will not always work):
render() {
return (
<div ref={this.card}>
<MyComponent />
</div>
);
}
Now, a bit of explanation on refs and the lifecycle methods, which may help you understand the context better.
Render does not guarantee that refs have been set:
This is kind of a chicken-and-egg problem: you want the component to do something with the ref that points to a node, but React hasn't created the node itself. So what can we do?
There are two options:
1) If you need to pass the ref to render something else, check first if it's valid:
render() {
return (
<>
<MyComponent ref={this.card} />
{ this.card.current && <OtherComponent target={this.card.current} />
</>
);
}
2) If you are looking to do some sort of side-effect, componentDidMount will guarantee that the ref is set:
componentDidMount() {
if (this.card.current) {
console.log(this.card.current.classList);
}
}
Hope this makes it more clear!
Try this <Component ref={this.card} />
I'm using gatsby and have a functional component that loops through some data to create radio button group with an onchange event and checked item. When i update the state whole page component rerenders. i though adding memo was meant to stop this but it doesn't seem to work.
here is the code
const BikePage = React.memo(({ data }) => {
console.log("page data", data)
const [selectedColor, setColor] = useState(data.bike.color[0])
const onColorChange = e => {
setColor(e.target.value)
}
return (
<div>
{data.treatment.price.map((value, index) => {
return (
<div>
<input
id={`bike-option-${index}`}
name="treatment"
type="radio"
value={value}
checked={selectedColor === value}
onChange={e => onColorChange(e)}
/>
<label
htmlFor={`treatment-option-${index}`}
>
{value}
</label>
</div>
)
})}
<Link
to="/book"
state={{
bike: `${data.bike.title}-${selectedColor}`,
}}
className="c-btn"
>
Book Now
</Link>
</div>
)
});
If you update the state the component will re-render, that's fundamentally how react works. the memoised data prop is coming from outside of the component.
"If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result" react.memo
You're not changing the incoming props though, you're changing the state
Side note: i imagine that on changing this value you probably want to be changing the state of the data on the server through some means also ( REST POST / graphql mutation). Subsequent refetches of this data would re-render this component as well. It depends what you're trying to ultimately achieve.
Is it safe to use React.forwardRef method directly inside render function of another component -
Example -
function Link() {
// --- SOME EXTENSIVE LOGIC AND PROPS CREATING GOES HERE ---
// --- OMITTED FOR SIMPLICITY ---
// TO DO: Remove forward ref as soon Next.js bug will be fixed -
// https://github.com/zeit/next.js/issues/7915
// Please note that Next.js Link component uses ref only to prefetch link
// based on its availability in view via IntersectionObserver API -
// https://github.com/zeit/next.js/blob/canary/packages/next/client/link.tsx#L119
const TempShallow = React.forwardRef(props =>
cloneElement(child, {
...props,
...baseProps,
onClick: handleClick
})
);
return (
<NextLink href={href} as={as} prefetch={prefetch} passHref {...otherProps}>
<TempShallow />
</NextLink>
);
}
As you see it's a temporary workaround for a bug in Next.js v9 - https://github.com/zeit/next.js/issues/7915.
Beware forwardRef affects reconciliation: element is always re-created on parent re-rendering.
Say
function App() {
const [,setState] = useState(null);
const Input = React.forwardRef((props, ref) => <input {...props} />)
return (
<div className="App">
<h1>Input something into inputs and then click button causing re-rendering</h1>
<Input placeholder="forwardRef" />
<input placeholder="native" />
<button onClick={setState}>change state to re-render</button>
</div>
);
}
You may see that after clicking button forwardRef-ed input is dropped and re-created so it's value becomes empty.
Not sure if this could be important for <Link> but in general it means things you'd expect to run only once per life time(say fetching data in componentDidMount or useEffect(...,[]) as alternative) will happen much more frequently.
So if choosing between this side effect and mocking warning I'd rather ignore Warning. Or create own <Link > that will not cause warnings.
[UPD] missed one thing: React checks forwardRef by reference in this case. So if you make forwardRef out of the render(so it's referentially the same) it will not be recreated:
const Input = React.forwardRef((props, ref) => <input {...props} />)
function App() {
const [,setState] = useState(null);
return (
<div className="App">
<h1>Input something into inputs and then click button causing re-rendering</h1>
<Input placeholder="forwardRef" />
<input placeholder="native" />
<button onClick={setState}>change state to re-render</button>
</div>
);
}
But still I believe it's safer to ignore warning than to introduce such a workaround.
Code above has worse readability to me and is confusing("why ref is not processed at all? was it intentional? why this forwardRef is here and not in component's file?")
I concurr with skyboyer, I'll add that it might be possible to create the forwardRef component outside of the render function to avoid re-creating the component each render. To be checked.
const TempShallow = React.forwardRef(({ child, ...props }) => React.cloneElement(child, props))
function Link() {
// --- SOME EXTENSIVE LOGIC AND PROPS CREATING GOES HERE ---
// --- OMITTED FOR SIMPLICITY ---
// TO DO: Remove forward ref as soon Next.js bug will be fixed -
// https://github.com/zeit/next.js/issues/7915
// Please note that Next.js Link component uses ref only to prefetch link
// based on its availability in view via IntersectionObserver API -
// https://github.com/zeit/next.js/blob/canary/packages/next/client/link.tsx#L119
return (
<NextLink href={href} as={as} prefetch={prefetch} passHref {...otherProps}>
<TempShallow {...props} {...baseprops} child={child} onClick={onClick} />
</NextLink>
)
}
I am kinda new to react and to the webitself.
this is my render function
render() {
const {repositories} = this.props
return (
<div className='mt4 bt b--black-20 boardingbox scrollarea-content' style={{overflow: 'scroll', height: '100vh'}}>
{
repositories.map((repo, index) => {
console.log(repo.name)
return <Note name={repo.name} desc={repo.name} key={index} onClick={ this.handleClick.bind(this) }/>
})
}
</div>
)
}
The repositories is changing the way I want, but for some reason the its not get re-rendered. I passing the repositiores property from the parent.
The first time I render it (click to the search button, get a response from the server, and set the repo array), its working fine. But at the 2nd search, when there is something in the array, its not working properly, and not re-render.
UPDATE:
The parent's render / onClick
render() {
const {repositories} = this.state
return (
<div className='w-third navpanel br b--black-20'>
<SearchBar onClick={this.onClick} onChange={this.onChange}/>
<RepoList repositories={repositories}/>
</div>
//<NewNote />
//<Tags />
//<NoteList />
);
}
onClick = (event) => {
const {searchTerm} = this.state
let endpoint = 'https://api.github.com/search/repositories?sort=stars&order=desc&q=' + searchTerm;
fetch(endpoint)
.then(blob => blob.json())
.then(response => {
if(response.items)
this.setState({ repositories: response.items });
})
}
UP-UPDATE:
Search Comp:
constructor({onClick, onChange}) {
super()
this.onClick = onClick
this.onChange = onChange
this.state = {
imageHover: false
}}
render() {
return (
<div className='flex items-center justify-between bb b--black-20'>
<div className='ma2 inputContainer w-100'>
<input className='pa1 pl4 boardingbox w-100 input-reset ba b--black-20 br4 black-50 f6' placeholder='repos' type="text" onChange={this.onChange}/>
</div>
<div className='mr2'>
<div className='boardingbox pointer contain grow'>
<img src={(this.state.imageHover) ? NoteImageOnHover : NoteImage} alt=''
onMouseOver={()=>this.setState({imageHover: true})}
onMouseOut={()=>this.setState({imageHover: false})}
onClick={this.onClick}/>
</div>
</div>
</div>
)}
first responde
second responde
and I am really ashamed that I could screw up like this.
So basicly the problem was:
return <Note name={repo.name} desc={repo.name} key={index} onClick={ this.handleClick.bind(this) }/>
So I was as stupid to use INDEX as a KEY so I could not add again the same key to the array.
Thanks anyway guys! :)
The root cause most probably is due to error in function binding.
In your SearchComponent you are using the "props" to create function bindings in the contructor. This can cause your SearchComponent to refer to wrong instance of the functions for onClick and onChange. Would suggest referring to the official documentation for more details.
you do not need to rebind the functions in your SearchComponent, you can just use the functions received in props.
<input className='pa1 pl4 boardingbox w-100 input-reset ba b--black-20 br4 black-50 f6' placeholder='repos' type="text" onChange={this.props.onChange}/>
<!-- snipped other irrelevant code -->
<img src={(this.state.imageHover) ? NoteImageOnHover : NoteImage} alt=''
onMouseOver={()=>this.setState({imageHover: true})}
onMouseOut={()=>this.setState({imageHover: false})}
onClick={this.props.onClick}/>
Why could be happening to cause this behavior
Remember, constructor is only called once the component instance is being constructed, once it has been created and remains alive, React lifecycles take over.
So, when you first render your screen, the component is created and since there is only 1 of everything, it kind of works.
When you run your first search: onChange/onClick callbacks modify the state of the parent component. Which then calls render on the parent component.
At this point, it is possible that your SearchComponent maybe holding on to the wrong instance of the call back methods, which would thus not set state on the parent and thus not force re-render.
Additional Notes on your constructor
Normally you shouldn't refer to props in your constructor, but if you need to, then you need to have it in the format below. Here are the relevant docs:
constructor(props) {
super(props);
// other logic
}