I have the following react component, which contains the state signed_in.
When the login state changes, the callback fires (verified using the console logging), but the component does not re-render.
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {};
auth.onAuthStateChanged(function(user) {
if (user) {
this.state = { signed_in: true };
console.log("signed in");
} else {
this.state = { signed_in: false };
console.log("signed out");
}
});
}
render() {
return (
<MDBContainer className="text-center mt-5 pt-5">
<div>
{this.state.signed_in ? (
<div>
<h5>Please sign-in:</h5>
<StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={auth} />
</div>
) : (
<h5>You are already signed in.</h5>
)}
</div>
</MDBContainer>
);
}
}
I suspect this may be because the callback function isn't modifying the components state (this.state)? What is the fix here?
In the case of your class based component, a re-render is triggered by calling the components setState() method.
The setState() method accepts an object describing the state change that will be applied to your components state:
/* Update the signed_in field of your components state to true */
this.setState({ signed_in: true });
By calling setState() React internally applies the state change to the existing component state and then triggers a re-render, at which point any state changes that have been made will be visible in your components the subsequent render cycle.
In the case of your code, one way to achieve these changes would be:
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {};
/* Make arrow function, allowing component's
setState method to be accessible via "this" */
auth.onAuthStateChanged((user) => {
if (user) {
/* Pass state change to setState() */
this.setState({ signed_in: true });
console.log("signed in");
} else {
/* Pass state change to setState() */
this.state = { signed_in: false };
console.log("signed out");
}
});
}
render() {
return (
<MDBContainer className="text-center mt-5 pt-5">
<div>
{this.state.signed_in ? (
<div>
<h5>Please sign-in:</h5>
<StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={auth} />
</div>
) : (
<h5>You are already signed in.</h5>
)}
</div>
</MDBContainer>
);
}
}
Related
So I´m having this issue with my react class component.
Since it´s handling "a lot" of state, I don´t want to use a functional component bc I think it is more readable like so.
A few layers down from my class component I have a features component, which dispatches selected features from checkboxes to the redux state.
My class component needs to know these changes and re-renders based on the selected features, stored in the redux store.
The problem is, it always gets the new props from the mapStaeToProps function, but I can´t setState(...) based on the new props, bc componentDidMount only runs once at the beginning or componentWillReceiveProps() doesn´t exist anymore.
import { connect } from 'react-redux';
import TaggingComponent from '../../../content-components/TaggingComponent';
import SupplyAmount from '../../../content-components/SupplyAmount';
import FeaturesComponent from '../../../content-components/FeaturesComponent';
import './TokenGenerator.css';
const features = {
Features: [
'Mintable',
'Burnable',
'Pausable',
'Permit',
'Votes',
'Flash Minting',
'Snapchots'
],
'Access Control': ['Ownable', 'Roles'],
Upgradeability: ['Transparent', 'UUPS']
};
class TokenGenerator extends React.Component {
constructor(props) {
super(props);
this.state = {
tokenName: '',
shortName: '',
supplyAmount: null,
selectedFeatures: this.props.selectedFeatures
};
}
static getDerivedStateFromProps(props, current_state) {
if (current_state.selectedFeatures !== props.selectedFeatures) {
return {
selectedFeatures: props.selectedFeatures
};
}
return null;
}
setTokenName = (e) => {
this.setState({ tokenName: e.target.value });
};
setShortageName = (e) => {
this.setState({ shortName: e.target.value });
};
setAmount = (e) => {
this.setState({ supplyAmount: e.target.value });
};
render() {
return (
<div className="grid grid-cols-7 gap-10">
{/* Name and shortage input */}
<TaggingComponent
setTokenName={this.setTokenName}
setShortageName={this.setShortageName}
/>
{/* Token supply */}
<SupplyAmount setAmount={this.setAmount} />
{/* Tokenfeatures */}
<FeaturesComponent features={features} />
{/* Selected Features listing */}
<div className="card-bg border-2 border-white text-white p-11 rounded col-span-5 row-span-5">
<h4 className="text-center">Selected Features</h4>
{this.state.selectedFeatures.map((feature) => {
return <p key={feature}>{feature}</p>;
})}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log(state);
return { selectedFeatures: state.tokenGeneratorState.selectedFeatures };
};
export default connect(mapStateToProps)(TokenGenerator);
Is the new getDerivedStateFromProps method the only way to update state vom changing props?
Cheers
You don't need to set state for each prop change. Because the component which receives props will re-render automatically whenever any of it's props changes.
Take this as example,
class ComponentA {
render() {
<ComponentB someProp={someDynamicallyChangingValue} />
}
}
class ComponentB {
render() {
<div>{this.props.someProp}</div>
}
}
Here, the ComponentB will be automatically re-rendered whenever someProp is changed in ComponentA.
I have two components, one parent one child. I am using the fetch method in componentDidMount() callback. Once I do this, I set the state with key items to that data that is pulled from the api. Once I do this it should be able to be console logged in the child component as a prop. However this is not working. What am I doing wrong here?
Parent Component:
import React, {Component} from 'react';
import Map from './maps/Map';
class Main extends Component {
constructor(props){
super(props);
this.state = {
name: "John",
items: []
}
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits
})
})
}
render() {
return (
<div>
<Map list={this.state.name} items={this.state.items}></Map>
</div>
)
}
}
export default Main;
Child Component:
import React, {Component} from 'react';
class Map extends Component {
constructor(props) {
super(props);
console.log(props.items)
}
render () {
return (
<h1>{this.props.name}</h1>
)
}
}
export default Map;
First, fetch is asynchronous. So, the fetch statement might be pending by the time you try to console.log the result inside the child constructor.
Putting the console.log inside the render method would work, because the component will be rerendered, if the state items changes.
The constructor for a component only runs one time during a lifecycle. When it does, props.items is undefined because your ajax request is in-flight, so console.log(props.items) doesn't show anything.
If you change your constructor to console.log("constructed");, you'll see one-time output (stack snippets may not show this--look in your browser console). Henceforth, componentDidUpdate() can be used to see the new props that were set when your ajax request finishes.
You could also log the props inside the render method, which will run once before the ajax request resolves and again afterwards when props.items changes.
As a side point, you have <Map list=... but the component tries to render this.props.name, which is undefined.
Also, if you aren't doing anything in the constructor (initializing state or binding functions) as here, you don't need it.
class Map_ /* _ added to avoid name clash */ extends React.Component {
constructor(props) {
super(props);
console.log("constructed");
}
componentDidUpdate(prevProps, prevState) {
const props = JSON.stringify(this.props, null, 2);
console.log("I got new props", props);
}
render() {
return (
<div>
<h1>{this.props.name}</h1>
<pre>
<ul>
{this.props.items.map((e, i) =>
<li key={i}>{JSON.stringify(e, null, 2)}</li>)}
</ul>
</pre>
</div>
);
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {name: "John", items: []};
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({items: dat.hits})
});
}
render() {
return (
<div>
<Map_
name={this.state.name}
items={this.state.items}
/>
</div>
);
}
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Main />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
The only problem you have is that you are trying to use this.props.name and your Map component props are called list and items, so it will return undefined.
If you log your props in the constructor you will get the initial state of Main because the fetch hasn't returned anything yet. Remember that the constructor only runs once. So you are probably getting an empty array when you log props.items in the constructor because that's what you have in your initial state.
{
name: "John",
items: []
}
If you log the props in your render method you will see your array filled with the data you fetched, as you can see here:
https://codesandbox.io/s/stoic-cache-m7d43
If you don't want to show the component until the data is fetched you can include a boolean property in your state that you set to true once you the fetch returns a response and pass it as a prop to your component. Your component can you use that variable to show, for example, a spinner while you are fetching the data. Here's an example:
https://codesandbox.io/s/reverent-edison-in9w4
import CircularProgress from "#material-ui/core/CircularProgress"
class Main extends Component {
constructor(props) {
super(props);
this.state = {
name: "John",
items: [],
fecthed: false
};
}
componentDidMount() {
fetch("https://hn.algolia.com/api/v1/search?query=")
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits,
fecthed: true
});
});
}
render() {
return (
<Map
fetched={this.state.fecthed}
list={this.state.name}
items={this.state.items}
/>
);
}
}
class Map extends Component {
render() {
return (
<div>
{this.props.fetched ? (
<div>
<h1>{this.props.list}</h1>
{this.props.items.map((item, indx) => (
<div key={indx}>Author: {item.author}</div>
))}
</div>
) : (
<CircularProgress />
)}
</div>
);
}
}
Hope this helps. Cheers!
I'm new to React, just a question on componentWillUnmount() and render() lifecycle method. Below is some code:
...
export default class App extends Component {
constructor(props) {
super(props);
this.state = { showMessage: true }
}
handleChange = () => { this.setState({ showMessage: !this.state.showMessage });
}
render(){
<div>
<input type="checkbox" checked={ this.state.showMessage } onChange={ this.handleChange } />
<label>Show</label>
</div>
{ this.state.showMessage && <Message message="Hello" /> }
}
}
export class Message extends Component {
...
componentWillUnmount() {
console.log("Unmount Message Component");
}
render(){
console.log(`Render Message Component `);
return (
<div>{this.props.message}</div/
)
}
}
I trigger the unmounting phase of the Message componentby unchecking the checkbox , so I have those in the console output:
Render Message Component
Unmount Message Component
so my question is:
It is not efficient because the current Message component is going to be destroyed since I don't need it once I uncheck the box. But Message component's render() was still being called, which is unnecessary since we don't really care what content it contains, is it a way to avoid the call of re-render method and just get componentWillUnmount() to be called? I was thinking to use shouldComponentUpdate(), but I can stop render() method to be called, but that will also stop componentWillUnmount() to be called too
When you unmount a component render is not executed - only componentWillUnmount is called. The Render Message Component log is caused by initial render when Message is visible:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { showMessage: true }
console.log("initial render:");
}
handleChange = () => { this.setState({ showMessage: !this.state.showMessage });
}
render(){
this.state.showMessage || console.log("unmounting:");
return <div>
<input type="checkbox" checked={ this.state.showMessage } onChange={ this.handleChange } />
<label>Show</label>
{ this.state.showMessage && <Message message="Hello" /> }
</div>
}
}
class Message extends React.Component {
componentWillUnmount() {
console.log("Unmount Message Component");
}
render(){
console.log(`Render Message Component `);
return (
<div>{this.props.message}</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div id="root"></div>
I've recently seen this type of react pattern where the state is being set in a render by using this.state:
class ShowMe extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
};
}
render() {
if (this.props.show) {
this.state.showButton = true; //setting state in render!!
}
return (
<div>
<div> Show or hide button </div>
{this.state.showButton && <Button content='Btn'/>}
</div>
)
}
}
This seems like an anti-pattern. Can this cause bugs? It seems to work properly though.
I would just use a component lifecycle to set the state:
class ShowMe extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
};
}
componentWillReceiveProps(nextProps) {
if(nextProps.show) {
this.setState({
showButton: true,
})
}
}
render() {
return (
<div>
<div> Show or hide button </div>
{this.state.showButton && <Button content='Btn'/>}
</div>
)
}
}
What is the recommended way?
render should always be pure without any side effects, so it's certainly a bad practice.
from the React docs :
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. If you need to interact with the browser, perform your work in componentDidMount() or the other lifecycle methods instead. Keeping render() pure makes components easier to think about.
Take a look also here and here.
It is an anti-pattern. If showButton state is not always equal to show props (which is the case in the example), I would use this:
class ShowMe extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: this.props.show,
};
}
componentDidUpdate(prevProps, prevState) {
prevProps.show !== this.props.show && this.setState({showButton: this.props.show})
}
render() {
return (
<div>
<div> Show or hide button </div>
{this.state.showButton && <Button content='Btn'/>}
</div>
)
}
}
Edit: As of React 16.3 one should use getDerivedStateFromProps in this case.
Note that componentWillReceiveProps will be deprecated.
From the docs: getDerivedStateFromProps is invoked after a component is instantiated as well as when it receives new props. It should return an object to update state, or null to indicate that the new props do not require any state updates.
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
It is incorrect setting state in render method. You can set state in lifecyles method. But other thing is that your component can receive same props many times, so your component will be set state many times, and renderd. To solve this problem you need to compare your new with your current props for example compare json objects:
componentWillReceiveProps(nextProps) {
if(JSON.stringify(this.props) !== JSON.stringify(nextProps) && nextProps.show) {
this.setState({
showButton: true,
})
}
}
or use PureComponent. And that garentee you that your component will not rerendered constantly.
And it will be better if you do not rerender component if state.showButton currently seted to true.
componentWillReceiveProps(nextProps) {
if(JSON.stringify(this.props) !== JSON.stringify(nextProps) && nextProps.show) {
if(!this.state.showButton) {
this.setState({
showButton: true,
})
}
}
}
I know that the question with this title has already been asked few times before but the problem is that I couldn't get an appropriate answer. So as I am new to reactJS and trying to create login logout form.
What I want to do is to pass or change a state of parent component from a child component through an event handler(When a user clicks on logout button). Below are the two Components:
First One:
class Home extends React.Component {
constructor(props){
super(props);
this.state = {login : false};
}
login(){
// this method updates the login.state : true
}
render() {
return (
<div>
{this.state.login ? (<ChatBox userNick="fad" />) : (<LoginScreen onSubmit={this.login} />) }
</div>
);
}
}
And Second:
class ChatBox extends React.Component{
logout(){
// Expecting or trying to update parent.state.login : false
// via this method as well
}
render() {
return (
<div className="chat-box">
<button onClick={this.logout} > Logout </button>
<h3>Hi, {this.props.userNick} </h3>
</div>
);
}
}
I have simplified these component to come on point.
What's going here?
Home Component is the main parent component. Initially the state.login is false and in this situation LoginScreen Components shows up. Now, when user login through LoginScreen Component state.login updates to true, it's time to show for ChatBox Component.
Now you can see that ChatBox Component contains a button which calls a method logout to logout user. What I want is to update once again the state.login to false in Home Component When user click on the Logout Button.
I don't know how to do it, It will be appreciate if you help me.
Thanks in advance.
Do it in the same way as you are doing for Login, pass a function as a prop and call it on logout, see updates below.
const LoginScreen = () => (<div>Login Screen</div>);
class Home extends React.Component {
constructor(props){
super(props);
this.state = {login : true};
this.logout = this.logout.bind(this);
}
login(){
// this method updates the login.state : true
}
logout() {
// this method updates the login.state : false
this.setState({ login: false });
}
render() {
return (
<div>
{this.state.login ? (<ChatBox userNick="fad" onLogout={this.logout} />) : (<LoginScreen onSubmit={this.login} />) }
</div>
);
}
}
class ChatBox extends React.Component{
constructor(props) {
super(props)
// This makes sure `this` keeps pointing on this instance when logout is called from the outside.
this.logout = this.logout.bind(this);
}
logout(){
// Call the onLogout property.
this.props.onLogout();
}
render() {
return (
<div className="chat-box">
<button onClick={this.logout} > Logout </button>
<h3>Hi, {this.props.userNick} </h3>
</div>
);
}
}
ReactDOM.render(<Home />, document.querySelector('#main'));
<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>
<div id="main"></div>
You can pass an event from the Parent component to the Child component that handles the change of the state, like so:
class App extends React.Component {
constructor() {
super();
this.state = { isLoggedIn: false };
}
_handleLogin() {
this.setState({ isLoggedIn: true });
}
_handleLogout() {
this.setState({ isLoggedIn: false });
}
render() {
const { isLoggedIn } = this.state;
return (
<div>
{
isLoggedIn ?
<ChatBox logoutEvent={this._handleLogout.bind(this)} />
:
<Login loginEvent={this._handleLogin.bind(this)} />
}
</div>
);
}
}
const Login = ({ loginEvent }) => (
<button type="button" onClick={loginEvent}>Login</button>
);
const ChatBox = ({ logoutEvent }) => (
<div>
<h1>This is the Chat Box!</h1>
<button type="button" onClick={logoutEvent}>Logout</button>
</div>
);
ReactDOM.render(
<App />,
document.getElementById('container')
);
Here's the fiddle