undefined in variable using react componentDidMount - javascript

I have no clue why this.role is undefined in render.
export default class Dashboard extends Component {
componentDidMount() {
this.role = window.localStorage.getItem('role')
console.log('role', this.role) //return admin
}
render(){
console.log('role', this.role) //return undefined
return(
<div>
Component
</div>
)
}
}
I checked the localStorage of my app and it has value.

what happens is that at the initial render, render() method is called (before componentDidMount() is called), so it shows 'undefined'.
changing the value of 'this.role' won't re-render the page.
You will have to use state for this.
Below code should work I believe.
export default class Dashboard extends Component {
constructor(){
super()
this.state = {
role : undefined
}
}
componentDidMount() {
this.setState({role: window.localStorage.getItem('role')})
console.log('role', this.role) //return admin
}
render(){
console.log('role', this.state.role) //return undefined
return(
<div>
Component
</div>
)
}
}

It's returning undefined because you're setting this.role after the component is mount (componentDidMount). So the first render doesn't have this.role.
After componentDidMount is run you're not changing the state and the render is not running again (and therefore not getting the new info).
Try with componentWillMount instead, it should probably work.
Here's the React Lifecycle documentation.
Edit: Added code.
export default class Dashboard extends Component {
componentWillMount() {
this.role = window.localStorage.getItem('role')
console.log('role', this.role) // return admin
}
render(){
console.log('role', this.role) // now returning admin because this.role is set before the 1st render
return(
<div>
Component
</div>
)
}
}
As other users have pointed out, you can also use setState instead and it would also work (In that case, when the state changes the render is run again and your role is displayed accordingly).

You see undefined in the view because by the time the component has rendered there was nothing in role because componentDidMount is called after the initial render. Moreover, the component doesn't rerender after you have set role value from localStorage because it is not on the state. If you place role on the state and do this:
componentDidMount() {
this.setState({ role: window.localStorage.getItem('role')});
}
render(){
console.log('role', this.state.role)
return(
<div>
Component
</div>
)
}
then you will be able to see value of role in the view, but it will cause extra rerender of the component since you will change its state, according to react docs about componentDidMount:
Calling setState() in this method will trigger an extra rendering, but
it will happen before the browser updates the screen.
You can read more about componentDidMount here.
Update:
In your case you don't have to put role on the state, but then you can fetch its value from the constructor:
constructor(props) {
super(props);
this.role = window.localStorage.getItem('role');
}
and it will be available in the view.

Related

passing state value to a child component via props

i'm trying to pass the value entered by the user from the app component to the passTicket component. I tried invoking props to pass this state data but I keep getting an undefined error when attempting to access it. I'm new to react and it would be great if someone can help me make sense of what i'm getting wrong. This is a sample of what i'm trying to achieve.
This is my main component:
class App extends Component {
constructor(props){
super(props);
this.state = {
ticket:"",
};
this.changeTicket = this.changeTicket.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.keyPress = this.keyPress.bind(this);
}
changeTicket(e){
this.setState({
ticket : e.target.value,
})
}
handleSubmit(){
this.setState({
updatedTicket: this.state.ticket
});
}
keyPress(e){
if (e.keyCode ===13){
this.handleSubmit();
}
}
render() {
return (
<div className="App">
<header className="App-header">
<input type="text" placeholder="ENTER TICKET NUMBER" value={this.state.ticket} onKeyDown={this.keyPress} onChange={this.changeTicket}/>
</header>
</div>
);
}
}
and i'd like to be able to store the updatedTicket value in a variable which I can use in my PassTicket component. this is what i've attempted so far but the error it occurs is the following Uncaught TypeError: Cannot read property 'updatedTicket' of undefined
this is what my second component looks like:
class PassTicket extends Component {
transferredTicket(){
const myTicket = this.props.state.updatedTicket;
return myTicket
}
render() {
return (
<p>{this.transferredTicket()}</p>
);
}
}
When passing down a property from a parent to a child component, the property will be stored onto the props by the name it's passed through. For example:
class Parent extends Component {
state = {
ticket: '',
}
render() {
return <ChildComponent updatedTicket={this.state.ticket} />
}
}
class ChildComponent extends Component {
static propTypes = {
updatedTicket: PropTypes.string,
}
static defaultProps = {
updatedTicket: '',
}
render() {
return (
<div>{this.props.updatedTicket}</div>
);
}
}
In the example you've given, it doesn't seem like you're passing the state down to the component you're trying to access it in. In addition, it seems like you're trying to access the updatedTicket as a property of a state object, so just beware of how you're accessing your props.
Therefore, in order to access the updatedTicket property on the child component, you'll first need to import the PassTicket component, instantiate it in the parent (App) component, and pass the property down:
<PassTicket updateTicket={this.state.ticket} />
You would then be able to access the string in the PassTicket component like so - this.props.updateTicket
So .state in react is a local state that is only visible to the individual component. You can read more about it here: https://reactjs.org/docs/state-and-lifecycle.html
In order to pass your state around, you need to use the props system. So where you instantiate your component, you can pass in the state of the parent. For example:
<PassTicket ticket={this.state.updatedTicket}/>
Then inside your PassTicket render function, you can access the ticket prop:
render() {
const { ticket } = this.props
return (
<div>{ticket}</div>
)
}

componentWillMount check on undefined before api request will resolve

