this.forceUpdate() not re-rendering dynamically created components - reactjs

Assume all the various components have been defined.
In my react component, I want the button click to trigger the appending of a new TextBox component in my dynamically created questions component. When I tested the button click with forceUpdate(), a TextBox was successfully appended to questions but there was no apparent addition of a new TextBox element. I tested whether the component was actually re-rendering by using <h4>Random number : {Math.random()}</h4> and it turns out the component was doing so, as the number changed every time I pressed the button.
Is something being done wrong?
constructor (props) {
super(props);
this.questions = [];
this.questions.push(<TextBox key={this.questions.length}/>);
this.createTextBox = this.createTextBox.bind(this);
this.loadTextBox = this.loadTextBox.bind(this);
}
createTextBox() {
this.questions.push(<TextBox key={this.questions.length}/>);
this.forceUpdate();
}
loadTextBox() {
return (this.questions);
}
render() {
return(
<div>
<h4>Random number : {Math.random()}</h4>
{this.loadTextBox()}
<ButtonToolbar className="add-question">
<DropdownButton bsSize="large" title="Add" id="dropdown-size-large" dropup pullRight>
<MenuItem eventKey="1" onClick={this.createTextBox}>Text Box</MenuItem>
</DropdownButton>
</ButtonToolbar>
</div>
);
}

Only items inside this.state are properly monitored by React on whether or not a rerender should occur. Using this.forceUpdate does not check to see if this.questions has been changed.
Use this.questions as this.state.questions. When you do this, do not mutate this.state.questions. Instead, make a new copy of it and use this.setState on it.
constructor (props) {
super(props);
this.state = {
questions: [<TextBox key={0}/>]
}
this.createTextBox = this.createTextBox.bind(this);
this.loadTextBox = this.loadTextBox.bind(this);
}
createTextBox() {
const newQuestions = [...this.state.questions, <TextBox key={this.questions.length}/>]
// or you can use
// const newQuestions = this.state.questions.concat(<TextBox key={this.questions.length + 1}/>)
this.setState({questions: newQuestions})
}
loadTextBox() {
return (this.state.questions);
}
One important thing to note is that this.forceUpdate is almost never needed. If you find yourself using it, you are writing your code in an unoptimal way. I made some modifications to your code regarding how keys are assigned. The only reason you should ever be checking for updates is if something in this.state has changed, which involves using this.setState.

Related

How to handle multiple material-UI Popovers [React.js]?

My app has multiple Popover components, I know how to handle the state of one Popover component, using something like this:
class App extends Component {
constructor(props) {
super(props);
this.state = { pop_open: false };
}
handleProfileDropDown(e) {
e.preventDefault();
this.setState({
pop_open: !this.state.pop_open,
anchorEl: e.currentTarget,
});
}
handleRequestClose() {
this.setState({
pop_open: false,
});
};
render() {
return (
<div>
<button type="submit" onClick={this.handleProfileDropDown.bind(this)} >My Customized PopOver</button>
<Popover
open={this.state.pop_open}
anchorEl={this.state.anchorEl}
onRequestClose={this.handleRequestClose.bind(this)}
>
{"content"}
</Popover>
</div>
);
}
}
But for more than one Popover, I do not know how to do that, should I create a state for each Popover? Sorry for the question but I am new to the frontend world.
note: kindly do not use hooks in your answer.
An internal state is a good option when only the Component is going to modify it. It keeps the logic simple and inside the same block of code. On the other hand managing the state from outside of the Component lets other components read its values and modify them. This is a common approach when using Redux or Context, where there is a global app state. This state is meant for properties that several Components need to read/write to.
Which to use when is a design decision and depends on each situation. In my opinion each Component should handle its own state when possible. For example, when values are only going to be modified by it, or a children Component. Having an external state makes sense when multiple Components are going to read or modify it, or when the state values need to be passed several levels deep in the hierarchy.
In the example you propose I can see that the Popover is working with an internal state. This can work and you can use the Component several times and it will carry all the logic inside. If you rename the Components you can see more easily what I mean. I dont know exactly how the Component with the button works but this is to make the explanation clear:
class Popover extends Component {
constructor(props) {
super(props);
this.state = { is_open: false };
}
open = () => {
this.setState({
is_open: true
});
}
close = () => {
this.setState({
is_open: false
});
}
toggle = () => {
this.setState(prevState => ({
is_open: !prevState.is_open
}));
}
render() {
return (
<div>
<button onClick={this.toggle}>
Open
</button>
{this.state.is_open && <PopoverContent />}
</div>
);
}
}
If you need further explanation or something is not clear, let me know.

