Stop React Higher Order Component from mounting before wrapped component? - javascript

I have an auth-related HOC which wraps components. In the wrapped components, I want a user prop which is set by the HOC. However, in a child component I call the prop in componentDidMount, and I get the error:
TypeError: Cannot read property 'identity' of null
Which tells me that the user prop (which has the identity attribute) is not being set in time.
Here is the HOC:
export default function withAuth(AuthComponent) {
return class AuthWrapped extends Component {
constructor() {
super();
this.state = {
user: null
};
this.Auth = new AuthService();
}
async componentDidMount() {
try {
const profile = await this.Auth.getProfile(); //Returns a decoded JWT token containing with 'identity':'test'
console.log(profile.identity); //prints 'test' twice
this.setState({
user: profile
});
console.log(this.state.user); //prints the full profile including 'identity':'test' once and null once
}
}
render() {
const user = this.state.user;
console.log(user); //prints null once and the correct profile once
return (
<AuthComponent history={this.props.history} user={this.state.user} />
);
}
};
}
Notice that the console.logs each print twice.
Here's the wrapped component:
class Account extends Component {
componentDidMount() {
axios.get('/user/' + this.props.user.identity).then(res => { //This is the line which triggers the error.
//do something
});
}
render() {
<div><AnotherComponentWrappedBySameHOC/></div>
);
}
}
export default withAuth(Account);
When I remove the other component that is also wrapped by withAuth, the console.logs only run once. Makes sense. However, the logs which still print are the logs which print null. In other words, the wrong logs.
Basically, I think this means that the componentDidMount call in withAuth is taking too long, and completing after the componentDidMount call in Account. Try as I might, though, I have no solutions. Any ideas?

You can just wait untill you have the user and then render the child like
render() {
const user = this.state.user;
console.log(user); //prints null once and the correct profile once
return (<div>
{user && <AuthComponent history={this.props.history} user={this.state.user} />}
</div>);
}
};
This will only render the child component when user has some data

Related

React not loading state properly on mount during axios call

Basically I am making a basic react App that is grabbing some data from a DB and I am stuck on the basic setup.
My intention is to have my state contain the response from my server querying my databse.
My response is 100% working and sending the data back as expected from the axios call, however the state is never getting update.
EDIT : I am attempting to pass the movies down the chain to a Component called MovieList, I have provided the code for that as well.
App.jsx
import React from 'react';
import axios from 'axios';
import MovieList from './MovieList.jsx';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {allMovies: []};
}
componentDidMount() {
var that = this;
axios.get('http://localhost:3000/api/movies')
.then( res => {
that.setState({allMovies: res.data});
})
.catch( err => {
console.log(`Err # [ App.jsx - componentDidMount ] ::: ${err}`);
});
}
render() {
return (
<div>
<h1>Hello World!</h1>
<MovieList movies={this.state.allMovies} />
</div>
)
}
}
MovieList.jsx
import React from 'react';
class MovieList extends React.Component {
constructor(props) {
super(props);
this.state = {};
console.log(this.props); //EMPTY OBJECT MOVIES DIDN'T GET INTO PROPS
}
render() {
return (
<div>
<h1>Test</h1>
</div>
)
}
}
export default MovieList;
NOTE : I also logged the props on mount and attempted to render them and they were empty.
Basically if I try to pass down this.state.allMovies or console.log it, its always just the initial empty array.
Maybe I don't understand how async setting the state can be done? I took a similar approach on my last school project and it seemed to work fine.
You don't await the axios promise to resolve, so you simply are logging what the state is when the component mounts. Use one of the following to log updated react state.
Use componentDidUpdate to log the updated state.
componentDidMount() {
console.log(this.state);
}
Use the setState callback function to log the state
componentDidMount() {
var that = this;
axios.get('http://localhost:3000/api/movies')
.then( res => {
console.log(res.data);
that.setState(
{ allMovies: [res.data] },
() => console.log(this.state), // <-- setState callback function
);
})
.catch( err => {
console.log(`Err # [ App.jsx - componentDidMount ] ::: ${err}`);
});
}
You'll never see it in your constructor, because when your component is instantiated, it's done so with an empty array.
You will see it if you do a console.log(this.props) in componentDidUpdate or render however.
This is because when App is mounted, your component passes a movies prop of [] to MovieList. After the movies return from the server (and you update the state of App), App will render again and pass the array returned from the server, causing your MovieList component to render again. It's constructor won't be called, because it's already instantiated, but MovieList will call componentDidUpdate and render again.
class MovieList extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
console.log(this.props); // you'll see this get logged twice - once with an empty array for movies and once with the movies returned from the server.
return (
<div>
<h1>Test</h1>
</div>
)
}
}

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>
)
}

