React state/props won't update - javascript

I have the code below, but my state won't update.
I am using Angular http ajax-call to receive if the user is correct or not. When I pass the the new error-message as a prop nothing happens, but the component does receive it since I can access it through nextProps.
I have also tried to skip the constructor, componentWillReceiveProps and shouldComponentUpdate to just render out { this.props.error }, but that did not work either.
This is my render-function to render the DOM first time
// Some code
.then(function(response){
// Some code
}, function(response){
_this.renderLogin("User not found"); // Is sending the error-message to the function
});
// Some code
_this.renderLogin = function(error){
render(
<Login error={error} />,
document.getElementById("app")
);
};
_this.renderLogin("Standard");
This is the Login-component:
class Login extends React.Component {
constructor(props){
super(props);
this.state = {
error: this.props.error
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.error !== this.state.error) {
this.setState({ error: nextProps.error });
console.log(nextProps.error); // User not found
console.log(this.state.error); // Standard
}else{}
}
shouldComponentUpdate(nextProps, nextState){
console.log(nextState.error); // User not found
console.log(nextProps.error); // User not found
console.log(this.state.error); // Standard
return true;
}
render(){
return(
<div>
{ this.state.error } // Always showing: 'Standard'
</div>
);
}
}
export default Login;
Thanks in advance for any help!

From what I can see of your code Login should not be a stateful component since it does nothing to mutate the state... its just setting a prop it receives to its state for no reason. In React state is passed down with props and renders are triggered on components that need updating with the new prop value. Nothing is happening in your code because the component has already been affixed to the DOM, but your're trying to reaffix it to the DOM with a new value with this
.then(function(response){
// Some code
}, function(response){
_this.renderLogin("User not found"); // Is sending the error-message to the function
});
Something like that code needs to be within stateful react component that evaluates if the user is logged in or not. The state must be mutated WITHIN a react component and not outside trying to pass it in. In the code below I didn't change your Login to be stateless, but it still works because I've muted the value within a React component.
class RenderLogin extends React.Component {
constructor(props){
super(props);
this.state = {
errors: "Standard",
};
this.changeError = this.changeError.bind(this);
}
changeError() {
this.setState({errors:"Boom"});
}
render() {
return (
<div>
<Login error={this.state.errors} />
<button onClick={this.changeError}>Change</button>
</div>
);
}
}
class Login extends React.Component {
constructor(props){
super(props);
this.state = {
error: this.props.error
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.error !== this.state.error) {
this.setState({ error: nextProps.error });
console.log(nextProps.error); // User not found
console.log(this.state.error); // Standard
}else{}
}
shouldComponentUpdate(nextProps, nextState){
console.log(nextState.error); // User not found
console.log(nextProps.error); // User not found
console.log(this.state.error); // Standard
return true;
}
render(){
return(
<div>
{ this.state.error } // Always showing: 'Standard'
</div>
);
}
}
ReactDOM.render(<RenderLogin />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<!-- begin snippet: js hide: false console: true babel: true -->
<div id="app"></div>

Related

React app: props do not get updated externally

I have a React app that gets initialized as simple as:
let globalTodos = some_fetch_from_localstorage();
...
function changeGlobalTodos() {
globalTodos = another_fetch_from_localstorage();
}
...
ReactDOM.render(<ReactApp todos={globalTodos} />, document.getElementById('app'));
Inside of the app I'm doing the following:
constructor(props) {
super(props);
this.state = {
todos: []
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.todos !== prevState.todos) {
return { todos: nextProps.todos };
} else return null;
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.todos !== this.props.todos) {
this.setState({ todos: this.props.todos });
}
}
The problem is that whenever I update globalTodos, the props on the React app don't get updated: it stays on the initial globalTodos's value.
I have tried playing with getDerivedStateFromProps is being called only on first setup of the props while componentDidUpdate never gets called :-/
What am I missing here?
I can't leave a comment, so I'll just post this here. React won't re-render unless you're updating a state.
I'd make globalTodos a state and add onto it from there using setState, then you can pass that on as a prop to the child component in your case ReactApp. You don't need to change them as states in your child component.
Example:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
globalTodos: initialFetchedArray
};
}
changeGlobalTodos() {
let newTodos = fetchNewArray;
this.setState({globalTodos: newTodos});
}
ReactDOM.render(<ReactApp todos={globalTodos} />, document.getElementById('app'));
}
//You just need your prop here, here you can do whatever you want to do with the array if it's display you can use map
class Child extends Component {
render {
return(
{this.props.todos}
)
}
}
Really the main thing here is making your globalTodos a state, using setState to change that state, and just passing that state down as a prop.

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

Stop React Higher Order Component from mounting before wrapped component?

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

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