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
}
Related
I have currently a drop-down select to filter some charts after 'Apply'. It works fine.(See screenshot below).
The problem is that when another timespan gets selected, React does a re-render to all charts before I click 'Apply' button.
I want to avoid this unnecessary re-render by implementingshouldComponentUpdate, but I can't figure out how.
Below what I tried but it did not work(still a re-render):
shouldComponentUpdate(nextState) {
if (this.state.timespanState !== nextState.timespanState) {
return true;
}
return false;
}
But it always return true, because nextState.timespanState is undefined. Why?
Drop-down Select
<Select value={this.state.timespanState} onChange={this.handleTimeSpanChange}>
handleTimeSpanChange = (event) => {
this.setState({ timespanState: event.target.value });
};
constructor(props) {
super(props);
this.state = { timespanState: 'Today'};
this.handleTimeSpanChange = this.handleTimeSpanChange.bind(this);
}
You're on the right track with using shouldComponentUpdate, it's just that the first parameter is nextProps and the second is nextState, so in your case, the undefined value is actually nextProps with the wrong name.
Change your code to this,
shouldComponentUpdate(nextProps,nextState) { // <-- tweak this line
if (this.state.timespanState !== nextState.timespanState) {
return true;
}
return false;
}
Finally, I solve the problem by separating drop-down selectbox and charts into two apart components and made the drop-down component as a child component from its parent component, charts components.
The reason is the following statement
React components automatically re-render whenever there is a change in their state or props.
Therefore, React will re-render everything in render() method of this component. So keeping them in two separate components will let them re-render without side effect. In my case, any state changes in drop-down or other states in Filter component, will only cause a re-render inside this component. Then passing the updated states to charts component with a callback function.
Something like below:
Child component
export class Filter extends Component {
handleApplyChanges = () => {
this.props.renderPieChart(data);
}
render(){
return (
...
<Button onClick={this.handleApplyChanges} />
);
}
}
Parent component
export class Charts extends Component{
constructor(props){
this.state = { dataForPieChart: []};
this.renderPieChart = this.renderPieChart.bind(this);
}
renderPieChart = (data) => {
this.setState({ dataForPieChart: data });
}
render(){
return (
<Filter renderPieChart={this.renderPieChart} />
<Chart>
...data={this.state.dataForPieChart}
</Chart>
);
}
}
If still any question, disagreement or suggestions, pls let me know:)
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.
I am writing a higher-order component that takes children and then re-renders them based on the state of a context provider.
Consider the following simplified example:
index.js
const ChildElem = () => {
return(
<div/>
)
}
class Example extends React.Component{
render(){
return(
<FocusProvider>
<ChildElem/>
<ChildElem/>
<ChildElem/>
</FocusProvider>
)
}
}
FocusProvider.js
class FocusProvider extends React.Component{
renderChildren = (providerState) => {
//Does nothing with state and simply returns children yet they still re render
return this.props.children
}
render(){
return(
<Provider>
<Subscribe to={[ContextProvider]}>
{provider => this.renderChildren(provider.state)}
</Subscribe>
</Provider>
)
}
}
As you can see from the example the children of FocusProvider are being returned from a function that subscribes to a context.
The problem I am running into is that the children are being re-rendered even though nothing is being changed on them. The only thing that is being changed is the state of the context provider they are subscribed to.
Any advice would be greatly appreciated
You can control whether the component should update or not there is a function of react component class
shouldComponentUpdate ( nextProps, nextState, nextContext ) {
/* compare nextState with your current states properties if you want to update on any basis return true if you want to render the component again */
return true; // will re-render component ,
return false; // do not re-render component even if you change component states properites
}
nextProps contains that prop the new props , and nextState contain new State properties
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.
How to initialize state with dynamic key based on props? The props is a data fetched from external source (async). So the props will change when the data is succesfully downloaded. Consider a component like this.
edit: I want to make the state dynamic because I want to generate a dialog (pop up) based on the item that is clicked. the DialogContainer is basically that. visible prop will make that dialog visible, while onHide prop will hide that dialog. I use react-md library.
class SomeComponent extends React.Component {
constructor() {
super();
this.state = {};
// the key and value will be dynamically generated, with a loop on the props
// something like:
for (const item of this.props.data) {
this.state[`dialog-visible-${this.props.item.id}`] = false}
}
}
show(id) {
this.setState({ [`dialog-visible-${id}`]: true });
}
hide(id) {
this.setState({ [`dialog-visible-${id}`]: false });
}
render() {
return (
<div>
{this.props.data.map((item) => {
return (
<div>
<div key={item.id} onClick={this.show(item.id)}>
<h2> Show Dialog on item-{item.id}</h2>
</div>
<DialogContainer
visible={this.state[`dialog-visible-${item.id}`]}
onHide={this.hide(item.id)}
>
<div>
<h1> A Dialog that will pop up </h1>
</div>
</DialogContainer>
</div>
);
})}
</div>
)
}
}
// the data is fetched by other component.
class OtherComponent extends React.Component {
componentDidMount() {
// fetchData come from redux container (mapDispatchToProps)
this.props.fetchData('https://someUrlToFetchJSONData/')
}
}
The data then is shared via Redux.
However, based on my understanding so far, state can be updated based on props with componentWillReceiveProps or the new getDerivedStateFromProps (not on the constructor as above). But, how to do that on either method?
The example here only explains when the state is initialized on the constructor, and call setState on either cWRP or gDSFP. But, I want the key value pair to be initialized dynamically.
Any help/hint will be greatly appreciated. Please do tell if my question is not clear enough.
import React from 'react';
import {connect} from 'react-redux';
import {yourAction} from '../your/action/path';
class YourClass extends React.Component {
state = {};
constructor(props){
super(props);
}
componentDidMount(){
this.props.yourAction()
}
render() {
const {data} = this.props; //your data state from redux is supplied as props.
return (
<div>
{!data ? '' : data.map(item => (
<div>{item}</div>
))}
</div>
)
}
}
function mapStateToProps(state) {
return{
data:state.data //state.data if that is how it is referred to in the redux. Make sure you apply the correct path of state within redux
}
}
export default connect(mapStateToProps, {yourAction})(YourClass)
If you do this, <div>{item}</div> will change as you change the data state. The idea is to just map the redux state to your class props - you don't have to map the props back to the state. The render() automatically listens to changes in props supplied by redux. However, if you do want to somehow know redux state change in events, you can add the following functions.
componentWillReceiveProps(newProps){
console.log(newProps)
}
getDerivedStateFromProps(nextProps, prevState){
console.log(nextProps);
console.log(prevState);
}