React material UI open login dialog if token expired - reactjs

I am using react material UI. I am frequently checking if token is expired using setInternval() and if its expire than login dialog should be open and setInterval should be cleared using clearInterval(). Below is my code but I am getting warning as Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. and not able to achieve the required result.
App.js
import AuthService from './includes/AuthService.js';
class App extends React.Component {
constructor(props) {
super(props);
this.Auth = new AuthService();
}
componentDidMount(){
setInterval(() => {this.Auth.checkToken()}, 10000);
}
}
AuthService.js
class AuthService extends React.Component{
constructor(props) {
super(props);
this.state = {email: '', password : '', loginOpen : false};
}
checkToken() {
console.log("token checked");
if (decode(localStorage.getItem('jwtToken')).exp < Date.now() / 1000) {
this.setState({loginOpen : true}, () => {
console.log('state updated');
console.log(this.state.loginOpen);
clearInterval();
});
}
}
render(){
const { onRequestClose } = this.props;
const actions = [
<FlatButton
label="Close"
primary={true}
onClick={this.handleClose}
/>,
];
return (
<MuiThemeProvider>
<Dialog title="Result Details"
actions={actions}
modal={false}
open={this.state.loginOpen}
onRequestClose={this.handleClose}
autoScrollBodyContent={true}
>
</Dialog>
</MuiThemeProvider>
}

loginOpen and checkToken() look like something that can be moved up to App component, and be passed to AuthService as props.
Alternatively, you can simply move down and call checkToken() on componentDidMount() function of AuthService.
Like this:
class AuthService extends React.Component{
constructor(props) {
super(props);
// ...
this.checkToken = this.checkToken.bind(this);
this.intervalId = null;
}
componentDidMount(){
this.intervalId = setInterval(() => {this.checkToken()}, 10000);
}
checkToken() {
console.log("token checked");
if (decode(localStorage.getItem('jwtToken')).exp < Date.now() / 1000) {
this.setState({loginOpen : true}, () => {
console.log('state updated');
console.log(this.state.loginOpen);
if (this.itv) {
clearInterval(this.intervalId);
}
});
}
}
See which approach will work better, and see if my fix works, and I can add more explanations.
One more thing I want to point out is that clearInterval(..) takes the ID returned from setInterval.
Hence the setting of this.intervalId and passing it to clearInterval(..).
From your comment:
how can I change state in parent component i.e. app component from its
child component. Becase login modal is in app component.
You are rendering login modal in App component.
You can conditionally render the login modal based on App's this.state.loginOpen.
For example, if your App render function contains a login modal component called LoginModal
render() {
<div>
{ this.state.loginOpen && <LoginModal /> }
</div>
Or, if you are calling some function to show the login modal, you can do something like if (this.state.loginOpen) { showLoginModal(); }.

Related

The constructor and render function is called twice

I have a very simple component, in that I log the information to check the Component Lifecycle and see that the constructor and render function is called twice every time when I reload the browser. Could anyone please help me to review why?
Here is my code, and the result in the picture.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
name: 'Viet'
};
console.log('App constructor');
}
componentWillMount() {
console.log('App componentWillMount');
}
componentDidMount() {
console.log('App componentDidMount');
}
changeState = () => {
this.setState({ name: 'Viet is changed' })
}
render() {
console.log('App render');
return (
<div className='App'>
{this.state.name}
{<button onClick={this.changeState.bind(this)} >Click to change state</button>}
</div>
);
}
}
export default App;
In your render method you are not passing the function right due to which component is re-rendered again. You need to bind the function in constructor change the onClick of button as:
constructor(props) {
super(props);
this.state = {
name: 'Viet'
};
console.log('App constructor');
this.changeState = this.changeState.bind(this);
}
<button onClick={this.changeState}>Click to change state</button>
The double rendering is due to React.StrictMode. If you check your src/index.js the App Component is wrapped with React.StrictMode tags.
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
As mentioned in the release notes:
"React.StrictMode is a wrapper to help prepare apps for async rendering"
P.S: There is nothing to worry about re-rendering.

Calling child method from Parent in Reactjs

I know it's a question asked many times on SO but I am still facing a problem don't know how to resolve.
import Events from './subComponents/Events'
export default class userHome extends Component {
constructor() {
super();
this.state = {
events:[],
};
this.changeView=React.createRef();
}
changeViewClick = () =>{
this.changeView.current.changeDataView();
};
render() {
const {events} = this.state
return (
<IconButton onClick={this.changeViewClick}>
<CardView />
</IconButton >
<IconButton onClick={this.changeViewClick}>
<TableView />
</IconButton>
<Events ref={this.changeView} events={events} />
);
}
}
Events Component
export default class Events extends Component {
constructor(props) {
super(props);
}
changeDataView = () => {
console.log("hi");
}
render() {
return (<div>Hey Child</div>);
}
}
I am getting error as
TypeError: _this.changeView.current.changeDataView is not a function
My reactjs version is 16.6.3
In my opinion, have you tried to pass an anonymous function to child component ?
onClick={() => this.yourfunction()}
It happened, I think, because you called in your child component this props : this.changeView.current.changeDataView()
So when you pass that props in the child component you must passed it as an anonymous function to tell React it's a function to execute when the onClick event is triggered.
Let me know if that resolve your problem or if I am wrong

Remove a React component from the DOM

I have this piece of code (which I've simplified for posting here) that creates a component and renders it
const getComponentToRender = (user, path) => {
switch(path ){
case 'ChangePassword':
return <ChangePassword user={ user } />;
case 'NewPassword':
return <NewPassword user={ user } />;
case 'PasswordExpire':
return <PasswordExpire user={ user } />;
default:
return null;
}
}
class UserAdmin extends React.Component {
static propTypes = {
user: PropTypes.object.isRequired
};
render() {
const component = getComponentToRender(this.props.user, 'ChangePassword' );
return(
<div id='user-admin-wrapper'>
{component}
</div>
)
}
componentWillUnmount(){
}
}
When I navigate away from UserAdmin the componentWillUnmount gets called.
Q: What is the simplest way to actually remove the component ChangePassword or any other component (by its name) from the DOM when componentWillUnmount executes.
OR, removing the component at any point, without waiting for componentWillUnmount
Using react-dom 15.6.1 . btw
Un-mounting a component will un-mount(remove) all the child components it contains. So after componentWillUnmount the component you rendered inside it will be removed.
If you need to control over components that rendered without un-mounting you use conditional render logic.
Example
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
shouldIRender: true
};
}
componentDidMount() {
setTimeout(() => {
this.setState({shouldIRender: false});
}, 5000);
}
render() {
return(
<div>
<ComponentThatAlwaysHere />
{ this.state.shouldIRender === true ? <ComponentThatRemovesAfterStateChange /> : null }
{ this.state.shouldIRender === true && <AnotherComponentThatRemovesAfterStateChange /> }
</div>
)
}
}

