How to Get: Component Width After Render in React - javascript

I'm trying to create a general purpose component, that I can reuse in other applications. I need to know the width of the component after render so that I can modify the content of the component.
I've been trying to use the different life cycles in react without success.
componentDidUpdate() {
console.log('width', this.element.offsetWidth);
}
render() {
return (
<div ref={(element) => {this.element = element }} />
);
}
When I try this I get the width of the screen, but if I change the size of the window, I get the width of the component. See the Chrome Log:
ComponentDidMount executes before render so this.element is undefined.
I've also attempted to use different libraries from npm to solve this without luck.
Futher information: The component has to work inside a Bootstrap column, at different widths.
render() {
<Row>
<Col sm={3} />
<MyComponent />
</Col>
<Col sm={9} />
<MyComponent />
</Col>
<Row>
}
Clarification I do not want to resize the window, and I apologize for not being clear. The only reason for me to mention the resizing is that when the DOM has been created and I resize, I get the correct value in offsetWidth. I'm looking for a solution where I get the correct value without resizing. Either a post render function call, listeners, some react magic, or other solutions. My problem is my lack of knowledge with the virtual vs. real DOM.

I was unable to solve this problem with the answers given here. I only got the width of the browser window and not the component within. After some research, it looks like I'm having a chicken or the egg problem with the render. After some more research, I found react-sizeme that solves the issue.
import React, { Component } from 'react';
import sizeMe from 'react-sizeme';
class MyComponent extends Component {
render() {
const { width } = this.props.size;
return (
<div style={{
width: '100%',
backgroundColor: '#eee',
textAlign: 'center'
}}>
<span>My width is: {Math.floor(width)}px</span>
</div>
);
}
}
export default sizeMe()(MyComponent);
Which will produce the following when it first renders

If you need to hold component width state you can do something like this:
componentDidMount(){
this.boundingBox = this.element.getBoundingClientRect();
this.setState({
width:this.boundingBox.width
});
Observable.fromEvent(this.element,"resize")
.subscribe(
() => {
this.boundingBox = this.element.getBoundingClientRect();
this.setState({
width:this.boundingBox.width
});
}
);
};
You can replace Observable with event listener.
Alternatively you can update bounding box attached to class and derive state from it somewhere else.
componentDidUpdate(){
this.boundingBox = this.element.getBoundingClientRect();
};

Whereas this is not an answer to your question directly, it's a solution to your problem that does not add resizing and dirty logic inside of your components.
I'd recommend something like https://github.com/digidem/react-dimensions - which is a HOC wrapper and will listen to global resize events and send you props - containerWidth and containerHeight - I tend to use it a lot when working with SVG, canvas and data grids in react that need to remain responsive and need to know the element's size.
As for lifecycles - things like componentDidUpdate may not behave the way you think it should. Mount is a one-off. Read this comment - https://github.com/facebook/react/issues/2659#issuecomment-66165159

This "SizeMe" component helped me dynamically resize my d3 chart, thanks.
In case it may help someone, this my code from the calling Parent component:
import { SizeMe } from 'react-sizeme'
:
<SizeMe monitorWidth>
{({ size }) => (
<MyChartComponent
divChart = "my_chart_div"
chartWidth ={parseInt(size.width)}
someProps ={my_obj.some_data}
/>
)}
</SizeMe>
With the CSS:
#my_chart_div {
width: 100%;
}

Related

How to preserve children state when changing parents in react?

I am new to React and working on an app that changes navbar layout and some other details according to screen size, but renders the same responsive content inside. The <MyContent /> itself can vary according to the route or tab the user is visiting.
function App() {
const isMobile = useSomeHookToDetectIfMobile();
return (
{isMobile
? <MobileWrapper><MyContent /></MobileWrapper>
: <DesktopWrapper><MyContent /></DesktopWrapper>
}
);
}
The thing is, if the user resizes the window, when changing layout all the state inside <MyContent /> is lost and reset. What would be the most React way to maintain it on resize? Keep in mind that the shape of the state of <MyContent /> can vary according to what components it is displaying, so I don't think lifting state up is the way to go. Also its state only concerns itself, so I don't think it belongs farther up the component chain. I think I am missing some key point about the problem or coming at it the wrong way. Any help would be appreciated.
In this case, a simple local variable to keep the rendered content before the condition would be enough. That way, React can keep track of it and then it's always the same instance.
function App() {
const isMobile = useSomeHookToDetectIfMobile();
const content = <MyContent />;
return (
{isMobile
? <MobileWrapper>{content}</MobileWrapper>
: <DesktopWrapper>{content}</DesktopWrapper>
}
);
}

