I want to have a component level variable as constant in ReactJS, Redux with Typescript.
export class MyComponent {
initialProps = {};
constructor() {
}
componentWillMount() {
this.initialProps = this.props; //Early tried without this. But this.props is getting changed because of the filter obj.name in the below function.
}
render() {
return(
<MyPanel>
//Another <select> widget
{this.getCustomObject()}
</MyPanel>
);
}
getCustomObject() {
if(this.props.selectedId == 2) {
let filteredTypes = [];
this.initialProps.obj.forEach((o) => {
if(this.props.obj.name !== 'ABC'){
filteredTypes.push(this.props.obj);
}
});
this.props.types = filteredTypes;
}
return this.props;
}
Note: Actually i want to do re-render the component and display the filtered object based on the dropdown selection. But once we filtered. Next time when we change the dropdown value we are getting the filtered the value in the props.
But we need the props which was passed first time to this componennt and filter the values everytime. SO tried with this.initialProps. But that also getting changed once this.props is updated.
Related
I'm trying to conditionally make an option of a select disabled, for example, if two items are selected, the the third option is diabled, if one or three items are selected, the third option is enabled. However the Select option does not change when selected number of items change even it's passed as props of the option. When I console.log, I do see the option object disabled value changed when I change selected items numbers. May I know why the Select does not re render? Thanks!
class parentComponent extends PureComponent{
constructor(props) {
super(props)
this.state = {
options:[]
}
}
render() {
const {
countOfItems
} = this.props
let options = [...somefunction()];
if (somecondition1) {
options.options.filter(...)
}
if (somecondition2) {
options.options.filter(...)
}
this.setState({options})
......
if (countOfItems === 2) {
options[3].disabled = true
} else {
options[3].disabled = false
}
console.log(options[3])
......
return (
<Select
options ={options}
isOptionDisabled = {(option) => option.disabled}
......
)
}
}
React component render their UI/DOM after DOM diffing which causes the component to make a new reference of return statement and render updated changes to UI. which is triggered by useSTATE hook. If you are not updating state. No dom diffing occur. So you'll have the old reference of Dom.
You can do it this way
class Options {
this.state={options:[]}
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.countOfItems !== prevProps.countOfItems) {
let tempOptions = [...this.state.options]
if (this.props.countOfItems === 2) {
tempOptions[3].disabled = true
} else {
tempOptions[3].disabled = false
}
this.setState({options:tempOptions})
}
}
render() {
......
return (
<Select
options ={this.state.options}
isOptionDisabled = {(option) => option.disabled}
......
)
}
I am working on React Js in class component I am declaring some states and then getting data from API and changing that state to new value but React is not rendering that new value of state. but if I console.log() that state it gives me new value on console.
My code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState({ unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}
This is printing 2 on console but rendering 0 on screen. How can I get updated state(2) to render on screen.
and if I visit another page and then return to this page then it is rendering new value of state (2).
Please call getUnread() function in componentDidMount, something like this
componentDidMount() {
this.getUnread()
}
This is because in React class components, while calling setState you it is safer to not directly pass a value to set the state (and hence, re-render the component). This is because what happens that the state is set as commanded, but when the component is rerendered, once again the state is set back to initial value and that is what gets rendered
You can read this issue and its solution as given in react docs
You pass a function that sets the value.
So, code for setState would be
this.setState((state) => { unread: data.count });
Hence, your updated code would be :
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState((state) => { unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}
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)
);
}
}
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 !
I have a local state of currentMenu in component of MenuItemContainer
export default class MenuItemsContainer extends React.Component {
constructor () {
super();
this.state = {
currentMenu: [],
};
}
I render menu_items by using function_renderMenuItem like below,
_renderMenuItems(menuitems) {
const { order } = this.props;
return menuitems.map((menuitem) => {
if (menuitem.category_id == this.props.order.currentCategoryId) {
this.state.currentMenu.push(menuitem)
else {
return false;
}
return <MenuItem
key={menuitem.id}
order={order}
dispatch={this.props.dispatch}
channel={this.props.order.channel}
{...menuitem} />;
});
}
What I want to do with currentMenu is that storing menuItems which category_id of menuItem equals to currentCategoryId of order state.
Now I am using push(menuitem) to push those items to the state. However, in currentMenu, it should store only if when category_id of menuItem is equal to currentCategoryId of orders state. So when there are changes of currentCategoryId, it should reset currentMenu and get new menuItem
How can I do this?
Thanks in advance
In order to actually trigger the state change you need to do this via setState
so you can add a setState command at the end of your _renderMenuItems method like so:
this.setState({currentMenu: state.currentMenu})
But the better practice is to have the state already containing the right items and not filter it in the render method.
Im not sure from your example how you are getting the menuItems, but in theory you should build the state from them and then call the _renderMenuItems method (that will use the state)