How to make subchild affect parent state in react

Intro
I'm just beggining with react, but I've got a project and I want to be able to affect parent state from a sub components (or however it's made).
The final result is to get a Contact list that can be edited on the fly.
Problem:
The easiest way to simplify the probably, that I have is probably by starting with the TodoApp (from React's site) that i've modified slightly. Instead of having a list item that is staticly constructed from the state
ParentState ---> Content
I want to be able to have something like this
ParentState <--> ContentInput
State of my problem:
The following code is where i'm stuck at. There is a comment down bellow. I would like to have that imput affect the TodoApp's State. Maybe I got it the wrong way, if so, what is the Right Way?
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = { items: [] };
this.handleSubmit = this.handleSubmit.bind(this);
this.showState = this.showState.bind(this);
}
render() {
return (
<div>
<h3>TODO</h3>
<button onClick={this.showState}>Console log current state</button>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input ref="field" />
<button>
Add #{this.state.items.length + 1}
</button>
</form>
</div>
);
}
handleSubmit(e) {
e.preventDefault();
if (!this.refs.field.value.length) {
return;
}
const newItem = {
text: this.refs.field.value,
id: Date.now()
};
this.setState(prevState => ({
items: prevState.items.concat(newItem)
}));
}
showState() {
console.log(this.state)
}
}
class TodoList extends React.Component {
render() {
return (
<ul>
{this.props.items.map(item => (
// MAKE THAT INPUT CHANGE THE PARENT STATE
<li key={item.id}><input type="text" defaultValue={item.text} /></li>
))}
</ul>
);
}
}
ReactDOM.render(<TodoApp />, document.getElementById('root'))
https://codepen.io/smdimagerie/pen/Zvdoaj?editors=0010
If you really need direct communication between your parent and something deep in its render tree, you typically have a questionable design going on that should get cut up into single parent-child communication steps, so that you can ask at each step "is it really necessary that this specific child talks to this specific parent?".
That said, the obvious React way to do this is to pass down a function handler so that children can propagate data to a parent, which can then do "whatever is necessary":
class Parent extends Component {
onChange(e) {
...
}
render() {
return <Child onChange={e => this.onChange(e)}/>
}
}
and then make the child call its this.props.onChange(...) when you need it to trigger functionality in the parent. If you need that to happen in the child's children, then you keep passing it down as far as necessary.
Alternatively, if you have a distance to cover, what you probably need instead is for "maybe some component, I don't know which, and I don't care" to do something based on an event getting generated. In this case, you can either use standard JS custom events dispatched on the document, or use a dipatching service like flux (which for small use cases is absurd overkill).

Tabs only mount Tab content on the first time it becomes active

