I want to update the state of a main component from the "A" component then push it to "B" component and then use it render to dynamically populate boxes.
Main Component :
constructor() {
super();
this.state = {
events:[],
alerts:[],
};
}
addEvent = newEvent => this.setState(state => {
const {events} = this.state
return [...events, newEvent]
});
addAlert = newAlert => this.setState(state =>{
const {alerts} = this.state
return [...alerts, newAlert]
});
render(){
const {events} = this.state
const {alerts} = this.state
console.log(events) // events are empty even after I pass and store it
// in SearchFlight Component
return(
<div >
<SearchFlight events={events} alerts={alerts} addAlert={this.addAlert} addEvent={this.addEvent} />
<Events events={events}/>
<Alerts />
</div>
);
}
SearchFlight Component(A component) :
handleSubmit= event =>{
const {addEvent} = this.props
const {addAlert} = this.props
event.preventDefault();
var newEvents=[];
var newAlerts=[];
var singleEvent={
event_name:'home',
date_time:'12-08-18 12:45 AM',
};
newEvents.push(singleEvent);
newAlerts.push("Remove the luggage tag");
addAlert(newAlerts);
addEvent(newEvents);
}
Then I have Event Component(B Component) which right now just have render method. I want to get the updated events here.
Problem : Getting empty events when I did console.log(events) in render method of Main Component
You aren't using the setState correctly in addEvents and addAlerts method. the callback pattern of setState needs to return an object
addEvent = newEvent => this.setState(state => {
const {events} = state
return { events: [...events, ...newEvent]}
});
addAlert = newAlert => this.setState(state =>{
const {alerts} = state
return {alerts: [...alerts, ...newAlert]
});
Also since events is an array of objects your need to iterate on them to render. Refer How to render an array of objects in React? answer for more details on how to do that
The State is private to the component.
If the state is passed to the child component using props, again props should not be mutated as per the reactjs guidelines.
You can only read the value in Component A from the props and modify that value to pass it further to the nested components. But can't modify the state of the Main Component from A.
Check this out: https://reactjs.org/docs/state-and-lifecycle.html
As pointed out by OneJeet React is all about top-down approach and the child component should not be aware if a parent component is stateless or stateful.
Passing the setState as a function and allowing the child component to call it, is bad practice. One way is to use redux and eliminate the need to update parent state from child component or re-structure the whole thing in a different way.
Related
i have the following problem:
I have parent component (where is button, and array of child components to render).
To each child i pass props and child uses it as state, then changes it.
The problem is that children doesn't rerender.
As it may seem not understandable, here is something more clear (i hope):
Here is the simplified version of child.js
export default function ChildComponent(props) {
const [open, setOpen] = React.useState(props.open);
const handleClick = () => {
setOpen(true);
}; /* i actually never use handleClick */
const handleClose = (event) => {
setOpen(open => !open);
};
return (
<div>
<SomeComponent hideAfterTimeMs={1000} onClose={handleClose}/>
</div>
);
}
Parent:
import React from "react";
import Child from "./demo";
class MyClass extends React.Component {
constructor(props) {
super(props);
this.state = {
something: false,
};
}
displayKids = () => {
const a = [];
for (let i = 0; i < 1; i++) {
a.push(<Child open={true} key={i} message={"Abcd " + i} />);
}
return a;
};
handleChange = e => {
this.setState(prevState => ({
something: !prevState.something,
}));
};
render() {
return (
<div>
<button onClick={this.handleChange}>Nacisnij mnie</button>
{this.displayKids()}
</div>
);
}
}
export default MyClass;
So basically the child component renders,
and sets its "open" to false,
and when i click button again
i hoped for displaying child again,
but nothing happens.
Child renders something that disappears after a few seconds.
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity.
You are using the index as a key. Please try to use a unique key. E.g. child id or random hash code.
If the key is unique and new it will re-render. Right now it is not re-rendering because the key is the same.
Check out: https://reactjs.org/docs/lists-and-keys.html#keys
It doesn't look like your components are linked in any meaningful way. Clicking the button on the My Class component updates the something state, but that state is not passed to the Child component.
Similarly, the SomeComponent component handles its own close, and tells the Child component it is closed via handleClose - but that is not communicated to the parent and neither does the parent or Child communicate any open state to SomeComponent.
Changing some state on Child will not rerender it's own children. Something new has to be passed as a prop for that to happen.
how to update a component after updating props?
I have the following component structure:
MyList.js
render() {
return(
<ComponentList products={props.products}>
<ComponentBoard product={props.product[i]} //send choosen product(1) from list(100 products)
)
}
and next components have 2 similar component contentRow
ComponentList.js (
same(
<contentRow > list .map() //100 contentRow
)
ComponentBoard.js
same(
<contentRow > // 1 contentRow
)
in the component componentRow there are fields that display and through redux change the data in the store, for example, the username.
And when I open the ComponentBoard component and change the value in the ComponentRov field, the value in the ComponentList> componentRow should also change.
For a better understanding:
ComponentList is a table of ComponentsRow, when clicked to row, That opens the ComponentBoard window, in which there is also a componentRow.
Question: how to update the data that is displayed in componentList from componentBoard? they have similar props from 1 store
When serializing props as initial state you should listen for changes in props and update the state accordingly. In class based components you use componentDidUpdate in functional components you can achieve the same result with an useEffect listening for changes in a given prop
const Component = ({ foo }) =>{
const [state, setState] = useState(foo) //initial state
useEffect(() =>{
setState(foo) //everytime foo changes update the state
},[foo])
}
The class equivalent
class Component extends React.Component{
state = { foo : this.props.foo } // initial state
componentDidUpdate(prevProps){
if(prevProps.foo !== this.props.foo)
this.setState({ foo : this.props.foo }) //everytime foo changes update the state
}
}
for a better understanding of React, I recommend you read
React Life Cycle
the general idea is to make your list into the state of the MyList.js , that way, u can update it through a function in Mylist.js and pass it as a prop to the ComponentBoard . Now you can change the state of MyList and when that changes, so does ComponentList.
class MyList extends Component {
constructor(){
super();
this.state = {
// an array of objects
}}
handleBoardChange = () => { some function using this.setState }
// the rest of your Mylist class
}
I have some heavy forms that I'm dealing with. Thus, I'm trying to squeeze performance wherever I can find it. Recently I added the Why-did-you-render addon to get more insight on what might be slowing down my pages. I noticed that, for example, when I click on a checkbox component about all of my other components re-render. The justification is always the same. WDYR says
Re-rendered because of props changes: different functions with the
same name {prev onChangeHandler: ƒ} "!==" {next onChangeHandler: ƒ}
As much as possible, I try to respect best the best practices indications that I find. The callback functions that my component passes follow this pattern
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
export function TopLevelComponent({props}){
const defaultData = {name: '', useMale: false, useFemale: false}
const [data, setData] = useState(defData);
const { t } = useTranslation();
const updateState = (_attr, _val) => {
const update = {};
update[_attr] = _val;
setData({ ...data, ...update });
}
const updateName = (_v) => updateState('name', _v);//Text input
const updateUseMale = (_v) => updateState('useMale', _v);//checkbox
const updateUseFemale = (_v) => updateState('useFemale', _v);//checkbox
...
return <div>
...
<SomeInputComponent value={data.name} text={t('fullName')} onChangeHandler={updateName} />
<SomeCheckboxComponent value={data.useMale} onChangeHandler={updateUseMale} text={t('useMale')}/>
<SomeCheckboxComponent value={data.useFemale} onChangeHandler={updateUseFemale} text={t('useFemale')}/>
...
</div>
}
In an example like this one, altering any of the inputs (eg: Writing text in the text input or clicking one of the checkboxes) would cause the other 2 components to re-render with the justification presented above.
I guess that I could stop using functional components and utilize the shouldComponentUpdate() function, but functional components do present some advantages that I'd rather keep. How should I write my functions in such a way that interacting with one input does not force an update on another input?
The problem stems from the way you define your change handlers:
const updateName = (_v) => updateState('name', _v)
This line is called on each render and thus, every time your component is rendered, the prop has a new (albeit functionality-wise identical) value. The same holds for every other handler as well.
As an easy solution you can either upgrade your functional component to a fully fledged component and cache the handlers outside of the render function, or you can implement shouldComponentUpdate() in your child components.
You need to use memo for your child components to reduce renders
const SomeInputComponent = props => {
};
export default memo(SomeInputComponent);
// if it still causes rerender witout any prop change then you can use callback to allow or block render
e.f.
function arePropsEqual(prevProps, nextProps) {
return prevProps.name === nextProps.name; // use your logic to determine if props are same or not
}
export default memo(SomeInputComponent, arePropsEqual);
/* One reason for re-render is that `onChange` callback passed to child components is new on each parent render which causes child components to re-render even if you use `momo` because function is updated on each render so in order to fix this, you can use React hook `useCallback` to get the same function reference on each render.
So in you parent component, you need to do something like
*/
import { useCallback } from 'react';
const updateName = useCallback((_v) => updateState('name', _v), [])
You have to memoize parent function before pass to children, using useCallback for functional component or converting to class property if you use class.
export default class Parent extends React.PureComponent {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
console.log("click");
}
render() {
return (
<ChildComponent
onClick={ this.onClick }
/>
);
}
}
with useCallback:
Parent = () => {
const onClick = useCallback(
() => console.log('click'),
[]
);
return (
<ChildComponent
onClick={onClick}
/>
);
}
I'd like to know, how to handle the PropTypes Error when passing a component as a child:
Failed prop type: The prop `value` is marked as required in `ChildComponent`, but its value is `undefined`.
The render works as expected and it's passing the value prop correctly.
I suppose this happens because I am putting the component in the App component's render function without any props.
I am only passing those props to the ChildComponent when the ParentComponent maps over its children (which is the ChildComponent).
See the code: https://codesandbox.io/embed/r70r5z3j9q
Is there a way to prevent this from happening?
How should I be structuring my components?
Am I not supposed to passed components as children?
EDITED: Changed prop "name" to "value". To give it a more generic feel.
I tried to simplify the problem in the code.
I know I could pass the prop directly in App.
The use case would be when the parent is doing calculations and those calculations are supposed to be passed to the child. Without explicitly knowing what these children are.
That's why I'm using it as child in the first place.
You're using cloneElement and you're passing prop to it, not to original element. To fix it, pass props directly:
const App = () => (
<div>
<ParentComponent>
<ChildComponent name="bob" />
</ParentComponent>
</div>
);
You could easily pass component as a prop (not children) to you ParentComponent and render it only after it takes some heavy calculations:
const App = () => (
<div>
<ParentComponent component={ChildrenComponent} />
</div>
);
const ParentComponent extends React.Component {
state = { heavyComputationFinished: false } // initial state
componentDidMount() {
runYourHeavyComputations
.then(() => { this.setState({ heavyComputationsFinished: true }) })
}
render() {
const { component } = this.props
const { heavyComputationsFinished, name } = this.state
// return nothing if heavy computations hasn't been finished
if (!heavyComputationsFinished) { return null }
// we're getting this component (not its rendering call) as a prop
return React.render(component, { name })
}
}
I have the following function:
update() {
this.currentItem = [];
//...Populate currentItem
this.setState({
currentItem
});
}
Which renders on the page like this;
render() {
const { currentItem } = this.state;
return {currentItem}
}
I then pass this function into a child component, like this:
<Component
update={this.update.bind(this)}
/>
and then call it like this in my child component:
let { update } = this.props;
if (typeof update === "function")
update();
The problem is that the update function does not re render the content I am updating on the parent page. As far as I understand this, whenever setState is called, render also gets called. render() does seem to be getting called, but it does not seem to display the updated value - why is this, and how can I resolve it?
I guess it could be to do with the fact that it is coming from a child component?
I have tried forceUpdate, but it does not re render the component either - what am I missing?
Try avoiding this.forceUpdate() because this will not fire shouldComponentUpdate() which is a performance hook for your Component in React. As, I saw that you are passing your state to child component and trying to update the parents state object from there, which against the law. You should create a method in parent and pass that method as a prop to the child component. It should look like this
constructor(props) {
super(props);
this.state = { loading: false };
this.update = this.update.bind(this);
}
update(newState) {
this.setState({loading: newState })
}
render() {
return <ChildComponent update={this.update} />
}
I am just guessing here but i think you set the initial value for the child component in the constructor and the value you want it to reflect points to its own state instead of the parents state
I needed to set the state to loading first, then when I set it to loading = false, it would re render that page
this.setState({
loading:true
});
//...Do the work
this.setState({
loading:false
});