Reuse React component from render props

I wrote a react component in render props way,it will call children function with 3 react component object ( not sure the name exactly, the variable generated by executing jsx (<div>...</div>) );
<PaginatedTable> Usage example:
<PaginationTable data={data} ...otherprops>
{({ SearchBar, Table, PaginationBar })=>
(<div>
{SearchBar}
{Table}
{PaginationBar}
</div>)
}
</PaginationTable>
with render props, I'm so glad that I can custom these 3 child component object very easily such as rearrange order or adding custom elements between these three.
{({ SearchBar, Table, PaginationBar })=>
(<div>
{PaginationBar}
<h1> my custom search bar text </h1>
{SearchBar}
{Table}
</div>)
}
But now I wish more than arrange order inside , I wish I can move {SearchBar} out of to the same layer of 's sibling 's children such as this picture.
working demo: https://codesandbox.io/s/23q6vlywy
I thought this may be anti-pattern to the unidirectional data flow of React.
Extract {SearchBar} to another independent component then use it as <SearchBar ... /> inside of <ToolBarArea /> is what I learnd from React Doc.
But in this way, I have to do "lifting state up" and write similar function and states already had in <PaginationTable /> like below **text** parts are functions and states already had in <PaginationTable />
class ToolBarArea extends Component{
render(){
return(
<div>
// text
<SearchBar onChange={**this.props.onSearchBarChange**} />
//.... other text or elements
</div>);
}
}
class ContainerArea extends Component {
state={
**searchBarText:'',**
tableData : [{...}, {...}]
}
**onSearchBarTextChange = (event)=>{
this.setState({ searchBarText: event.target.value });
}
filterdTableData = ()=> this.state.tableData.filter(d=>d.name.includes(this.state.searchBarText);
**
}
I really hope there is a way I can simply move the variable {SearchBar} in the render props function out to without knowing is in the parent or parent's sibling or anywhere in the DOM tree.
such as
<ToolBarArea>
{SearchBar} // SearchBar from <PaginationTable />
</ToolBarArea>
Is there a way to reuseonSearchBarTextChange and filtedTableData functions and these **text** codes I already wrote in <PaginationTable /> ?
I believe you hit the nail on the head when you referred to lifting state. If you already wrote a similar function then your best option may be to 'abstract' that function so that it applies to both use cases. You could use a simple flag to differentiate the unique execution each needs. Then finally pass the function down to both components.
If you're adamant about avoiding this approach you could technically get around it by using event listeners to handle data transfer or watch variables in the window but this is for sure an anti-pattern.

How to add style - like margin - to react component?

So, expect two simple components that I have built:
import {Input} from 'semantic-ui-react';
import {Select} from 'semantic-ui-react';
const CategoriesDropdown = ({categories, onCategorySelected, selectedCategory}) => {
const handleChange = (e, {value})=>{
onCategorySelected(value);
};
return (
<Select placeholder="Select category" search options={categories} onChange={handleChange} value={selectedCategory} />
);
};
const IdentifiersInput = ({identifiers, onIdentifiersChanged}) => {
return (
<Input placeholder="Enter identifiers..." value={identifiers} onChange={onIdentifiersChanged}/>
);
};
Nothing fancy so far.
But now, I am building another component that displays those two in a flexbox row:
<Box>
<CategoriesDropdown categories={categories} selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}/>
<IdentifiersInput identifiers={identifiers} onIdentifiersChanged={this.changeIdentifiers}/>
</Box>
Unfortunately they are both displayed right next to each other without any margin in between.
Usually, I would just add a margin-left style to the second element, but because it is a React component, that doesn't work. Using style={{marginLeft: '20px'}} doesn't work as well, because the IdentifiersInput component doesn't use it.
I know that I can fix it by doing this: <Input style={style} ... inside the IdentifiersInput component.
However, this seems to be a very tedious way of achieving this goal. Basically, I have to add this to every single component I am writing.
I clearly must be missing something here. How am I supposed to apply such layout CSS properties to React components?
I think I understand.
1) Applying CSS directly to React Components does not work--I can confirm that.
2) Passing props down to the low level elements is tedious, confirmed but viable.
Notice hasMargin prop:
<Box>
<CategoriesDropdown
categories={categories}
selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}
/>
<IdentifiersInput
identifiers={identifiers}
onIdentifiersChanged={this.changeIdentifiers}
hasMargin
/>
</Box>
Possible input:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className, hasMargin }) => {
return (
<Input
className={className}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
style={hasMargin ? ({ marginLeft: '0.8rem' }) : ({})}
/>
);
};
NOTE: I do not like style as much as I like adding an additional class because classes can be adjusted via media queries:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className, hasMargin }) => {
const inputPosition = hasMargin ? `${className} margin-sm` : className
return (
<Input
className={inputPosition}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
/>
);
};
If you find inputPosition too verbose as shown above:
className={hasMargin ? `${className} margin-sm` : className}
3) You could accomplish it using a divider Component, sacreligious yet rapidly effective
<Box>
<CategoriesDropdown
categories={categories}
selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}
/>
<div className="divider" />
<IdentifiersInput
identifiers={identifiers}
onIdentifiersChanged={this.changeIdentifiers}
/>
</Box>
You can use media queries and control padding at any breakpoints if desired.
4) CSS pseudo-elements or pseudo-classes, I don't see any mention of them in answers so far.
MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes
CSS Tricks: https://css-tricks.com/pseudo-class-selectors/
Usually, when you have a random collection of DOM elements, you can calculate a way using CSS to wrangle them into the correct position. The list of available pseudo-classes is in that MDN link. It honestly helps to just look at them and reason about potential combinations.
My current issue is I don't know what is in <Box /> other than it probably has a div with display: flex; on it. If all we have to go on is that and the div is called <div className="Box">, maybe some CSS like this will fix it:
.Box {
display: flex;
}
.Box:first-child {
margin-right: 0.8rem;
}
This is why it is extremely important to know exactly what the surrounding elements will or can be, and exactly which CSS classes/IDs are nearby. We are basically trying to hook into something and correctly identify the left child in Box and add margin to the right of it, or target the right child and add margin to the left of it (or depending on everything, target both and split the additional margin onto both).
Remember there is also ::before and ::after. You are welcome to get creative and find a solution that involves position:relative and position: absolute and adds no markup.
I will leave my answer like that for now, because I think either you already thought about pseudo-selectors, or you will quickly find something that works :)
That or the divider is actually quite viable. The fact you can use media queries alleviates you from concern of future management or scalability of the components. I would not say the same about <div style={{}} />.
As your component specializes another single component it would be a good practice to pass any props your wrapper does not care for to the wrapped component. Otherwise you will loose the ability to use the api of the original <Input>component including passing styles to it:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, ...props}) = (
<Input
{...props}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
/>
);
There may be valid cases where you explicitly want to prevent users to be able to pass props to the wrapped component but that does not look like one of those to me.
I clearly must be missing something here. How am I supposed to apply
such layout CSS properties to React components?
You did not miss something. A react component has no generic way to be styled because it is no DOM element. It can have a very complicated and nested DOM representation or no representation at all. So at some point you as the designer of the component have to decided where the styles, ids and class names should be applied. In your case it is as easy as passing these props down and let the <Input> and <Select>component decide. I find that to be quite elegant rather than tedious.
I see several ways to do it, but the easiest I see would be to pass a className to IdentifiersInput like so:
<IdentifiersInput className="marginLeft" identifiers={identifiers} onIdentifiersChanged={this.changeIdentifiers}/>
Inside IdentifiersInput I would just set that class to the Input:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className}) => {
return (
<Input className={className} placeholder="Enter identifiers..." value={identifiers} onChange={onIdentifiersChanged}/>
);
};
Semantic UI's Input element can receive a className prop.
I would then just use CSS or SCSS to add styles to that particular class. In this case, the margin you want.

