Checkbox in react functional component causes rerender - javascript

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.

Related

prevent re render component using React and React-memo

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

Prevent re-rendering unchanged items in todo app with React Context and useReducer

I'm creating a simple React todo app using context and useReducer, and I'm unsure how to prevent every single todo item from re-rendering when one of the changes. When one todo changes, an action is dispatched, causing the state to update. Specifically, the reducer returns a new copy of the state in which one todo is updated and the other todos are the same.
Given that state changes, it makes sense that all of the todos re-render when one is updated. However, my todos component passes the necessary props to each todo -- and those props don't change -- so I'd think that the todo components that don't have to change props wouldn't re-render. Instead, when I use dev tools and check the box to highlight re-renders, I see that they all-flash. What am I missing?
// from the reducer
case "EDIT_TODO":
return state.map(todo => {
if (todo.id === action.payload.id) {
return {
...todo,
desc: action.payload.value
};
} else {
return todo;
}
});
default:
return state;
// from the todos component
<ul className="TodosApp">
{todos.map(todo => (
<li key={todo.id}>
<Todo
id={todo.id}
complete={todo.complete}
description={todo.desc}
/>
</li>
))}
</ul>
// from the todo component
// handleToggleTodo function dispatches an "EDIT_TODO" action
const EditTodo = ({ id, description, complete }) => {
// not showing the handleToggleTodo function, which dispatches an "EDIT_TODO" action
// and has a payload with the todo id and the updated todo description.
return (
<Fragment>
<input type="checkbox" checked={complete} onChange={handleToggleTodo} />
<input
className="Todo-input"
type="text"
value={description}
onChange={handleChange}
/>
</Fragment>
)
My work is in codesandbox, https://codesandbox.io/s/determined-fire-8hirp?fontsize=14
You can use React memo() method.
https://dmitripavlutin.com/use-react-memo-wisely/
You need to wrap you stateless component with react memo and component will not be re-rendered if props are the same.
import React from "react";
import { useTodosState } from "./todos_context";
import Todo from "./todo";
const Todos = () => {
const todos = useTodosState();
return (
<>
<h2>Todos</h2>
<ul className="TodosApp">
{todos.map(todo => (
<li key={todo.id}>
<Todo
id={todo.id}
complete={todo.complete}
description={todo.desc}
/>
</li>
))}
</ul>
</>
);
};
const MemoizeTodos = React.memo(Todos);
export default MemoizeTodos;
When deciding to update DOM, React first renders your component, then
compares the result with the previous render result. If the render
results are different, React updates the DOM.
Current vs previous render results comparison is fast. But you can
speed up the process under some circumstances.
When a component is wrapped in React.memo(), React renders the
component and memoizes the result. Before the next render, if the new
props are the same, React reuses the memoized result skipping the next
rendering.

Using React.forwardRef inside render function directly

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>
)
}

With React-Router, How do I update the URL without triggering a re-render?

I have a parent component that should render another component when the URL is matches a certain path:
const View: React.SFC<Props> = ({
....
}) => {
return (
<div>
....
<Route path={jobPath} component={JobPanel} />} />
</div>
);
};
JobPanel.tsx will render if jobPath === /careers/:id which all works.
JobPanel.tsx has a link that will currently go back with this.props.history.push(/careers)
<BackLink
to="/company/careers"
onClick={(e: any) => { handleClose(); }}
>
<StyledChevron orientation={Orientation.Left} />
Go Back
</BackLink>
or
<BackLink
onClick={(e: any) => { this.props.history.push('/careers/); handleClose(); }}
>
<StyledChevron orientation={Orientation.Left} />
Go Back
</BackLink>
The problem is that JobPanel is supposed to have a transition in and out of the page with this Component:
class JobPanel extends Component {
render() {
const { isOpen, handleClose, job } = this.props;
return (
<StyledFlyout
active={isOpen}
Where isOpen is a boolean value in redux store.
While rendering JobPanel all works, I believe react-router is causing the page to re-render whenever the URL is changed. I'm not entirely sure on how to achieve no re-rendering.
Use the render prop instead of component in the Route. eg:
<Route path={jobPath} render={({ history }) => (
<JobPanel {...routeProps} />
)} />
From https://reacttraining.com/react-router/web/api/Route/component:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
For more details on using render see https://reacttraining.com/react-router/web/api/Route/render-func for more details.

Passing dynamic onChange listener to children

I have a stateful component that holds some state
state={
name:'',
age:'',
occupation:''
}
And a function to update the state onChange listener
onValueChange = (key, event) => {
this.setState({ [key]: event.target.value });
};
I pass the state and function down to child as props
<ComponentA {...this.state} changed={this.onValueChange}>
Inside my component B which is a child of A I want to programatically create inputs based on given props and change the state by invoking that function every time user types in input.
<ComponentB>
{ Object.entries(this.props)
.filter(
prop =>
prop[0] !== 'changed'
)
.map(propName => (
<Input
key={propName}
label={propName[0]}
value={propName[1]}
onValueChange={this.props.changed(propName[0])}
/>
))}
</ComponentB>
My Input component just renders the following
<input
onChange={this.props.onValueChange}
value={this.props.value}
type={this.props.type}
placeholder=" "
/>
Can't make it work for some reason.
Thanks for any help!
You are currently invoking this.props.changed straight away on render by writing onValueChange={this.props.changed(propName[0])}. Instead of invoking it on render you should give it a function to call when onValueChange occurs instead.
You also want to give the Input a unique key prop that will not change between state updates, so that React doesn't create an entirely new component every time and you e.g. lose focus of the input. You can use propName[0] instead, which will be unique.
<Input
key={propName[0]}
label={propName[0]}
value={propName[1]}
onValueChange={event => this.props.changed(propName[0], event)}
/>

Categories

Resources