I would like to load the tab content only on the first time it becomes active, after that the content stays in the DOM
This is what I have
<Tabs defaultActiveKey={1} animation={false} id="my-tabs" mountOnEnter unmountOnExit>
<Tab eventKey={1}>
<div>content1</div>
</Tab>
<Tab eventKey={2}>
<div>content1</div>
</Tab>
</Tabs>
it works fine, but there is a lag between switching tabs, since the content I have is quite large and I would like to render it only once, on the first time the tab becomes active.
Is there a way to achieve that? I'm using react-bootstrap 0.30.10
UPDATE:
apparently mountOnEnter must be used with animation, otherwise it will not work as intended. I made the change and it works fine now
Old answer:
so I have come up with this wrapping component as follow
class TabsLazyLoad extends Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
this.handleSelect = this.handleSelect.bind(this);
}
getInitialState() {
return {
key: this.props.key || this.props.defaultActiveKey,
rendered: [],
};
}
addRenderedTab(key) {
const newState = _.cloneDeep(this.state);
newState.rendered.push(key);
this.setState(newState);
}
handleSelect(key) {
this.setState({ key });
}
render() {
return (
<Tabs activeKey={this.state.key} onSelect={this.handleSelect} {...this.props}>
{_.map(this.props.children, (tabComponent) => {
if (_.includes(this.state.rendered, tabComponent.props.eventKey)) {
return tabComponent;
}
if (tabComponent.props.eventKey === this.state.key) {
this.addRenderedTab(this.state.key);
}
// if it's not rendered, return an empty tab
const emptyTab = _.cloneDeep(tabComponent);
emptyTab.props.children = null;
return emptyTab;
})}
</Tabs>
);
}
}
TabsLazyLoad.propTypes = Tabs.propTypes;
It seems to be working fine, but I reckon this is a bit hacky, but it's the best I can come up with for now.
It sounds like a good use case for the "Avoid Reconciliation" option that React provides.
Here's a link to the relevant section in the documentation.
Essentially, there's a lifecycle event called shouldComponentUpdate that defaults to true. When you change it to false, it tells React not to run the component through the standard Reconciliation process (i.e. the "diff" checks).
Like with any lifecycle method, you can create a conditional statement for it.
For a component that should be made completely static after its first render, this is really all you need:
class YourComponent extends React.Component {
...
shouldComponentUpdate() {
return false;
}
...
}
However, for a more general use case, you'd want to write a conditional statement based on the props and/or the state of the component:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// Your state
};
}
shouldComponentUpdate(nextProps, nextState) {
// A conditional statement to determine whether
// this component should check for updates or not
}
render () {
return (
<div>
{/* Your JSX*/}
</div>
)
}
I don't use React Boostrap but I guess it's based on the Component design,
example, the rendered content used TabIndex state. Take a closer look at this sample code:
renderActiveTabContent() {
const { children } = this.props
const { activeTabIndex } = this.state
if (children[activeTabIndex]) {
return children[activeTabIndex].props.children
}
}
So the content component render every time Tab state is indexed.
You could use https://github.com/reactjs/react-tabs for your solution other wise take a look of those codes to write a simple one, the Component is rendered once and show/hide state via display: style attribute.
Hope it's help.

ReactJS displaying / hiding parallel components

Preface: I'm very new to ReactJS, so my question will reflect my lack of understanding of how things work. Right now I'm using React + Alt.
I have a basic page with a parent component (I'll call it MyContainer) that has multiple parallel panel-esque components (MyPanel) displayed side by side. What I'm now trying to implement is each panel component has an Expand icon that when clicked, will make that panel take up the full width of its parent container (ie. width:100%) and hide all other panels (ie: display:none or visibility:hidden). When the button is clicked again, the initial state of all panels is again shown.
So my question is the proper way to implement this in ReactJS/Alt. So far I have the easy part done, which is listening for the Expand button click and updating the state of whether the panel should be displayed as normal or full-width.
export default class MyPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false
};
this.handleExpand = this.handleExpand.bind(this);
}
handleExpand() {
this.setState({ expanded: !this.state.expanded });
}
render() {
var expandStyle = {};
if (this.state.expanded) {
expandStyle.width = 'calc(100% - 40px)';
}
return(
<div style={expandStyle}>
<ExpandButton onExpandEvent={this.handleExpand} />
{/* Rest of panel props data rendered here */}
</div>
);
}
}
The part I don't know how to do is have this component tell its sibling components whether they should hide or show itself. Trying to find similar questions here, I think there are two approaches: using props in the parent component to keep track of which panels are hidden or shown, or using Store/Actions to keep track of this, but I'm not sure on the implementation of either.
Again, I'm new to ReactJS, so go easy on me :)
You have your methodology for how to expand/hide slightly off: since your panels are all encompassed within a parent component, that component should decide if an individual panel is normal, expanded, or hidden. You should be using the state of the parent component to accomplish this, rather than the state of each panel component. For example:
var Container = React.createClass({
getInitialState () {
return {
expandedPanel: null,
panels: someArrayOfPanelData
};
},
changeExpandedPanel (panelIndex) {
return () => {
this.setState({
expandedPanel: panelIndex
});
};
},
render () {
let panels = this.state.panels.map((panelData, i) => {
return (
<Panel
key={i}
data={panelData}
expanded={(i == this.state.panelExpanded)}
handleExpansion={this.changeExpandedPanel(i)}
/>
);
});
return (
<div>
{panels}
</div>
);
}
});
var Panel = React.createClass({
propTypes: {
...,
handleExpansion: React.PropTypes.function.isRequired
},
handleExpand () {
this.props.handleExpansion();
},
render () {
...
}
});
What you are doing here is passing the parent's event handler down to the children. Since the function is defined in the parent, it has access to the parent's state/props, etc.