Need to Execute Function before render() in ReactJS

I've created a login system with React which stores a session when the user logs in. When the page is reloaded, I have added a function which should check if the session exists and then either setState() to true or to false.
As I'm new to React, I'm not sure how to execute this function. Please see my code below for App.js:
import React from 'react';
import './css/App.css';
import LoginForm from "./LoginForm";
import Dashboard from "./Dashboard";
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
renderLoginForm: true
};
this.handleLoginFormMount = this.handleLoginFormMount.bind(this);
}
handleLoginFormMount() {
this.setState({
renderLoginForm: false
});
}
// Check session function.
checkSession() {
fetch('/check-session', {
credentials: 'include'
})
.then((response) => {
return response.json();
})
.then((sessionResult) => {
if (sessionResult.username) {
console.log('false');
this.setState({
renderLoginForm: false
});
} else {
console.log('true');
this.setState({
renderLoginForm: true
});
}
})
.catch((error) => {
console.log('Error: ', error);
});
}
render() {
checkSession();
return (
<div className="App">
{this.state.renderLoginForm ? <LoginForm mountLoginForm={this.handleLoginFormMount} /> : null}
{this.state.renderLoginForm ? null : <Dashboard />}
</div>
);
}
}
export default App;
Having checkSession() in this position outputs the following in the console when loading the page:
Line 50: 'checkSession' is not defined no-undef
If I put the function outside of the class App extends React.Component {}, then it tells me that I cannot set the state of undefined.
Functional Component: In my case I wanted my code to run before component renders on the screen. useLayoutEffect is a hook provided by React for this exact purpose.
import React, { useLayoutEffect } from "react";
...
const App = () => {
useLayoutEffect(() => {
//check local token or something
}, []);
}
Read More: https://reactjs.org/docs/hooks-reference.html#uselayouteffect
Having checkSession() in this position outputs the following in the console when loading the page:
Line 50: 'checkSession' is not defined no-undef
That's because it's a method, but you're calling it like a freestanding function. The call should be this.checkSession();. But keep reading.
Separately:
The render function must be pure, it cannot have side-effects like changing state. Instead, put any side-effects code in componentDidMount; from the documentation for that lifecycle method:
If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
Be sure that your component renders correctly for the original state (before the session check), as well as for the updated state (after the session check).
More about lifecycle methods and such in the documentation.
Alternately, if this component can't do anything useful without the session, you might move the session check to its parent component, and have the parent only render this child component when it has the session check results.

Call a function in react component, WITHOUT event handlers or props

sorry if this question appeared somewhere else, but it's getting extremely frustrating to find answers where every question involves event handler or child element method calling.
I need to call a function when component is initialized, basically when window loads, or instantly.
On initialization I want to call a getGameMeta() to update Game state, if I'm trying to call it in jsx either I make a loop or get an error saying "Functions are not valid as a React child. This may happen if you return a Component instead of from render...."
class Game extends React.Component{
constructor(props) {
super(props);
this.state = {name: undefined,};
this.getGameMeta = this.getGameMeta.bind(this);
}
getGameMeta(){
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
};
render(){
return (
<div>
{/* {this.getGameMeta()} */} causes loop
{/* {this.getGameMeta} */} causes error
<p>{this.state.name}</p>
</div>
);
};
};
Using the componentDidMount hook is a great way to load data from a remote endpoint when the component is first mounted.
Example
class Game extends React.Component {
constructor(props) {
super(props);
this.state = { name: undefined };
this.getGameMeta = this.getGameMeta.bind(this);
}
componentDidMount() {
this.getGameMeta();
}
getGameMeta() {
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
}
render() {
return (
<div>
<p>{this.state.name}</p>
</div>
);
}
}
You can call it in componentDidMount. It guarantees that it will be called once and right after when component will be mounted. More over from React Docs:
If you need to load data from a remote endpoint, this is a good place
to instantiate the network request.
getGameMeta(){
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
};
componentDidMount(){ this.getGameMeta() }
So seems like this is the way you are looking for
You can simply use useEffect if you are using a functional component.
basically, it loads the data before rendering your UI.
import {useEffect} from "react";
const GameData=()=>{
const [fetchD,setFetchD]=useState("");
useEffect(()=>{
fetch(Url).then(data => {
console.log(data);
setFetchD(data[0].name);
});
});
})
}
export default GameData;
//you can also check react documentation at https://reactjs.org/docs/hooks-effect.html

undefined in variable using react componentDidMount

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.

Categories

Resources