My case requires that I use React.createRef() for each time picker I have (there is a certain functionality in the time pickers that can be only used through Refs).
When the page is mounted, I have initially 2 time pickers. All works well when I have only two, but my work is ruined because I am required to Add/Remove Time Pickers using buttons. So I am able to add the time pickers easily.
But now my question is how do I create the refs ? My declaration for React.createRef() is in the Constructor(){} for the first 2 refs.
The question is where do I instantiate the refs for the time pickers that are added onClick ?
Thank you
You should wrap your time picker in an another component, create the ref there and perform the work that requires a ref inside of that components then forwarding the result via props (you can pass a function via a prop).
For example, you could give each component an unique ID (uuid does a great job of that), pass that via a prop, pass a value and pass a function that accepts an ID and a value, then call that function whenever a result from the ref is obtained.
You could do something like this, but it requires you to have a unique identifier per component that should not be the index. (Cause this can change)
Pseudo Code
class Wrapper extends Component {
construct() {
...
this.refsById = {}
}
getRefOrCreate(id) {
if(_has(this.refsById[id]) {
return this.refsById[id];
} else {
this.refsById[id] = React.createRef();
return this.refsById[id];
}
}
onClickHandler(value, id) {
const ref = this.refs[id];
const { onClick } = this.props;
}
render(){
// Here you need to know how many pickers you need, and their id
const { pickersInformationArray} = this.props;
return (
<div> { pickersInformationArray.map((id) => <TimePicker ref={this.getRefOrCreate(id);} onClick={(value) => { this.onClickHandler(value, id); } } )} </div>
)
}
I found the solution.
Let me first say that I was using the method of creating ref in the constructor in my incorrect solution
class DummyClass extends Component {
constructor(){
this.timePickerRef = React.createRef();
}
}
render() {
let timers = array.map( index => {
<TimePicker
ref={timepicker => timePickerRef = timepicker}
value={00}
onChange={(data) =>
{this.handleTimePcikerValueChange(data, index);}}
/>
}
return (
timers
)
}
}
what I instead did was the following
disregard and remove this.timePickerRef = React.createRef() since it will no longer be necessary
while the code in handleTimePcikerValueChange & render will be as follows:
handleTimePcikerValueChange = (value, index) => {
// do whatever manipulation you need
// and access the ref using the following
this[`timePicker_${index}`]
}
render() {
let timers = array.map( index => {
<TimePicker
ref={timepicker => this[`timePicker_${index}`] = timepicker}
value={00}
onChange={(data) =>
{this.handleTimePcikerValueChange(data, index);}}
/>
}
return (
timers
)
}
I didn't post the code that handle adding time pickers because it is irrelevant.
I would like to thank those who responded !
Related
I have a List component as shown bellow. Component renders list of Items and listens for item changes using websocket (updateItems function). Everything works fine except that I noticed that when a single item change my renderItems function loops through all of items.
Sometimes I have more than 150 items with 30 updates in a second. When this happens my application noticeable slows down (150x30=4500 loops) and when another updateItems happens after, its still processing first updateItems. I implemented shouldComponentUpdate in Items component where I compare nextProps.item with this.props.item to avoid unnecessary render calls for items that are not changed. Render function is not called but looks like that just call to items.map((item, index) slowing down everything.
My question is, is there a way to avoid looping through all items and change only the one that updated?
Note that other object data are not changed in this case, only items array within object.
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
object: null, // containing items array with some other data
// such as objectId, ...
};
}
componentDidMount() {
// call to server to retrieve object (response)
this.setState({object: response})
}
renderItems= (items) => {
return items.map((item, index) => {
return (
<Item key={item.id} item={item}/>
);
});
}
// this is called as a websocket onmessage callback
// data contains change item that should be replaced in items array
updateItems = data => {
// cloning object here in order to avoid mutation of its state
// the object does not contains functions and null values and cloning
// this way works in my case
let cloneObject = JSON.parse(JSON.stringify(this.state.object));
let index = // call to a function to get index needed
cloneObject.items[index] = data.change;
this.setState({object: cloneObject});
}
render() {
return (
this.state.object && {this.renderItems(this.state.object.items)}
);
}
}
First I would verify that your Item components are not re-rendering with a console.log(). I realize you have written that they don't in your description but I'm unconvinced the map loop is the total cause of the issue. It would be great if you posted your Component code because I'm curious if your render method is expensive for some reason as well.
The method you are currently using to clone your last state is a deep clone, it's not only slow but it will also cause each shallow prop compare to resolve true every time. (ie: lastProps !== newProps will always resolve true when using JSON.parse/stringify method)
To keep each item's data instance you can do something like this in your state update:
const index = state.items.findIndex(item => item._id === newItem._id);
const items = [
...state.items.slice(0, index),
newItem,
...state.items.slice(index + 1),
];
Doing this keeps all the other items intact, except for the one being updated.
Finally as per your question how to prevent this list re-rendering, this is possible.
I would do this by using moving the data storage out of state and into two redux reducers. Use one array reducer to track the _id of each item and an object reducer to track the actual item data.
Array structure:
['itemID', 'itemID'...]
Object structure:
{
[itemID]: {itemData},
[itemID]: {itemData},
...
}
Use the _id array to render the items, this will only re-render when the array of _ids is changed.
class List() {
...
render() {
return this.props.itemIds.map(_id => <Item id={_id} />);
}
}
Then use another container or better yet useSelector to have each item fetch its data from the state and re-render when it's data is changed.
function Item(props) {
const {id} = props;
const data = useSelector(state => state.items[id]);
...
}
You can try wrapping the child component with React.memo(). I had a similar problem with a huge form (over 50 controlled inputs). Every time I would've typed in an input all the form would've get re-rendered.
const Item = memo(
({ handleChange, value }) => {
return (
<>
<input name={el} onChange={handleChange} defaultValue={value} />
</>
);
},
(prevProps, nextProps) => {
return nextProps.values === prevProps.values;
}
Also, if you're passing through props a handler function as I did above, it's worth mentioning that you should wrap it inside a useCallback() hook to prevent recreation if the arguments to the function did not changed. Something like this:
const handleChange = useCallback(e => {
const { name, value } = e.target;
setValues(prevProps => {
const newProps = { ...prevProps, [name]: value };
return newProps;
});
}, []);
For your scenario I would recommend don't use state for your array rather create state for every individual element and update that accordingly. Something like this
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
individualObject: {}
};
}
object = response; // your data
renderItems= (items) => {
this.setState({
individualObject: {...this.state.individualObject, ...{[item.id]: item}
})
return items.map((item, index) => {
return (
<Item key={item.id} item={item}/>
);
});
}
updateItems = data => {
let cloneObject = {...this.object}
let index = // call to a function to get index needed
cloneObject.items[index] = data.change;
this.setState({
individualObject: {...this.state.individualObject, ...{[item.index]: item}
})
}
render() {
return (
this.renderItems(this.object)
);
}
}
I'm trying to update a prop value of a component in a list of components. Following is an example of it.
I'm developing an app using ReactNative
...
constructor(props) {
state = {
components: [*list of components*],
}
componentDidMount() {
fetchingAPI().then(response => {
const components = [];
for (const data of response.data) {
components.push(<MyComponent numOfLike={data.numOfLike} />);
}
this.setState({components});
});
}
render() {
return (
...
{this.state.components}
...
);
}
When I want to update a component, I update the whole state named components like :
updateAComponent(index, newNumOfLike) {
const components = this.state.components;
components[index] = <MyComponent numOfLike={newNumOfLike} />
this.setState({components});
}
But, this method change the component, not update. right? I means the components state is updated but MyComponent in components[index] is changed.
So, if I want to update the MyComponent in components[index] using the way of update the props numOfLike directly, how can I do it?
addition :
What I did not mention is that the MyComponent has a Image tag in it. So if I use FlatList or array.prototype.map there are several issues.
If I update the state, the whole list will be re-rendered. So if there are many list item, the speed of updating is very slow.
Since there are Image tag in the list, if I update a list item, the whole Image tags blink since the list items are re-rendered.
In this situation
Is there way to re-render(update) only a component which I want to update? (target updating)
If there in no way to target updating, just let the whole list items(components) re-rendered when just a component is updated?
You can use setNativeProps, described in the direct manipulation documentation
components[index].setNativeProps(propsObj)
You can modify your componentDidMount function like this (so that there are no race around or async conditions in the code) -:
componentDidMount() {
fetchingAPI().then(response => {
this.setState({
components: this.state.components.concat(
response.data.map(i => <MyComponent numOfLike={i.numOfLike} />)
)});
});
}
Can you try with the FlatList?
eg:
...
constructor(props) {
state = {
componentsData: [],
}
componentDidMount() {
fetchingAPI().then(response => {
this.setState({componentsData: response.data});
});
}
_renderItems = ({ item, index }) => {
return(
<MyComponent numOfLike={item. numOfLike} />
)
}
render() {
return (
...
<FlatList
data={this.state.componentsData}
renderItem={this._renderItems}
keyExtractor={(item, index) => index.toString()}
extraData={this.state}
/>
...
);
}
Then when you want to update the list,
updateAComponent(index, newNumOfLike) {
const data = this.state.componentsData;
data[index].numOfLike = newNumOfLike
this.setState({componentsData: data});
}
So I know that you can access a component's children with this.props.children:
<MyComponent>
<span>Bob</span>
<span>Sally</span>
</MyComponent>
Which is great if I'm interested in Bob and Sally, but what if I want to interact with the components that make up MyComponent (i.e. Subcomp1 and Subcomp2 shown below)?
render: function() {
return (
<div className="my-comp">
<Subcomp1 />
<Subcomp2 />
</div>
);
},
Use Case
I'm trying to create a higher order component that manages the tab index (roving tab index: https://www.w3.org/TR/wai-aria-practices/#kbd_roving_tabindex) of the wrapped component's sub-components, so it would be great if I could get a ref to the wrapped component and filter it's subcomponents by type.
So far the only approach that seems possible is to have each component store a ref for each of it's subcomponents, but this is tedious and kind of defeats the purpose of an HOC. Is there a generic way to access these sub-components?
A rough example of what I'm trying to do:
var HOC = (ComposedComponent) => {
return React.createClass({
componentDidMount: function() {
const subComponents = this.composedComponent.subComponents; // Something like this would be nice
const menuItems = subComponents.filter(() => {
// figure out a way to identify components of a certain type
});
this.applyRovingTabIndex(menuItems);
},
render: function() {
return (
<ComposedComponent
ref={(c) => { this.composedComponent = c }}
{...this.props} />
);
}
});
};
The tabIndex manipulation need not be done in the HOC, rather it can be done in the Parent component that renders all the HOCs. Because all you need is to determine which sub component is clicked and adjust the selected state on the Parent component. This selected state can then be propagated back to the sub components who compare their index with selected index and assign tabIndex accordingly.
You can send the respective props to determine whether the current ComposedComponent is selected or not by passing an onClick event handler all the way. Then in your sub component you can access tabIndex using this.props.tabIndex and render your parent div as
<div tabIndex={this.props.tabIndex}> </div>
The code below is almost like pseudo code to give an idea. If you feel that this does not solve your requirement you can try out a Tab example worked out by an awesome developer at this link CODEPEN EXAMPLE
const HOC = (ComposedComponent) => {
return class extends React.Component {
render (
<ComposedComponent
tabIndex={this.props.selected === this.props.index ? "0" : "-1"}
{...this.props}
/>
)
}
}
class Parent extends React.Component {
state = {
selected: 0
}
// Set the current selection based on the currentSelection argument
// that is bound to the function as it is sent along to Props
adjustTabIndices = (currentSelection) => (event) => {
this.setState({selection: currentSelection})
}
render {
return (
<div>
{
// These are your various MenuItem components that
// you want to compose using HOC
[MenuItem1, MenuItem2, MenuItem3].map(index => {
const MenuItem = HOC(MenuItem1);
return (
<MenuItem
key={index}
onClick={this.adjustTabIndices(index)}
selection={this.state.selected}
index={index}
/>
)
})
}
</div>
)
}
}
If I simply have:
const App = function() {
return (
<div>{this.renderList()}</div>
)
}
How do I define the renderList method?
I can't do const renderList = function() {} (nor with var or let). I can't do renderList() {}.
What's the right syntax?
I am hesitant to give a solution to this because inline Stateless Functions are not supposed to have methods. if you want a method you should use a Class and theres nothing wrong with using a class. Its all based on what you need to do. Stateless Functions are designed to be a super light weight way to render something that doesn't need methods, or a state or even a this context (in terms of a class).
you should be doing it like this.
class App extends Component {
constructor(){
super();
// note this is a Stateless Component because its a react class without a state defined.
}
renderList = () => {
return <span>Something Here</span>;
}
render() {
return <div>{this.renderList()}</div>
}
}
a HACK way that I wouldn't recommend (but does solve your question in the way you want it to) would be like this.
const App = () => {
let renderList = () => {
return <span>Something Here</span>
}
return <div>{renderList()}</div>
}
The reason why its generally a bad practice is because you are creating a function and all the memory allocation needed every render cycle. Subsequently, the internal diffing optimizations that react provides is generally useless if you do this because a new function gives a different signature than the prior render cycle. If this had a lot of children, they would all be forced to re-render!
Edit - React Version 16.8.0 +
You can use Hooks to do this. I would recommend using memo to memoize the function, this way you aren't creating it in memory each render cycle.
const RenderList = React.memo(props => (
<span>Something Here</span>
))
const App = function() {
const renderList = ()=> {
return "this variables"
}
return (
<div>{renderList()}</div>
)
}
You would want to do something like this
const App = function() {
return (
<div>{renderList()}</div>
)
}
function renderList(){
return "this variables"
}
Naturally this is a bad approach you its recommended that you pass in functions as props and stateless component are always dumb componets. Say if you are using redux for example you can have your component render like this
import {connect} from 'react-redux';
const App = (props) => {
return (
<div> {props.renderList} </div>
)
}
function renderList (){
return "your render logic"
}
export default connect(null, {renderList})(App)
Can you try something like
const App = () => {
return (
<div>{this.renderList()}</div>
)
}
App.renderList = () => {
return 'This is my list'
}
You can create render list function as standalone and use function parameter to pass props into function.
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 });
}
}