I'm making some buttons that will show a certain class depending on the status from the database.
I've passed my API results through an emitter and into my state using the below:
constructor(props) {
super(props);
this.state = {
tickets: [],
user: []
};
this.onGetAllTickets = this.onGetAllTickets.bind(this);
this.onGetCurrentUser = this.onGetCurrentUser.bind(this);
}
However, I'm having to set a new state in my button using the getInitialState() option. My button looks like this:
const AssignButton = React.createClass({
getInitialState() {
return {
isPressed: false,
completeStatus: "Assign"
};
},
handleClick() {
this.setState({
isPressed: true,
completeStatus: "Assigned",
});
},
render () {
var assignClass = 'ui orange right floated button assign';
//THIS IS WHERE I AM TRYING TO CHECK TICKET STATUS
if(this.state.tickets.status === 2) assignClass = 'ui green right floated button done disabled';
else if(this.state.isPressed) assignClass += ' disabled';
return <button className={assignClass} onClick={this.handleClick}>{this.state.completeStatus}</button>;
}
});
However, passing the this.state.tickets.status results in the following:
TicketeesBoard.js:123 Uncaught (in promise) TypeError: Cannot read property 'status' of undefined(…)
I'm assuming this is because my state is overwritten by the getInitialState() method.
The this.state.tickets.status etc. works outside of this AssignButton component.
How do I pass my tickets from the original state into the getInitialState() method?
The problem is that you're trying to access the state of a different component. Your'e addressing this.state.tickets.status where ticket state is not declared in AssignButton
You've got two components. TicketBoard & AssignButton.
Your setting the tickets state in TicketBoard and you're trying to access it in AssignButton
Try passing down the tickets state to the assignButton via props and the change your conditions from this.state.tickets.status to this.props.tickets.status.
In addition, make sure that you won't have problems with .status being called on an empty array...
Related
I'm stuck with calling my clickRemoveHandler. The idea is that I have two components: first - Layout that renders header, nav and footer components, second - calculator that is my core component with data input etc... In calculator component I have buttons with managed state, so when I click anywhere on Layout component (div), i need to call Calculator function that manipulates my buttons.
The code is below:
class Layout extends Component {
.....
clickHandler = (event) => {
Calculator.clickRemoveHandler(event);
console.log('Clikced')
};
.....
}
class Calculator extends Component {
state = {
currentServiceClass: null,
hoverIndex: null,
btnClicked: false,
selectedService: null
}
currentCursorPosition = {
el: null,
index: null,
rendered: false
}
static clickRemoveHandler = (event) => {
if ((!event.target.hasAttribute("type")) && (this.state.btnClicked)) {
this.currentCursorPosition = {
el: null,
index: null,
rendered: false
};
this.setState({currentServiceClass: null, hoverIndex: null, btnClicked: false})
}
}
....
}
There are a lot of logic in these components so they are too robust to post full code.
But the problem is that there is none of Calculator reference in Layout, calculator itself is rendered with Routing from another component, so I cannot pass any data from Layout to calculator directly.
What I want is to call static clickRemoveHandler from Layout. As I guess static is an option that make function global. So it works, but I got an error TypeError: undefined is not an object (evaluating 'Calculator.state.btnClicked'). As I see it means that when the clickRemoveHandler is called it is not associated with Calculator component, or doesn't have access to its state and props.
The question is how can I make it all work together ? Pass calculator state when calling function or is there another more elegant way to do it ?
I would suggest for the case you described (different components on different levels need access to some state and manipulate it) to use React context. You can take a look also on state managers like Redux or MobX, but in this particular case it will be overhead since your application is not so "huge". Basically you need to create some separate folder (you can call it context), inside it you should create context itself, export it and wrap in it you most up level component so that all the children will be able to use it.
You can find an example here: https://codesandbox.io/s/spring-glitter-0vzul.
Here is a link to documentation: https://reactjs.org/docs/context.html
I can provide you some more details if you need
That was a challenge, but I've done it!
Layout component:
state = {
firstMount: false,
clicked: false,
clickedEvt: null
};
clickHandler = (event) => {
console.log('Clikced')
if (this.state.clickedEvt)
this.setState({clicked: false, clickedEvt: null});
else
this.setState({clicked: true, clickedEvt: event.target}, ()=>{setTimeout(() =>
this.setState({clicked: false, clickedEvt: null})
, 50)})
};
<LayoutContext.Provider value={{
clicked: this.state.clicked,
clickedEvt: this.state.clickedEvt,
handleClick: this.clickHandler
}}>
render() {
return(
<div onClick={(event) => this.clickHandler(event)} className="web">
First I call handleClick as onclick event from Layout component, then it is called again from calculator
componentDidUpdate() {
if (this.context.clicked) {
this.clickRemoveHandler(this.context.clickedEvt)
}
}
I have two component
Read Mode and Pagination
Read Mode Component
state = {
currentPdf:[],
currentPage: null,
totalPages: null,
intialState:1,
};
constructor(props) {
super(props);
this.onChangePage = this.onChangePage.bind(this);
this.onCurrentPageNo = this.onCurrentPageNo.bind(this);
this.onTotalPage = this.onTotalPage.bind(this);
}
componentDidMount() {
this.props.fetchPdfContent();
}
onCurrentPageNo(currentPageNo){
this.setState({ currentPage: currentPageNo });
}
onChangePage(currentPdf) {
this.setState({ currentPdf: currentPdf });
}
onTotalPage(totalpages){
this.setState({ totalPages: totalpages });
}
gotoPrevPage = (currentTarget) => {
if (this.state.currentPage > 1) {
let prevPage = this.state.currentPage - 1;
this.setState({ intialState: prevPage });
}
}
render() {
return (
<button className="btn btn-primary prev-btn pageBtn" onClick={this.gotoPrevPage.bind(this)}>
<span aria-hidden="true" className="icon-ico_arrow-right icomoon"> Left </span>
</button>
<Pagination initialPage={intialState} items={pdfcontnet} onTotalPage={this.onTotalPage} onChangePage={this.onChangePage} onChangePageNo={this.onCurrentPageNo} />
)
}
Pagination Component
constructor(props) {
super(props);
this.state = { pager: {} };
}
componentDidMount() {
// set page if items array isn't empty
if (this.props.items && this.props.items.length) {
this.setPage(this.props.initialPage);
}
}
componentDidUpdate(prevProps, prevState) {
// reset page if items array has changed
this.setPage(this.props.initialPage);
}
setPage(page) {
console.log(page + 'pages');
var items = this.props.items;
var pager = this.state.pager;
if (page < 1 || page > pager.totalPages) {
return;
}
// get new pager object for specified page
pager = this.getPager(items.length, page);
// get new page of items from items array
var pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);
// update state
this.setState({ pager: pager });
// call change page function in parent component
this.props.onChangePage(pageOfItems);
}
when i click the gotoPrevPage () initalState value need to pass
this.setPage(this.props.initialPage);
if i assign componentDidUpdate() state I got
You need to change initialPage={intialState} to initialPage={this.state.intialState} in your Read Mode Component render method.
PS: You should actually spell it initial, not intial.
try this:
onClick={()=>this.gotoPrevPage.bind(this)}
Your state object should be inside your constructor function in ReadMode. You should also be using this.setState({}) to update state. You also shouldn't be trying to reload the entire DOM when a new item is added the way you are in the Pagination component via commponentDidUpdate. React uses a virtual DOM and doesn't need to reload every element on the page every time one element is updated. It looks like part of your problem is your App continuously updates and you're getting stuck in a never ending loop updating the DOM.
Also, you might not need Pagination to have local state. You could store the state/data in the parent container and just pass it to Pagination via props. React uses one-way data flow and you should familiarize yourself with passing props from a parent component to a child component.
Brush up on state and lifecycle functions by reading the documentation, get an understanding of how React uses the virutal DOM to update elements, and rewrite your App so that state is stored mainly in the parent component and is passed via props to the child component.
Overlooking basic React concepts and structuring your project in a less than ideal way is the source of your problems.
Currently I get my data from an API in a JSON-format when running my saga. The fetching process begins, when the component did mount. That means the component renders two times.
Now, when the data is available as props. I can use it in order to render it.
My approach to this is like following, I have got a:
Constructor with the initial state
I fetch data in "componentDidMount"
I got a function that takes the JSON properties from props and puts it into new variables
I run this function in my render() function, when the props contain the fetched data
The Problem in this approach: Once the component runs the function where the data becomes "structured", the render-function loops and then after some time, the values of the properties get displayed with a warning message in the console.
My Questions:
How to prevent the looping when render() runs once?
How can I design this, so that particular properties of the fetched object merge into a new object and how to
I hope I described the most important things about my issue. Here is the code:
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
deviceInfo: {
name: "Initial Name",
batLevel: "78%",
}
}
}
componentDidMount() {
this.props.requestApiData();
}
updateDeviceInfoWithState (){
const devices = (this.props.data.data);
if(devices){
const newDeviceInfo = this.state.deviceInfo;
newDeviceInfo.name = devices[0].shadow.desired.payload.refAppData.name;
newDeviceInfo.batLevel = devices[0].shadow.reported.payload.refAppData.batteryState.level;
this.setState({
deviceInfo: newDeviceInfo,
});
}
}
render() {
this.updateDeviceInfoWithState()
return (
<div className='container'>
<p> {this.state.deviceInfo.name} </p>
<p> {this.state.deviceInfo.batLevel} </p>
</div>
)
}...
Updating the state in the render method is not a good practice, since it might cause an infinite loop.
In your case state is redundant, since you only take the data from props, or replace it with defaults. Instead of using the state return the name and batLevel in the updateDeviceInfoWithState method, and use it in the render method.
Example (not tested):
class Dashboard extends React.Component {
componentDidMount() {
this.props.requestApiData();
}
updateDeviceInfoWithState (){
const devices = this.props.data.data;
if(devices){
const device = devices[0].shadow;
return {
name: device.desired.payload.refAppData.name,
batLevel: device.reported.payload.refAppData.batteryState.level
};
}
return {
name: "Initial Name",
batLevel: "78%",
};
}
render() {
const { name, batLevel } = this.updateDeviceInfoWithState();
return (
<div className='container'>
<p> {name} </p>
<p> {batLevel} </p>
</div>
);
}...
Note 1: If you want to decouple your component from the state, it's better to enforce simple properties as input for the data. For example, this component needs as properties the name and batLevel. It doesn't need to be aware of the array of devices, shadow, payload, etc... You can prepare the data when you receive it in the saga, or use a redux selector in mapStateToProps.
Note 2: If you really need the data in your state, you can use the getDerivedStateFromProps life-cycle method (React 16.3), or update the state in the componentWillReceiveProps if you use an older version.
For this case you can use ComponentWillRecieveProps method like this
componentWillRecieveProps(nextProps) {
// Condition as per ur requirement.
If(this.props.data != nextProps.data) {
this.updateDeviceInfoWithState(nextProps)
}
}
This method will only run whenever ur component props are changed.
I am new to react and while going through the tutorials I found this ,
"The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it's invoked, and it does not directly interact with the browser." - https://facebook.github.io/react/docs/react-component.html#reference
I am little confused with this. If render function should return same result each time how can I modify the display based on states ?
For example I have text with edit button. When I click on edit, text-box should appear with content of text and edit button changes to save.
"The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it's invoked, and it does not directly interact with the browser." - https://facebook.github.io/react/docs/react-component.html#reference
is absolutely a correct statement in react. Changing a state cannot be done can't inside a render method. You can access the state inside the render but change. To change the state we use setState function in callback method which set the new state and ready to change anywhere in your component.
Note: setState expects the state to be initialized first. getInitialState is the function for the same and given in example below
eg.
var firstName = 'Anand';
var testExample = React.createClass({
getDefaultProps: function () {
return {
name: 'React',
message: 'This is the default message!'
};
},
getInitialState: function () {
return {
name: this.props.name
};
},
handleNewName: function (name) {
this.setState({
name: name
});
},
render: function () {
var name = this.state.name;
var message = this.props.message;
return (
<div>
<h1>{message}</h1>
<h2>{name}</h2>
</div>
);
}
});
Inside the handleNewName function we have changed the state which then used inside render. Hope this helps
What I'm trying to achieve: Pass data from child to parent.
How I'm trying to achieve it: Using this.state as described here
Not sure how to word the title: When I console.log(this.state) in the function in which I modify the state, the correct values in this.state are printed out. However, when I try to read the state in another function, this.state is apparently still at empty values.
constructor(props) {
super(props);
this.state = {
titleInputValue: "",
};
}
<form onSubmit={this.handleSubmit.bind(this)}>
<TextInput
val={this.state.titleInputValue}
changeHandler={this.textInputChangeHandler} />
</form>
// This is the function which can apparently only get the initial state
// title and body are empty strings, they should have values
handleSubmit(event) {
event.preventDefault();
const title = this.state.titleInputValue;
const body = this.state.bodyInputValue;
console.log(this.state);
Meteor.call('stories.insert',title,body);
}
// However, here the state is retrieved just fine.
// This is the function the child calls to update the state.
textInputChangeHandler(event) {
// This console.log call shows the correct state!
console.log(this.state);
this.setState({
titleInputValue: event.target.value,
});
}
TextInput has attribute onChange={this.props.changeHandler.bind(this)}
For illustration:
I wrote asd, and the state was successfully retrieved in textInputChangeHandler, which is the first two console.log calls, but then it's empty in handleSubmit.
This is because the event handler scope is not Component class level. When your component handles the event, it's context is the component (in your case TextInput ) not the parent.
You have to bind that function to this of Component class scope:
<TextInput
val={this.state.titleInputValue}
changeHandler={this.textInputChangeHandler.bind(this) } />
Using JavaScript bind you can specify the function context as well.