render simple HTML with react.cloneElement

React.cloneElement() always require first parameter as react component which should be passed as children in props.
Is there are way to pass a simple HTML node as a children. Please refer the code below for better understanding of my issue:
Dialog.jsx (Common component):
return (
<div className="app-dialog-jsx" ref={(ele) => this.ele = ele}>
{this.state.show && React.cloneElement(this.props.children, {
contentStyle: {
height: 400,
overflowY: 'auto',
overflowX: 'hidden'
},
method1: this. method1,
method2: this. method2
})}
</div>
);
now I can not pass:
<Dialog
ref={(dialog)=>this.dialog=dialog}
method1={()=>console.log(1)}
method2 ={()=>console.log(1)}
>
<h4>somethign</h4>
</Dialog>
H4 needs to be a react component otherwise it will not set the props in cloneElement. How can I send simple HTML here, any help?
Detail about why your fiddle is not working as expected.
See the code here:
{this.props.show && React.cloneElement(this.props.children, {
contentStyle: {
color:'red'
}
})}
Issue is in case of Custom Component like CCC, contentStyle will get passed as props and you are using it like this:
style={this.props.contentStyle}
That means at the end style will be applied on div not contentStyle. But in case of div, contentStyle will get applied and that will not change anything because div expect style not contentStyle.
To solve your problem rename contentStyle to style at all the places.
Check this working fiddle.
The best link that can describe the answer is here:
https://reactjs.org/warnings/unknown-prop.html
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
To fix it we should split the props before rendering. like:
render(){
const {children, addCustomProps, ...props} = this.props;
return(<div {...props}>{children}</div>);
}
To avoid the warning, we should pass only those props to the DOM , which can be recognized as a HTML attribute or React attribute like className.