Check state from imported component

Much of a basic question. Can I pass the state property to another component? So if I create a login app and after a successful login from API call I set the state of loggedInUser: 12345 in say a component called Login.js
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
data:[],
loggedInUser: 12345
}
}
render(){
return(
//Return the this.state.loggedInUser
)
}
}
How can I pass this.state.loggedInUser from Login.js to another component where I've imported Login.js?
For example, in my Page1.js I have import Login from './Login'
Can something like this be achieved? I just want to pass the this.state.loggedInUser value to any page where it us imported.
Thanks.
As mentioned in the comment above, redux is probably best practice here. But here is an example of accomplishing this with vanilla react.
export default class Parent extends Component {
constructor(props) {
super(props)
this.state = {
logginUser: undefined
}
this.handleUserState = this.handleUserState.bind(this);
}
handleUserState = (userInfo) => {
this.setState({logginUser: userInfo})
}
render = () => {
return (
<div>
<Login handleUserState={this.handleUserState} />
</div>
)
}
}
export default class Login extends Component {
constructor(props) {
super(props);
this.loginUser = this.loginUser.bind(this);
}
loginUser = (e) => {
e.preventDefault()
<--- make call to api for userInfo here and pass it to the call below --->
this.props.handleUserState(userInfo)
}
render =() => {
return(
<div>
<button type="submit" onClick={this.loginUser} />
</div>
)
}
}
Basically what's happening here since you are importing Login into another component, you will have that 'Parent' component act as the state manager and save the user data at that level. You then pass the function that updates the state to the Login component and call it once you have the user data to update it with.
I hope this helps!

Call external Api on button click and then render a child component with the api result data

<!--Weather.js-->
<!-- begin snippet: js hide: false console: true babel: false -->
import React from 'react'
import MyApi from '../api/MyApi';
import InputCity from './InputCity'
import WeatherData from './WeatherData'
export default class Weather extends React.Component{
constructor(props){
super(props);
this.state = {
weather:[],
city: ''
}
}
makeRequest = (city) => {
MyApi.getWeather(city)
.then(function (res) {
this.setState(function () {
return{
weather:res
}
})
}.bind(this));
}
componentDidMount(){
this.makeRequest(this.state.city)
}
setCity = (mycity) =>{
this.setState(function () {
return{
city:mycity
}
})
}
render(){
const showWeatherData = this.state.weather;
return(
<div>
<InputCity setCity={this.setCity}/>
{showWeatherData && <WeatherData city={this.state.city}/>}
{console.log(this.state.weather)}
</div>
);
}
}
I have three components:
Weather
InputText
WeatherData
Now the InputText Component is rendered when the main Weather component is rendered, the InputText component contains a textfield and a button.
So now when i click the button need to call an openweathermap api and then display the result in WeatherData Component.
The WeatherData component must be rendered only after the button click.
How can i achieve this??
add some state to the Weather component, call it showWeatherData for example, set it to null in the beginning. Give it a value after you receive back the data from your api.
in your JSX inside Weather component, use the && to short circuit the WeatherData component (just a short way instead of using an if or a tertiary operator)
render(){
<InputText />
{ ShowWeatherData && <WeatherData /> }
}

Resources