New to both react and javascript and was wondering how do we re-render the component?
I need to make sure that the details (E.g. badge counter) in button component re-render everytime we get a change in facets.
I see some older attempts by other developers to set state but it doesn't seem to be working so i need some guidance on the following.
How to detect when there is a change in facets?
How do we re-render the component everytime we get a change in facets?
class SearchResult extends React.Component {
constructor(props) {
super(props);
this.state = {
_query: props.query,
_facets: props.facets,
_hasLoaded: false,
};
}
render() {
const {
onEntityClick, facets, entity, total, pagingTotal, isFilterSubmitted, loading, query,
} = this.props;
const {
_query, _facets, _hasLoaded,
} = this.state;
if ((_query !== query && query !== '*') && !loading) {
this.setState({
_query: query,
_facets: facets,
});
}
if ((_query === query && query === '*' && !_hasLoaded) && !loading) {
this.setState({
_query: query,
_facets: facets,
_hasLoaded: true,
});
}
return (
<Fragment>
<ButtonComponent
btnTheme="gray"
size="small"
label="Note"
icon="note"
// eslint-disable-next-line no-nested-ternary
badgeCounter={!loading ? entity === 'note' && isFilterSubmitted ? pagingTotal : _facets.note : 0}
disabled={entity === 'note'}
callbackFunc={() => onEntityClick('note')}
/>
</Fragment>
);
}
}
You're making an antipattern in your code:
this.setState() should never be called inside the render() function.
Instead, what you would like to do is check componentDidUpdate():
componentDidUpdate(prevProps) {
if (prevProps.query !== props.query && query !== "*") {
...
}
}
This guarantees a re-rendered so you don't need to worry about "manually" re-rendering.
Also note that props.query as well as other values, don't need to be saved in the component state unless you need a modified copy of it -- that's what didUpdate will do.
You can try componentWillReceiveProps LC method, if there is a change in props and set the state(re-render) of the component acc. to that change.
UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
console.log(nextProps.facets);
console.log(this.state._facets);
if (nextProps.facets !== this.state._facets) {
this.setState({
_facets: 'YES'
})
}
}
Related
i have a select menu with defaultValue is null
when i pass props to it , it dosent rerender with the new props as defaultValues
ps : the select is multi
i tried to use component will recieve props and everything that i find but still dosent work
this is my select component :
import React, { useState, useEffect } from "react";
import Select from "react-select";
class SelectMenu extends React.Component {
state = {
defaultValues: [],
};
componentWillReceiveProps(newProps) {
this.setState({ defaultValues: newProps.defaultValue });
}
render() {
return (
<Select
options={this.props.options}
closeMenuOnSelect={this.props.closeMenuOnSelect}
components={this.props.components}
isMulti={this.props.isMulti}
onChange={(e) => this.props.onChange(e, this.props.nameOnState)}
placeholder={this.props.default}
defaultValue={this.state.defaultValues}
/>
);
}
}
export default SelectMenu;
componentWillReceiveProps won't be called during mounting.
React doesn’t call UNSAFE_componentWillReceiveProps() with initial props during mounting. It only calls this method if some of component’s props may update. (https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops)
Also, componentWillReceiveProps is deprecated and will be removed in React 17. Take a look at getDerivedStateFromProps instead, and especially the notes on when you do not need it.
I beleive that in your case using the constructor will be perfectly fine, something like:
class Components extends React.Component {
constructor(props) {
super(props)
this.state = { some_property: props.defaultValue }
}
}
i find a solution for this problem
by using components will recieve props
and setting my state with the comming props
and in the render you need to do condition to render the select menu only if the state.length !== 0
i posted this answer just in case someone face the same problem i know its not the most optimal solution but it works for me
sorry for the previous solution but its not optimal i find a way to make it work
so instead of defaultvalues
you have to make its as value props
and if you want to catch the deleted and added values to your default
this function will help you alot
onChange = (e) => {
if (e === null) {
e = [];
}
this.setState({
equipments: e,
});
let added = e.filter((elm) => !this.state.equipments.includes(elm));
if (added[0]) {
let data = this.state.deletedEquipments.filter(
(elm) => elm !== added[0].label
);
this.setState({
deletedEquipments: data,
});
}
let Equipments = e.map((elm) => elm.label);
let newEquipments = Equipments.filter(
(elm) => !this.state.fixed.includes(elm)
);
this.setState({
newEquipments: newEquipments,
});
let difference = this.state.equipments.filter((elm) => !e.includes(elm));
if (difference.length !== 0) {
if (
!this.state.deletedEquipments.includes(difference[0].label) &&
this.state.fixed.includes(difference[0].label)
) {
this.setState({
deletedEquipments: [
...this.state.deletedEquipments,
difference[0].label,
],
});
}
}
};
constructor(props) {
super(props);
this.state = {
equipments: [],
newEquipments: [],
deletedEquipments: [],
};
}
class Select extends React.PureComponent {
constructor(props) {
super(props)
this.state = { value: this.props.defaultValue }
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
e.persist()
if (typeof this.props.onDataChange !== 'undefined') {
this.setState({ value: e.target.value }, () => this.props.onDataChange(e))
} else {
this.setState({ value: e.target.value })
}
}
render() {
const { options } = this.props
return (
<div>
<select
value={this.state.value}
onChange={this.handleChange}
>
{options.map((option, i) => {
const value = option.value || option.path || null
const label = option.label || option.name || option
return (
<option key={`option-${i}`} value={value}>
{label}
</option>
)
})}
</select>
</div>
)
}
}
class Display extends React.PureComponent {
constructor(props) {
super(props)
}
async getSomeValues() {
try {
this.setState({ isReady: false })
await Axios.get(`some-values`)
.then(async (result) => {
this.setState({
values: result.data.values,
default: result.data.default
})
})
} catch (error) {
console.log(error)
} finally {
this.setState({ isReady: true })
}
}
componentDidMount() {
this.getSomeValues()
}
render() {
return (
<Select
options={this.state.values}
defaultValue = {this.state.defaultValue}
/>
)
}
}
I'm trying to solve what i believe to be a pretty simple problem. I have a parent component that houses a child component which is rending a select dropdown.
My parent makes a call to an API service and pulls back a list of items that are to be displayed in the select dropdown. My API returns the set of options to be displayed and an initial value to be selected on load.
The initial render takes the defaultValue property and sets the state to be displayed in the initial instance in the select component constructor, the problem i have with this, is that the api call is done after the initial render so the default value always comes out being null.
I need a mechanism to set the value of the select dropdown to an initial value on load but it has to be done as a result of the api call that happens once the component has loaded?
What is the cleanest way to set the state value to whatever is returned from the API on initial load?
I'm sure this must be an easy problem to solve but i keep getting stuck between what i want to do and the load / render patterns in react.
Any help would be appreciated.
I see two options:
Option 1
You can prevent the rendering of your Select component until the request is finished. This will mean your constructor will fire after you have the data and will be initialized correctly.
render() {
if (this.state.defaultValue) {
return (
<Select
options={this.state.values}
defaultValue={this.state.defaultValue}
/>
)
} else {
return null; // or loading graphic
}
}
Option 2
In your Select component, use a lifecycle method like componentDidUpdate to check if the defaultValue prop has changed from the last render. If so, set the default value in state. This will make it so that defaultValue only gets set once.
componentDidUpdate(prevProps) {
if (this.props.defaultValue !== prevProps.defaultValue) {
this.setState({ defaultValue: this.props.defaultValue });
}
}
I click Item -> I get data from url:https: // app / api / v1 / asset / $ {id}. The data is saved in loadItemId. I am moving loadItemId from the component Items to the component Details, then to the component AnotherItem.
Each time I click Item the props loadItemId changes in the getDerivedStateFromProps method. Problem: I'll click Element D -> I see in console.log 'true', then I'll click Element E --> It display in console.log true andfalse simultaneously, and it should display only false.
Trying to create a ternary operator {this.state.itemX ['completed'] ? this.start () : ''}. If {this.state.itemX ['completed'] call the function this.start ()
Code here: stackblitz
Picture: https://imgur.com/a/OBxMKCd
Items
class Items extends Component {
constructor (props) {
super(props);
this.state = {
itemId: null,
loadItemId: ''
}
}
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://app/api/v1/asset/${id}`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
this.setState({
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render () {
return (
<div >
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
>
<Details
loadItemId={this.state.loadTime}
/>
</div>
)
}
Item
class Item extends Component {
render () {
return (
<div onClick={() => this.props.selectItem(item.id}>
</div>
)
}
}
Details
class Details extends Component {
constructor() {
super();
}
render () {
return (
<div>
<AnotherItem
loadItemId = {this.props.loadItemId}
/>
</div>
)
}
}
AnotherItem
class AnotherItem extends Component {
constructor() {
super();
this.state = {
itemX: ''
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.loadItemId !== prevState.loadItemId) {
return { itemX: nextProps.loadItemId }
}
render () {
console.log(this.state.itemX ? this.state.itemX['completed'] : '');
{/*if this.state.loadX['completed'] === true, call function this.start()*/ }
return (
<button /*{this.state.loadX['completed'] ? this.start() : ''}*/ onClick={this.start}>
Start
</button>
);
}
}
here:
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
you call setState(), then 'Item' and 'Details' and 'AnotherItem' call their render method. so you see log for previous 'loadItemId'.
when 'load' method work done. here:
this.setState({
loadItemId: response.data
});
you setState() again, then 'Item' and 'Details' and 'AnotherItem' call their render method again. in this time you see log for new 'loadItemId'.
solution
setState both state in one place. after load method done, instead of:
this.setState({
loadItemId: response.data
});
write:
this.setState({
itemId: id,
loadItemId: response.data
});
and remove:
this.setState({
itemId: id
})
from 'selectItem' method.
Need some clarification, but think I can still address this at high level. As suggested in comment above, with the information presented, it does not seem that your component AnotherItem actually needs to maintain state to determine the correct time at which to invoke start() method (although it may need to be stateful for other reasons, as noted below).
It appears the functionality you are trying to achieve (invoke start method at particular time) can be completed solely with a comparison of old/new props by the componentDidUpdate lifecycle method. As provided by the React docs, getDerivedStateFromProps is actually reserved for a few 'rare' cases, none of which I believe are present here. Rather, it seems that you want to call a certain method, perhaps perform some calculation, when new props are received and meet a certain condition (e.g., not equal to old props). That can be achieved by hooking into componentDidUpdate.
class AnotherItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
start = () => { do something, perform a calculation }
// Invoked when new props are passed
componentDidUpdate(prevProps) {
// Test condition to determine whether to call start() method based on new props,
// (can add other conditionals limit number of calls to start, e.g.,
// compare other properties of loadItemId from prevProps and this.props) .
if (this.props.loadItemId && this.props.loadItemId.completed === true) {
//Possibly store result from start() in state if needed
const result = this.start();
}
}
}
render () {
// Render UI, maybe based on updated state/result of start method if
// needed
);
}
}
You are encountering this behaviour because you are changing state of Items component on each click with
this.setState({
itemId: id
})
When changing its state, Items component rerenders causing AnotherItem to rerender (because that is child component) with it's previous state which has completed as true (since you've clicked element D before). Then async request completes and another rerender is caused with
this.setState({
loadItemId: response.data
});
which initiates another AnotherItem rerender and expected result which is false.
Try removing state change in selectItem and you'll get desired result.
I'd suggest you read this article and try to structure your code differently.
EDIT
You can easily fix this with adding loader to your component:
selectItem = (id) => {
this.setState({
itemId: id,
loading: true
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://jsonplaceholder.typicode.com/todos/${id}`,
method: "GET"
})
.then(response => {
this.setState({
loading: false,
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render() {
return (
<div >
<ul>
{this.state.items.map((item, index) =>
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
/>
)
}
</ul>
{this.state.loading ? <span>Loading...</span> : <Details
itemId={this.state.itemId}
loadItemId={this.state.loadItemId}
/>}
</div>
)
}
This way, you'll rerender your Details component only when you have data fetched and no unnecessary rerenders will occur.
Hi am using reactquill in my child component and i want to update my parent state when users type. currently i am doing it using onBlur() but that is not what the users want.
this is my child component.
public componentWillReceiveProps(newProps): void {
//console.log(newProps, "new props");
this.setState({
text: newProps.value
});
}
public setProps() {
//console.log("set props", this.state.text);
if(this.state.text === "<p><br></p>"){
this.props.onChange("");
} else {
this.props.onChange(this.state.text);
}
}
public handleChange(value) {
this.setState({ text: value });
//console.log("update props of parent", value);
//this.props.onChange(value);
}
public render() {
return (
<div>
<div className="text-editor" onBlur= {this.setProps}>
<ReactQuill value={this.state.text}
onChange={this.handleChange}
//onKeyPress={this.handleKeyDown}
//onKeyDown={this.handleKeyDown}
onBlur= {this.setProps}
modules={this.modules}
formats={this.formats}/>
</div>
</div>
);
}
and this i from my Parent Component calling the child;
public renderEditableAnswer = (cellInfo) => {
return (
<div>
<QnAAnswerInput
value={cellInfo.original.Answer}
onChange={data => this.updateQnAAnswer(data, cellInfo)}
/>
</div>
);
}
public updateQnAAnswer = (data, cellInfo) => {
let qnaItems = [...this.state.qnaItems];
let index;
if(cellInfo.original.Id != null){
index = _.findIndex(qnaItems,d => d.Id == cellInfo.original.Id);
} else {
index = _.findIndex(qnaItems,d => d.identifier == cellInfo.original.identifier);
}
if(this.getText(data) !== this.getText(cellInfo.original.Answer)){
let item = {
...qnaItems[index],
Answer: data,
};
qnaItems[index] = item;
this.setState({ qnaItems });
this.updateActionHistory(item,index);
}
}
this component is inside a ReactTable cell, hence the cellInfo. Note that i do have one functionality in the parent component that would add a new row to the table which needs to have an empty values for the child component. i noticed that without the WillReceiveProps method, my "Add New Empty Row" is not working.
In my current code, if i comment out the this.props.onChange(this.state.text); inside the handleChange method, typing inside the editor fires the componentWillReceiveProps (iterating through all my reacttable values, which is a lot) which renders a delay in typing a text. and this is not good.
is there anyway for me to update my parent state with onChange without having typing delays?
Use only componentDidMount() and componentDidUpdate() the other life cycle methods are bad practice.
You have a typing delay because of componentWillReceiveProps, never use it. I do not understand your code, there are no names and you have unnecessary code.
Instead of onBlur= {this.setProps} in div ,
call it in componentDidUpdate
componentDidUpdate = ( prevProps , prevState) =>{
if(prevState.editorHtml !== this.state.editorHtml )
this.setProps()
}
Do you have any better solution?
I have a react component with a method that reads some data from an object and then returns the text on its status on another component.
The problem is that when the application is loading at first the obj length will be 0 initially and when the object data is loaded it will then
update and the obj.length will be higher than 0.
The what happens is this:
Scenario 1 (obj will actually have data)
-- App Loading
-- getTxt checks if obj is empty
-- As data is not loaded yet on the first call obj.length will return 0 so 'No Data Found' is displayed
-- Then when the app finishes loading it then updates and the obj.length > 0 so 'Found the data' is displayed
or
Scenario 2 (obj will be empty)
-- App Loading
-- getTxt checks if obj is empty
-- As data is not loaded yet on the first call obj.length will return 0 so 'No Data Found' is displayed
-- Then when the app finishes loading it then updates and the obj is actually empty so it can stay the same.
My problem is that if after the app is loaded and obj.length re-checked it returns > then 0 then I don't want to display the first 'No Data Found',
but I need to have the condition just in case after the app has finished loading the data the data is still = 0
Here is the code:
import React from 'react'
class MyComponent extends React.Component {
getTxt() {
if (this.props.obj.length > 0) {
return 'Found the data';
} else if (this.props.obj.length == 0) {
return 'No Data Found';
}
return 'Searching Data'
}
render() {
return <SomeComponent text={this.getTxt()}/>
}
}
export {MyComponent}
What can I do in order to get this done?
As stated in the comments by Felix Kling.
You have three different states.
Initial state
No data
There is some data
Initial state will have the data props set to null. After the data was received it could be an empty object {} or an object with some information.
I would probably write this in the following way:
class MyComponent extends React.Component {
getStatus() {
const { data } = this.props;
if (data === null) {
return "Searching data"
}
if (Object.keys(data).length === 0) {
return "No data was found"
}
return "Found some data"
}
render() {
return <SomeComponent text={this.getStaus()}/>
}
}
This is almost perfect because I would like to separate logic from the view. I will have a reducer that will get the data and upon its values/length it will determine the status.
const statusReducer = (data) => {
if (data === null) {
return "Searching data"
}
if (Object.keys(data).length === 0) {
return "No data was found"
}
return "Found some data"
}
class MyComponent extends React.Component {
state = { data: null }
componentDidMount() {
getData()
.then(resp => resp.json)
.then(resp => this.setState({data: resp.data})
}
render() {
return <SomeComponent text={statusReducer(this.state.data)} />
}
}
You may ask what's the point of passing this to another function (reducer)? First, as I mentioned, separating the logic from the view. Second, statusReducer can be reused in other components. Third, easier to test the code.
NOTE: We only took care of the happy path. However, if there was a problem with the request or the response contains error, we probably get the wrong status.
To handle such case lets look at a different approach. Lets have data and status in state
class MyComponent extends React.Component {
state = { data: null, status: 'PENDING' }
componentDidMount() {
getData()
.then(resp => resp.json)
.then(resp => this.setState({data: resp.data, status: 'COMPLETE'})
.catch(err => this.setState(data: err.Message, status: 'ERROR'})
}
render() {
switch(this.state.status) {
case 'COMPLETE':
return <SomeComponent text={statusReducer(this.state.data)} />
case 'ERROR':
return <ErrorComponent message={this.state.data} />
case 'PENDING':
return <Spinner />
default:
// there is a runtime/compiling error.
// Notify it in you console.log or something
return null // Here I am silently failing but that's not a good practice
}
}
}
We could do even better if we move the switch-case statement to another function and call it loadReducer. But I will let you decide whether to do it or not.
Please notice Eitan's solution too. He uses both componentDidMount and componentWillUpdate to update status.
It sounds like you don't want to render anything until the data is received?
What I do when I need to do this is use component state to keep track of loading status. When the component mounts, set state.loading to true.
componentDidMount() {
this.setState({loading: true})
}
Set loading to false once everything is updated.
componentDidUpdate() {
if (this.state.loading) {
this.setState({loading: false})
}
}
And in the render function, conditionally render based on loading state.
render() {
const text = this.state.loading ? "Loading..." : this.getTxt()
return <SomeComponent text={text}/>
}
I agree with Felix in the comments. You may be better off with a nested ternary in a functional component. The component should update whenever props to it obj are updated.
export const myComponent = (props) => {
return (
<SomeComponent
text={ props.obj ?
props.obj.length > 0 ?
'Found Data'
:
'No Data Found'
:
'Searching'
}
/>
)
}