Show/Hide ReactJS components without losing their internal state?

I've been hiding/showing react components by not rendering them, for example:
render: function() {
var partial;
if (this.state.currentPage === 'home') {
partial = <Home />;
} else if (this.state.currentPage === 'bio') {
partial = <Bio />;
} else {
partial = <h1>Not found</h1>
}
return (
<div>
<div>I am a menu that stays here</div>
Home Bio
{partial}
</div>
);
}
but just say that the <Bio/> component has lots of internal state. Everytime I recreate the component, it loses it's internal state, and resets to it's original state.
I know of course that I could store the data for it somewhere, and pass it in via props or just globally access it, but this data doesn't really need to live outside of the component. I could also hide/show components using CSS (display:none), but I'd prefer to hide/show them as above.
What's the best practice here?
EDIT: Maybe a better way to state the problem is to use an example:
Ignore React, and assume you were just using a desktop app that had a configuration dialog with a Tab component called A, which has 2 tabs, named 1 and 2.
Say that tab A.1 has an email text field and you fill in your email address. Then you click on Tab A.2 for a second, then click back to Tab A.1. What's happened? Your email address wouldn't be there anymore, it would've been reset to nothing because the internal state wasn't stored anywhere.
Internalizing the state works as suggested in one of the answers below, but only for the component and it's immediate children. If you had components arbitrarily nested in other components, say Tabs in Tabs in Tabs, the only way for them to keep their internal state around is to either externalize it somewhere, or use the display:none approach which actually keeps all the child components around at all times.
It just seems to me that this type of data isn't data you want dirtying up your app state... or even want to even have to think about. It seems like data you should be able to control at a parent component level, and choose to either keep or discard, without using the display:none approach and without concerning yourself with details on how it's stored.
One option would be to move the conditional inside the component itself:
Bio = React.createClass({
render: function() {
if(this.props.show) {
return <p>bio comp</p>
} else {
return null;
}
}
});
<Bio show={isBioPage} />
Whether this is "best practise" or not probably depends on the exact situation.
Unfortunately, style={{display: 'none'}} trick only works on normal DOM element, not React component. I have to wrap component inside a div. So I don't have to cascade the state to subcomponent.
<div className="content">
<div className={this.state.curTab == 'securities' ? 'active' : ''}>
<Securities />
</div>
<div className={this.state.curTab == 'plugins' ? 'active' : ''}>
<Plugins />
</div>
</div>
Looks like official documentation suggests hiding stateful children with style={{display: 'none'}}
The fundamental problem here is that in React you're only allowed to mount component to its parent, which is not always the desired behavior. But how to address this issue?
I propose the solution, addressed to fix this issue. More detailed problem definition, src and examples can be found here: https://github.com/fckt/react-layer-stack#rationale
Rationale
react/react-dom comes comes with 2 basic assumptions/ideas:
every UI is hierarchical naturally. This why we have the idea of components which wrap each other
react-dom mounts (physically) child component to its parent DOM node by default
The problem is that sometimes the second property isn't what you want
in your case. Sometimes you want to mount your component into
different physical DOM node and hold logical connection between
parent and child at the same time.
Canonical example is Tooltip-like component: at some point of
development process you could find that you need to add some
description for your UI element: it'll render in fixed layer and
should know its coordinates (which are that UI element coord or
mouse coords) and at the same time it needs information whether it
needs to be shown right now or not, its content and some context from
parent components. This example shows that sometimes logical hierarchy
isn't match with the physical DOM hierarchy.
Take a look at https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example to see the concrete example which is answer to your question (take a look at the "use" property):
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...

Categories

Resources