I have a component that triggers some methods that depend on async API request. I use componentWillmount to check some props. If this prop is true I trigger some function otherwise false. But the problem is, that first time the prop is undefined, only after some time it will become false or true. How to check it and wait until request resolved?
componentWillMount = () => {
this.props.init(parseInt(this.props.tagId, 16), this.props.accessToken);
if (!this.props.notFound && !this.props.person) this.props.onFreeTag();
};
Use componentWillReceiveProps lifecycle function, something like:
componentWillReceiveProps = (nextProps) => {
if (!nextProps.notFound && !nextProps.person)
nextProps.onFreeTag()
}
}
It appears that first time when the component loads or get called your are passing it some value which is undefined initially and later becomes available. For example lets say you have a parent component as following
class Parent extends React.Component {
constructor() {
this.state = {0}
}
render() {
<Child value={this.state.value} />
}
}
As you can see, initially the state doesn't have a property value so the Child will receive undefined for this.props.value. It will only receive not undefined when some parent function changes it like this,
class Parent extends React.Component {
constructor() {
this.state = {0}
}
onAction() {
this.setState({value: true})
}
render() {
<Child value={this.state.value} />
}
}
So if on some event parent calls its OnAction it will change the state and Child will get this.props.value as true but as Child will be already rendered componentWillMount hook will not get triggered but componentWillReceiveProps will. So if you want to use the prop in componentWillMount make sure its passed at the first render of the child and if that is not possible use componentWillReceiveProps to process the props

Rendering component with its state

I'm really confused now about lifecycle hooks. Here's my code:
App.js:
class App extends Component {
constructor(props){
super(props);
this.state = {
arrayOfComponents: []
}
}
componentDidMount(){
//i get the properties from the server which responds with the database's elements
fetch('http://localhost:3001/')
.then(res => res.json())
.then(arrayOfData => this.setState({arrayOfComponents: arrayOfData}))
.catch(err => console.log(err))
}
render() {
console.log(this.state) //first returns empty array, after the mount returns the db's array of elements
return (
<div className="App">
<Component name='First' id={1} componentsComponents={this.state.arrayOfComponents} />
</div>
);
}
}
Component.js:
class Component extends React.Component{
constructor(props){
super(props);
this.state={
componentsComponents: []
}
}
//here i was tried with componentDidMount, componentWillMount to set the
//this.props.componentsComponents to this.state.componentsComponents
//but it didn't work
renderComponents = () => {
if(this.state.componentsComponents.length){
return this.state.componentsComponents.filter(c => c.inhertedFromId === this.props.id).map(c => {
return <Component name={c.name} id={c.id} componentsComponents={this.props.componentsComponents} />
})
}
}
render(){
return(
<div>
{this.renderComponents()}
</div>
)
}
}
So what i want to do is to the components renders themselves, depending on the array they get from the App.js. But how to set the state before the render happens? Can i somehow ask the component to render again if it did mount? Or any other solutions?
You can simply assign this.props.componentsComponents in constructor itself only.
constructor(props){
super(props);
this.state={
componentsComponents: this.props.componentsComponents||[]
}
}
Bring Filter Up To App
Here it appears you are not calling renderComponents, and you are also trying to render a Component inside itself, which is difficult to reason about. Bring the renderComponents function up to App, and render the data using Component inside of App, and simply pass props down to a stateless Component, which may be a simpler solution to represent the data.
If a recursive call is indeed the best way to represent this data, may need to use getDerivedStateFromProps to move props into state on update, if you wish to store that data in state - something like:
static getDerivedStateFromProps(nextProps) {
return {
componentsComponents: nextProps.componentsComponents
}
}
Added to Component.

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

ReactJS: Accessing Child Final State

This may seem as a bit of a redundant question but I'm trying to access the final state of a child in React, after it has updated. I've been looking into the React LifeCycle docs (I think that might be the issue, but not sure), searched high and low, and can't quite figure it out.
I've got a component which needs to access the (final) value of the state of a child, once that child has done some updating (AJAX request which then does a few this.setStates).
So far, I'm able to access the entire state of that child, accessing through a ref (Inside componentDidMount), but when I try to access a specific value of said state, it returns null or undefined.
Here's some example code to explain (I'll try to spare you as much useless code as possible):
class Layout extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
// This gives me the updated State where pageTitle = "Whatever"
console.log(this.refs.child1);
// However this gives me the initial State where pageTitle = null
console.log(this.refs.child1.state.pageTitle);
}
render(){
return (<div>
{React.cloneElement(
this.props.children,
{ref: 'child1'}
)}
</div>);
}
}
And here's the child component for reference (note: i'm using axios for my ajax requests):
export class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
resultData: result,
pageTitle: null
}
}
componentDidMount(){
this.serverRequest = axios.get(apiUrl)
.then(function(result){
this.setState({
resultData: result,
pageTitle: result.pageTitle
});
}.bind(this))
}
render(){
return(<div>
{use of different this.state.resultData values works fine here}
</div>)
}
}
Appreciate any help that comes this way
To use a callback, add this code to the parent element:
handleAsyncDone(data) {
// Do whatever it is people do with data
}
And then pass that function to the child component, and in the childcomponent, add
this.props.handleAsyncDone(this.state);
Which will pass the child state back up to the parent.

Categories

Resources