ReactJS get rendered component height

I'm attempting to integrate or create a React version of https://github.com/kumailht/gridforms, to do so I need to normalize the height of the columns inside of the row. The original takes the height of the grid row and applies it to the children columns.
I had planned to get the height of the row and then map it to a property of the child, though from my attempts I'm thinking this might not be the ideal way or even possible?
Below is my current code.
GridRow = React.createClass({
render(){
const children = _.map(this.props.children, child => {
child.props.height = // somehow get row component height
return child
})
return (<div data-row-span={this.props.span} {...this.props}>
{children}
</div>)
}
})
GridCol = React.createClass({
render(){
return (<div data-field-span={this.props.span} style={{height:this.props.height}} {...this.props}>
{this.props.children}
</div>)
}
})
I tested setting the style this way and it will work, however getting the height isn't.
EDIT: Fiddle:
https://jsfiddle.net/4wm5bffn/2/
A bit late with the answer but technically you can get element hight this way:
var node = ReactDOM.findDOMNode(this.refs[ref-name]);
if (node){
var calculatedHeight = node.clientHeight;
}
According to current React docs, the preferred use of refs is to pass it a callback rather than a string to be accessed elsewhere in this.refs.
So to get the height of a div (within a React.Component class):
componentDidMount() {
this.setState({ elementHeight: this.divRef.clientHeight });
}
render() {
return <div ref={element => this.divRef = element}></div>
}
Or it works this way, though I don't know if this is advisable since we set state in the render method.
getHeight(element) {
if (element && !this.state.elementHeight) { // need to check that we haven't already set the height or we'll create an infinite render loop
this.setState({ elementHeight: element.clientHeight });
}
}
render() {
return <div ref={this.getHeight}></div>;
}
Reference: https://facebook.github.io/react/docs/more-about-refs.html
Don't know about anyone else but I always have to get it on the next tick to be sure of getting the correct height and width. Feels hacky but guessing it's to do with render cycle but I'll take it for now. onLayout may work better in certain use cases.
componentDidMount() {
setTimeout(() => {
let ref = this.refs.Container
console.log(ref.clientHeight)
console.log(ref.clientWidth)
}, 1)
}
Here is an example of using refs and clientWidth/clientHeight:
import React, { Component } from 'react';
import MyImageSrc from './../some-random-image.jpg'
class MyRandomImage extends Component {
componentDidMount(){
let { clientHeight, clientWidth } = this.refs.myImgContainer;
console.log(clientHeight, clientWidth);
}
render() {
return (
<div ref="myImgContainer">
<img src={MyImageSrc} alt="MyClickable" />
</div>
);
}
}
export default MyRandomImage;
Note: this appears to work for width reliably, but not height. Will edit if I find a fix...
My personal opinion is to try and avoid using static and measured sizes like this if you can avoid it because it can complicate the application unnecessarily. But sometimes you cannot get around it. Your component will need to be mounted before you can get a size from it.
General approach:
Give the element a ref
When the element is rendered, grab the ref and call .clientHeight and/or .clientWidth
Put the values on the state or pass with props
Render the element that needs the size from the state variables
In your case you want to grab the size of a column you can do something like:
GridRow = React.createClass({
render(){
const children = _.map(this.props.children, child => {
child.props.height = // somehow get row component height
return child
})
return (<div data-row-span={this.props.span} {...this.props}>
<GridCol onSizeChange={(size) => {
//Set it to state or whatever
console.log("sizeOfCol", size);
}} />
</div>)
}
})
GridCol = React.createClass({
componentDidMount(){
//Set stizes to the local state
this.setState({
colH: this.col.clientHeight,
colW: this.col.clientWidth
});
//Use a callback on the props to give parent the data
this.props.onSizeChange({colH: this.col.clientHeight, colW: this.col.clientWidth})
}
render(){
//Here you save a ref (col) on the class
return (<div ref={(col) => {this.col = col}} data-field-span={this.props.span} style={{height:this.props.height}} {...this.props}>
<.... >
</div>)
}
})
According this answer sizes of a component can be turned out having zero width or height inside componentDidMount event handler. So I'm seeing some ways to solve it.
Handle the event on top-level React component, and either recalculate the sizes there, or redraw the specific child component.
Set the load event handler on the componentDidMount to handle loading the cells into the react component to recalculate the proper sizes:
componentDidMount = () => {
this.$carousel = $(this.carousel)
window.addEventListener('load', this.componentLoaded)
}
Then in the componentLoaded method just do what you need to do.
A bit more late, but I have an approach which can be used without using the getElementById method. A class based component could be created and the sample code can be used.
constructor(props) {
super(props);
this.imageRef = React.createRef();
}
componentDidMount(){
this.imageRef.current.addEventListener("load", this.setSpans);
}
setSpans = () => {
//Here you get your image's height
console.log(this.imageRef.current.clientHeight);
};
render() {
const { description, urls } = this.props.image;
return (
<div>
<img ref={this.imageRef} alt={description} src={urls.regular} />
</div>
);
}
Above solutions are good. I thought I'd add my own that helped me solve this issue + others discussed in this question.
Since as others have said a timeout function is unpredictable and inline css with javascript variable dependencies (ex. style={{height: `calc(100vh - ${this.props.navHeight}px)`}}) can alter the height of elements after the componentDidMount method, there must be an update after all of the elements and inline javascript-computed css is executed.
I wasn't able to find very good information on which elements accept the onLoad attribute in React, but I knew the img element did. So I simply loaded a hidden image element at the bottom of my react component. I used the onLoad to update the heights of referenced components elsewhere to yield the correct results. I hope this helps someone else.
_setsectionheights = () => {
this.setState({
sectionHeights: [
this.first.clientHeight,
this.second.clientHeight,
this.third.clientHeight,
]
});
}
render() {
return (
<>
<section
ref={ (elem) => { this.first = elem } }
style={{height: `calc(100vh - ${this.props.navHeight}px)`}}
>
...
</section>
...
<img style={{display: "none"}} src={..} onLoad={this._setsectionheights}/>
</>
);
}
For the sake of being thorough, the issue is that when the componentDidMount method is executed, it only considers external css (speculation here). Therefore, my section elements (which are set to min-height: 400px in external css) each returned 400 when referenced with the clientHeight value. The img simply updates the section heights in the state once everything before it has loaded.
I'd rather do it in componentDidUpdate, but by making sure a condition is met to prevent an infinite loop:
componentDidUpdate(prevProps, prevState) {
const row = document.getElementById('yourId');
const height = row.clientHeight;
if (this.state.height !== height) {
this.setState({ height });
}
}

Resources