I have a React-Select Field, inside a Formik Field, that when you select an item from the dropdown options, all the Parent Components are rerendered. This is the deepest child component available in the Container.
And it re-renders 4 Parents. Which is kind of Problematic. I want to limit the rerender of the Component to only itself.
The above happens because each Child Process passes
props to the Container, which is the master form.
And onSubmit it takes all the info(props) and makes the API Call.
I tried doing it with shouldComponentUpdate but no luck. I tried to do it with SetState, but that though fell in the water, as I couldn't make it work(Got a ton of errors).
--TLDR--
THE PROBLEM:
Make a Component retain the rendering to only itself. External Components used in it Formik and React-Select.
Here is the code for that:
<div className="w-50">
<Field
name={`${keyField}.${index}.permissions`}
render={({ field: { value, name }, form: { setFieldValue, setFieldTouched } }) => (
<div>
<label htmlFor="namespace-permissions" className="font-weight-medium">
Permissions in Namespace <Asterisk />
</label>
<Select
isMulti
closeMenuOnSelect={false}
id="namespace-permissions"
defaultValue={convertNamespaceToDefaultValue(
dependencies.namespacePermissions,
value
)}
options={convertNamespaceToSelect(dependencies.namespacePermissions)}
onChangeCallback={values => {
setFieldValue(name, convertSelectToNamespacesData(values));
setFieldTouched(name, true);
}}
/>
<ErrorMessage name={name} component={FormErrorMessage} />
</div>
)}
/>
</div>
The dependacies prop is what makes the trip up the tree, to the master form Props, and rerenders the entire Component Tree. This also, ties with another question I had yesterday, about react-select's closeMenuOnSelect={false} not working correctly.
^This is the reason why that happens. Thank you..
I don't know how you would be able to do this with the libraries that you're using. But when I don't want my components rendering unnecessarily I use React.memo it will shallow compare the props object and decide if needs to update.
From React DOCS
WITHOUT REACT MEMO
function App() {
return(
<Parent1/>
);
}
function Parent1(props) {
console.log('Rendering Parent1...');
const [parentState,setParentState] = React.useState(true);
return(
<Parent2
setParentState={setParentState}
/>
);
}
function Parent2(props) {
console.log('Rendering Parent2...');
return(
<Child
setParentState={props.setParentState}
/>
);
}
function Child(props) {
console.log('Rendering Child...');
return(
<button onClick={()=>props.setParentState((prevState)=>!prevState)}>Update ParentState</button>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
WITH REACT.MEMO
function App() {
return(
<Parent1/>
);
}
function Parent1(props) {
console.log('Rendering Parent1...');
const [parentState,setParentState] = React.useState(true);
return(
<Parent2
setParentState={setParentState}
/>
);
}
const Parent2 = React.memo(function Parent2(props) {
console.log('Rendering Parent2...');
return(
<Child
setParentState={props.setParentState}
/>
);
}
);
const Child = React.memo(function Child(props) {
console.log('Rendering Child...');
return(
<button onClick={()=>props.setParentState((prevState)=>!prevState)}>Update ParentState</button>
);
}
);
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
I would check if Formik's onSubmit is being called and if that's triggering the tree to re-render. If you have a button with type=button that could be triggering a form submit.
There is also a bug with Formik before v2 where Field will mount and unmount all of it's children on every update if given the render function through render or component prop. Instead just pass the render function as the Fields child.
Related
This is a very common performance problem while using the Context API. Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown.
If I have a the wrapper as this:
<CounterProvider>
<SayHello />
<ShowResult />
<IncrementCounter />
<DecrementCounter />
</CounterProvider>
And the value props as:
<CounterContext.Provider value={{increment, decrement, counter, hello }} >
{children}
</CounterContext.Provider>
Everytime I increment the count value from the IncrementCounter component, the entire set of wrapped components re-renders as it is how the Context API is supposed to work.
I did a bit of research and came across these solutions:
Split the Context into N number of Context according to the use-case : This solution works as expected.
Wrap the value provider using React.Memo: I saw a lot of articles suggesting to the React.Memo API as follows:
<CounterContext.Provider
value={useMemo(
() => ({ increment, decrement, counter, hello }),
[increment, decrement, counter, hello]
)}
>
{children}
</CounterContext.Provider>
This however doesn't work as expected. I still can see all the components getting re-rendered. What I'm doing wrong while using the Memo API? Dan Abramov does recommend to go by this approach in an open React issue
If anyone can help me out on this one. Thanks for reading.
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The above statement is true if a context is used like in the below example where components are directly nested in the provider. All of them re-render when count changes, no matter wether they are called useContext(counterContext) or not.
const counterContext = React.createContext();
const CounterContextProvider = () => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
<ComponentOne/>
<ComponentTwo />
</counterContext.Provider>
);
};
const ComponentOne = () => {
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider/>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The statement is false if you are consuming nested components with children. This time when count changes CounterContextProvider renders, but since it's rendering because its state has changed and not because of its parent rendering, and because a component cannot mutate its props, React won't render children. That's it if it was a normal component.
But since there is a context involved here, React will find all components that contain useContext(counterContext) and render them.
const counterContext = React.createContext();
const CounterContextProvider = ({ children }) => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
{children}
</counterContext.Provider>
);
};
const ComponentOne = () => {
const { count } = React.useContext(counterContext);
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider>
<ComponentOne />
<ComponentTwo />
</CounterContextProvider>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In the above example only ComponentOne renders when count changes, which is normal cause he is consuming it. Every component that calls useContext(counterContext) renders if one value of the context changes.
Even with useMemo wrapping the context object as you did, that's the behavior you get as soon as one variable in its dependency array changes.
I want to disable the rendering of a component inside react.js formik library
here is an example of code structure I have currently
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
{values?.show ?
(
<Text>Hello</Text>
) :
null}
<Rerenderedcomponent /> //no prop passed here
)
</formik>
And here is an example of my Rerendered component file
function Rerenderedcomponent()
{
const callingAPI = useCallback(()=>response,[])
}
export default React.memo(Rerenderedcomponent)
Now as I am clicking on the button(name showbtn) formik "show" field value is getting updated but my component(Rerenderedcomponent) is also getting rerendered & hence the api in it is getting called again
I tried by setting enableReinitialize={false} but nothing works
Is it possible to prevent this rerendering of the component(Rerenderedcomponent) on formik field update
PS:- The component should remain inside formik tag only
I prevent the component rerendering inside formik using the below workaround:
Created a new component say (Hello.js) & included the conditonal rendering(that was inside formik tag previously) inside it, like an example shown below
function Hello({show})
{
return(
<>
{show && <Text>Hello</Text>}
</>
)
}
export default React.memo(Hello);
Now I just imported & use the Hello.js component inside formik as shown below
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
<Hello show={values?.show}/> // Hello.js component
<Rerenderedcomponent /> //this will not rerender now
)
</formik>
Now since the component is already mounted into the DOM the rerendering will not occur on show value change
Also there is one another workaround to resolve this issue just by changing the order of components inside formik tag
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
<Rerenderedcomponent /> //placed above conditional rendering
{ values?.show ?
(
<Text>Hello</Text>
) :
null
}
)
I moved the rerendered component above the conditional rendering & it resolved the issue
To prevent RerenderedComponent from contacting the api every time. You must define a state in the parent component and pass it to child component:
const [apiData, setApiData] = useState(); // <===
return (
<Formik
initialValues={{ show: false }}
onSubmit={(values) => {}}
>
{({ setValues, values }) => (
<Form>
<button
type="button"
onClick={() => setValues({ show: !values.show })}
>
{values.show ? "hide" : "show"}
</button>
{values.show && (
<Rerenderedcomponent apiData={apiData} setApiData={setApiData} /> // <===
)}
</Form>
)}
</Formik>
);
And in the child component, you can check the existence of apiData and communicate with the api if needed:
function Rerenderedcomponent({ apiData, setApiData }) {
useEffect(() => {
if (!apiData) {
// fetch data here ...
setApiData('<response>');
}
}, []);
return null; // A Redact component must return a value
}
I'm a bit surprised I'm having trouble finding this online, but I can't seem to find an example of how to do this in a React functional component. I have a React component that I would like to render when I click a button. Right now the function fires and I can see my console.log firing, however the component isn't rendering. My first guess was that it won't render because React doesn't know to update the view, however I added boolean via useState and it still won't render. What am I doing wrong?
Below is the relevant code. How can I get the component in addSection to render?
const FormGroup = ({index}) => {
const [additionalSection, setAdditionalSection] = useState(false);
const addSection = form => {
setAdditionalSection(true);
console.log('form', form);
return additionalSection && (
<div key={form.prop}>
<p>This should render</p>
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
</div>
);
};
...
return (
...
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)}
>
span className="button--small">{form.button}</span>
</LinkButton>
</FormAdd>
);
You should change your state (or a prop in your useEffect dependency array in case you had one) in order to force a rerender. In this case:
setAdditionalSection(prevState=>!prevState);
A state change like the one you are calling, will trigger a re-render.
But all html to be rendered must be included in the functional components return statement.
The elements you want to render can be conditionally rendered like this:
const FormGroup = ({index}) => {
const [additionalSection, setAdditionalSection] = useState(false);
const addSection = form => {
setAdditionalSection(true);
console.log('form', form);
};
...
return (
...
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)}
>
<span className="button--small">{form.button}</span>
</LinkButton>
{additionalSection &&
<div key={form.prop}>
<p>This should render</p>
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
</div>
}
</FormAdd>
);
Alright I am trying to submit two different forms as independent components in another page
component where I only have one button to submit the data of both forms.
So I am struggling to have a shared state in the page component and I need to pass the whole state of each form component to my page component on submit.
Can anyone recommend a best practice for my use case ?
render() {
return (
<div as={Row} className="container" style={formStyle}>
<Col>
<Form onSubmit={this.submitData}>
<TripForm />
<PostForm heading="add your first blog entry" />
<Button variant="dark" type="submit">
Summing up
</Button>
</Form>
</Col>
</div>
);
}
define your state in the parent component and pass it down in props
class PageComponent = {
state = { } //define your state here
handleChange = () => {} // define a function that handles changing state
submitData = () => {
// in here you can access this.state and then submit form data with that state
}
render() {
return (
<div as={Row} className="container" style={formStyle}>
<Col>
<Form onSubmit={this.submitData}>
<TripForm handleChange={handleChange} someState={someState} />
<PostForm heading="add your first blog entry" handleChange={handleChange} someState={someState}/>
<Button variant="dark" type="submit">
Summing up
</Button>
</Form>
</Col>
</div>
);
}
}
I've also defined someState which you can pass down as props to the child/form components. once you set state in there with handleChange it will set state in the parent component and you can submitData with that state
Lets say I have a view component that has a conditional render:
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
MyInput looks something like this:
class MyInput extends React.Component {
...
render(){
return (
<div>
<input name={this.props.name}
ref="input"
type="text"
value={this.props.value || null}
onBlur={this.handleBlur.bind(this)}
onChange={this.handleTyping.bind(this)} />
</div>
);
}
}
Lets say employed is true. Whenever I switch it to false and the other view renders, only unemployment-duration is re-initialized. Also unemployment-reason gets prefilled with the value from job-title (if a value was given before the condition changed).
If I change the markup in the second rendering routine to something like this:
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<span>Diff me!</span>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
It seems like everything works fine. Looks like React just fails to diff 'job-title' and 'unemployment-reason'.
Please tell me what I'm doing wrong...
Change the key of the component.
<Component key="1" />
<Component key="2" />
Component will be unmounted and a new instance of Component will be mounted since the key has changed.
Documented on You Probably Don't Need Derived State:
When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.
What's probably happening is that React thinks that only one MyInput (unemployment-duration) is added between the renders. As such, the job-title never gets replaced with the unemployment-reason, which is also why the predefined values are swapped.
When React does the diff, it will determine which components are new and which are old based on their key property. If no such key is provided in the code, it will generate its own.
The reason why the last code snippet you provide works is because React essentially needs to change the hierarchy of all elements under the parent div and I believe that would trigger a re-render of all children (which is why it works). Had you added the span to the bottom instead of the top, the hierarchy of the preceding elements wouldn't change, and those element's wouldn't re-render (and the problem would persist).
Here's what the official React documentation says:
The situation gets more complicated when the children are shuffled around (as in search results) or if new components are added onto the front of the list (as in streams). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a key.
When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
You should be able to fix this by providing a unique key element yourself to either the parent div or to all MyInput elements.
For example:
render(){
if (this.state.employed) {
return (
<div key="employed">
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div key="notEmployed">
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
OR
render(){
if (this.state.employed) {
return (
<div>
<MyInput key="title" ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
<MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
Now, when React does the diff, it will see that the divs are different and will re-render it including all of its' children (1st example). In the 2nd example, the diff will be a success on job-title and unemployment-reason since they now have different keys.
You can of course use any keys you want, as long as they are unique.
Update August 2017
For a better insight into how keys work in React, I strongly recommend reading my answer to Understanding unique keys in React.js.
Update November 2017
This update should've been posted a while ago, but using string literals in ref is now deprecated. For example ref="job-title" should now instead be ref={(el) => this.jobTitleRef = el} (for example). See my answer to Deprecation warning using this.refs for more info.
Use setState in your view to change employed property of state. This is example of React render engine.
someFunctionWhichChangeParamEmployed(isEmployed) {
this.setState({
employed: isEmployed
});
}
getInitialState() {
return {
employed: true
}
},
render(){
if (this.state.employed) {
return (
<div>
<MyInput ref="job-title" name="job-title" />
</div>
);
} else {
return (
<div>
<span>Diff me!</span>
<MyInput ref="unemployment-reason" name="unemployment-reason" />
<MyInput ref="unemployment-duration" name="unemployment-duration" />
</div>
);
}
}
I'm working on Crud for my app. This is how I did it Got Reactstrap as my dependency.
import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';
const LifeActionCreate = () => {
let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();
const onCreate = e => {
const db = firebase.firestore();
db.collection('actions').add({
label: newLifeActionLabel
});
alert('New Life Insurance Added');
setNewLifeActionLabel('');
};
return (
<Card style={{ padding: '15px' }}>
<form onSubmit={onCreate}>
<label>Name</label>
<input
value={newLifeActionLabel}
onChange={e => {
setNewLifeActionLabel(e.target.value);
}}
placeholder={'Name'}
/>
<Button onClick={onCreate}>Create</Button>
</form>
</Card>
);
};
Some React Hooks in there