value is not getting stored in "state" by " this.setState({ value})" - reactjs

I am getting "user" from "decoded token" and setting that user in state, but value is not getting store in state although "user" has a value.
Here is my code.
class Complainer extends Component {
state = {};
componentDidMount() {
const user = auth.getCurrentUser();
console.log(user);
this.setState({ user });
if (!user) window.location = '/';
}
but user is not getting stored in state. please help.

Try this code:
class Complainer extends Component
{
state = {
user: '',
};
componentDidMount(){
const user = auth.getCurrentUser();
console.log(user);
this.setState({
user: user
});
}
render(){
return (
<React.Fragment>
<Navbar />
<main className="container">
<Switch>
<Route path="/complainer/view-all" component={AllComplaints} />
<Route path="/complainer/not-found" component={notfound} />
<Showcase user={this.state.user} />
</Switch>
</main>
</React.Fragment>
);
}

Related

componentDidMount getting called multiple times in redux connected component

I have a reactjs+redux app in which app.js, the first component to be mounted is given below:
//all imports here
class App extends React.Component {
constructor(){
super();
this.state = {
loginState : false,
}
}
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log(user);
if(user.displayName&&user.photoURL&&user.email)
this.props.dispatch(login(user.displayName, user.photoURL, user.email));
else
this.props.dispatch(login(user.email.split("#")[0], "", ""));
this.setState({
loginState: true
});
}
else{
this.props.dispatch(changeLoading());
}
});
}
logout = () => {
firebase
.auth()
.signOut()
.then(() => {
this.setState({
loginState : false,
});
this.props.dispatch(logout());
this.props.history.push('/login');
})
.catch((err) => {
console.log(err);
});
};
render() {
return (
<>
<Switch>
{this.props.userName?(<Route
exact
path="/"
component={() => <Homepage userName={this.props.userName} />}
/>):(<Route exact path="/" component={Loading} />)}
<Route exact path="/login" component={Login} />
<Redirect to="/" />
</Switch>
</>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.userState.isLoggedIn,
userName: state.userState.userName,
email: state.userState.email,
photoURL: state.userState.photoURL
};
};
export default withRouter(connect(mapStateToProps)(App));
Below is Homepage.js:
class Homepage extends React.Component{
componentDidMount(){
console.log("component mounted");
this.props.dispatch(fetchPosts());
}
render(){
console.log("Homepage reached");
if(this.props.userName==='') return <Redirect to="/login" />
return(
<div className="container-fluid m-0" style={{paddingTop: '100px',paddingLeft: '0',paddingRight: '0'}}>
<div className="row m-0">
<div class="col-md-1"></div>
<Main userName={this.props.userName} />
<Leftside userName={this.props.userName} />
<div class="col-md-1"></div>
</div>
</div>
);
}
}
const mapStateToProps=(state)=>({})
export default connect(mapStateToProps)(Homepage);
And below is the reducer:
export const userState=(state={isLoading: true,isLoggedIn: false,userName: '', photoURL: ''},action)=>{
switch(action.type){
case 'LOGIN': {console.log("reached");return {...state,isLoading: false,isLoggedIn: true, userName: action.payload.userName, photoURL: action.payload.photoURL, email: action.payload.email}}
case 'LOGOUT': return {...state,isLoading: false,isLoggedIn:false,userName: '',photoUrl: ''}
case 'CHANGE': return {...state,isLoading: false}
default: return {...state}
}
}
Basically what is happening is that initially when the app opens, this.props.userName is empty and hence Loading component is loaded. Once the firebase returns the user details, they are dispatched to redux reducer. When this happens, state.userState.userName becomes available and Homepage is mounted. As expected, its componentDidMount method runs and posts are fetched and dispatched to the redux store( posts are stored in redux store). But then suddenly Homepage unmounts and mounted again and consequently, componntDidMount runs again. So, in total, there are two fetchPost requests.
I do not understand this behaviour. I have read that componentDidMount runs only a single time.
Please help me to remove this bug.
Thank You!

React using Hooks to handle Conditional Rendering of Links

I am trying to do some basic conditional rendering based on user login. I have my event handlers and axios call in a Login component.
const Login = () => {
const handleChange = event => {
setCustomerLogin({
...customerLogin,
[event.target.name]: event.target.value
});
};
const handleSubmit = e => {
e.preventDefault();
axios
.post("/api/Authentication", customerLogin)
.then(function(response) {
setCustomerLogin(response.data);
console.log(response);
})
.catch(function(error) {
console.log(error);
});
};
My Navbar component is very basic right now and just automatically renders my SignedOutLinks, which are the links I display before a user is logged in.
const Navbar = () => {
return (
<nav className="nav-wrapper blue darken-4">
<div className="container">
<Link to='/' className="brand-logo left">Cars4U</Link>
<SignedOutLinks />
</div>
</nav>
)
};
I would like to define my setCustomerLogin function in App.js and have my Login component call this value. This is my App.js file so far, I am just uncertain how to define the function in my App.js and set the state in my Login component
const [customerLogin, setCustomerLogin] = useState([
{ username: "", password: "" }
]);
function App() {
return(
<div className="App">
<Navbar />
<Switch>
<Route path='/login' component={Login}/>
<Route path='/signup' component={Signup}/>
</Switch>
</div>
);
}
You can pass the state setter(setCustomerLogin) and state value(customerLogin) down to your Login component as props:
const [customerLogin, setCustomerLogin] = useState([
{ username: "", password: "" }
]);
function App() {
return(
<div className="App">
<Navbar />
<Switch>
<Route path='/signup' component={Signup}/>
<Route
path="/login"
render={() =>
<Login
customerLogin={customerLogin}
setCustomerLogin={setCustomerLogin}
/>}
/>
</Switch>
</div>
);
}
Note that I used a little different syntax for routing the Login component, you are still going to get the same result, only that now you can pass in any props you want to the component to render. You can read more about that kind of routing here.
And then, you can access them in the Login component via props:
const Login = ({setCustomerLogin, customerLogin}) => {
const handleChange = event => {
setCustomerLogin({
...customerLogin,
[event.target.name]: event.target.value
});
};
const handleSubmit = e => {
e.preventDefault();
axios
.post("/api/Authentication", customerLogin)
.then(function(response) {
setCustomerLogin(response.data);
console.log(response);
})
.catch(function(error) {
console.log(error);
});
};

Lifting state up and update it

I have a question regarding lifting state up, I am aware that his is a frequently asked question on here but I hope someone will try to help me understand.
Problem: I have a login component consisting of a form component in which I do my fetch call to execute the login. I want to keep the token I get from my fetch call and save it in the App component so I can use this token and pass it down other pages/components.
So to begin with, I start with my code in my App.js / the parent component?
In here i manage my Routes, and this is also her that I want to store my state.
I pass down the token from this state to the Login component as seen on the Route that renders the Login component.
From what I read, props are not mutable so this actually doesn't work the way I thought out? I made the handleToken function but I will return to that.
export default class App extends Component {
state = {
token: "",
regNo: "",
};
handleToken = (token) => {
this.setState({ token: token})
}
render() {
console.log(this.state.token);
return (
<Router >
<div>
<Header />
<Navbar isLoggedIn={this.state.token} />
<Switch>
<Route exact path="/" render={() => <Home />} />
<Route path="/profile" render={() => <Profile userToken={this.state.token} />} />
<Route path="/login" render={() => <Login userToken={this.state.token} />} />
<Route path="/register" render={() => <Register />} />
<Route path="/carpage" render={() => <CarPage regNo={this.state.regNo} />} />
<Route path="/find-rental" render={() => <FindRental regNo={this.state.regNo} setRegno={this.setRegno}/>} />
<Route path="/rent-out-car" render={() => <RentOutCar />} />
<Route component={NoMatch} />
</Switch>
</div>
</Router >
);
};
Now we move down to the Login component. This component consist of multiple other components that makes up the login page. One of which is the FormContainer where the fetch call for the Login happens.
const Login = (props) => (
<Container fluid={true}>
<BrandRow>
</BrandRow>
<FormRow>
<FormContainer /* userToken={this.props.userToken} */ />
</FormRow>
</Container>
);
export default Login;
(Not sure why it wouldn't render it properly as a code sample, so it went into code snippet)
So as you can see in this Login component, I have the FormContainer which it were all the action happens.
Let me show you:
class FormContainer extends Component {
state = {
userName: '',
password: '',
};
handleChange = event => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({ [name]: value });
}
handleSubmit = event => {
event.preventDefault();
const credentials = { username: this.state.userName, password: this.state.password };
this.login(credentials);
}
login = credentials => {
const url = "https://fenonline.dk/SYS_Backend/api/login";
const postHeader = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(credentials)
};
fetch(url, postHeader).then(res => {
if (!res.ok) { throw Error(res.status + ": " + res.statusText + " | Wrong username or password!"); }
return res.json();
}).then(data => {
this.props.userToken = data.token;
alert("You have succesfully logged in!" + data.token);
this.props.history.push('/profile')
}).catch(error => alert(error));
}
render() {
const { userName, password } = this.state;
return (
<Container>
<Form onSubmit={this.handleSubmit}>
<Title>Sign In</Title>
<Input
type="text"
name="userName"
value={userName}
onChange={this.handleChange}
placeholder="Username.."
/>
<Input
type="password"
name="password"
value={password}
onChange={this.handleChange}
placeholder="Password.."
/>
<Button type="submit">Sign In</Button>
<Button onClick={() => this.props.history.push('/register')}>Register</Button>
</Form>
<Text>Rental service created by users for users.</Text>
</Container>
);
}
}
export default withRouter(FormContainer);
What you want to look at here is the login function, where I created the fetch call and this is where i get the token that i want to my lifted up state to store. Now i try and set it by saying this.props.userToken = data.token; but if props are immuteable how do I do this?
In order to lift state to your top level component, you need to pass your handleToken function into your login component as a prop.
<Route path="/login" render={() => <Login userToken={this.state.token} handleToken={this.handleToken} />} />
Then you will need to pass that into your FormContainer component as a prop as well.
<FormContainer handleToken={this.props.handleToken} />
Lastly, in your fetch, you'll need to call this method in the second .then() instead of trying to assign the token to a prop.
this.props.handleToken(data.token)
This will allow the state to be lifted up to the parent and then allow the token to be passed as a prop to your other components.

Update context value after API call

I'm using React 16.3 Context API, I'm setting loggedin: bool & user: Object value using context, also using PrivateRoute for logged in user.
Here is a brief code.
// AuthContext JS
import React from "react";
const AuthContext = React.createContext();
class AuthProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: false,
user : null
};
this.setAuth = this.setAuth.bind(this);
};
setAuth(isLoggedIn = false, userData = null) {
this.setState({
isLoggedIn: isLoggedIn,
user : userData
});
}
render() {
return (
<AuthContext.Provider
value={ {...this.state, setAuth: this.setAuth} }>
{ this.props.children }
</AuthContext.Provider>
);
}
}
const AuthUser = AuthContext.Consumer;
export {AuthContext, AuthProvider, AuthUser};
function PrivateRoute({component: Component, ...rest}) {
return (
<AuthUser>
{
({isLoggedIn}) => (
<Route
{ ...rest }
render={ props =>
(
isLoggedIn ? (
<Component { ...props } />
) : (
<Redirect
to={ {
pathname: "/login",
state : {from: props.location}
} }
/>
)
)
}
/>
)
}
</AuthUser>
);
}
// App JS
class App extends Component {
render() {
return (
<HashRouter>
<AuthProvider>
<Switch>
<Route exact path="/login" name="Login Page" component={ Login } />
<Route exact path="/register" name="Register Page" component={ Register } />
<Route exact path="/404" name="Page 404" component={ Page404 } />
<Route exact path="/500" name="Page 500" component={ Page500 } />
<PrivateRoute path="/" component={ DefaultLayout } />
</Switch>
</AuthProvider>
</HashRouter>
);
}
}
export default App;
// Login JS
class Login extends Component {
handleSubmit(values) {
const opts = {
"email" : "test#example.com",
"password": "test123"
};
let _this = this;
fetch("API_URL", {
method: "post",
body : JSON.stringify(opts)
})
.then(
(response) => {
return response.json();
}
).then(
(data) => {
_this.setState({
isAuth: true,
user : data.data.user
});
_this.props.history.replace("/dashboard");
}
);
}
render() {
console.log(this.state.isAuth);
return (
<AuthUser>
{
({isLoggedIn, setAuth}) =>
(
<Redirect to="/dashboard" />
) : ( <div > // Login Page </div>
)
}
</AuthUser>
);
}
}
How do I update/call setAuth function of consumer
If I call setAuth from render function, it will give warning & loop over setState
Any Help!
In the handleSubmit function in the Login file, instead of calling
this.setState({
isAuth: true,
user: data.data.user
});
you should call the setAuth function provided by the context and update the user auth and data in the context there:
this.context.setAuth(true, data.data.user)
In order to use this.context, you may need to change from using context consumer to contextType:
static contextType = AuthContext
You have implement a higher order component that help component consume context value as props.
The following withContextAsProps HOC provides an example:
function withContextAsProps(Context, Component) {
function WithContextAsProps(prop) {
return (
<Context>
{value => <Component {...value} />}
</Context>
);
}
const componentName = Component.displayName || Component.name || 'Component';
const contextName = Context.displayName || Context.name || 'Context';
WithContextAsProps.displayName = `With${contextName}Context(${componentName})`;
return WithContextAsProps;
}
In Login component, the HOC can be used to make isAuth and setAuth value from AuthUser context consumer available as props in the Login component.
class Login extends Component {
handleSubmit = values => {
//...
fetch("API_URL", {
method: "post",
body : JSON.stringify(opts)
})
.then(response => response.json())
.then(
data => {
this.props.setAuth(true, data.data.user);
this.props.location.assign("/dashboard");
}
);
}
render() {
return this.props.isAuth ?
<Redirect to="/dashboard" />
: <div>Login Page</div>;
}
}
export default withContextAsProps(AuthUser, Login);

SetState error when user login returns errors

I've created a login page that takes a user from public to authenticated routes which works well. If there is an error with login (eg. email not found) I get the error in console 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. Please check the code for the LoginForm component..
I think it might be related to how I use createContainer in the App.
I believe the problem is related to the way meteor passes Meteor.loggingIn(); equals true before it has heard back from the server. If there is an error the true quickly changes to false, which I think leads to the page reloading.
I would like to be able to use this.setState({ loginError: error.reason }); so I can tell the user what went wrong.
Any suggestions.
Path: App.jsx
const App = appProps => (
<Router>
<Grid className="main-page-container">
<Switch>
<Authenticated exact path="/" component={Home} {...appProps} />
<Public exact path="/login" component={Login} {...appProps} />
</Switch>
</Grid>
</Router>
);
App.propTypes = {
loggingIn: PropTypes.bool,
authenticated: PropTypes.bool
};
export default createContainer(() => {
const loggingIn = Meteor.loggingIn();
return {
loggingIn,
authenticated: !loggingIn && !!Meteor.userId()
};
}, App);
Path: Public.jsx
const Public = ({ loggingIn, authenticated, component, ...rest }) => (
<Route
{...rest}
render={(props) => {
if (loggingIn) return <div />;
return !authenticated ?
(React.createElement(component, { ...props, loggingIn, authenticated })) :
(<Redirect to="/" />);
}}
/>
);
Public.propTypes = {
loggingIn: PropTypes.bool,
authenticated: PropTypes.bool,
component: PropTypes.func
};
export default Public;
Path: LoginForm.jsx
export default class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
errors: {},
password: '',
loginError: ''
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox'
? target.checked
: target.value;
const name = target.name;
this.setState({[name]: value});
}
handleSubmit(event) {
event.preventDefault();
this.setState({
errors: {}
}, function() {
var data = {
email: this.state.email,
password: this.state.password
};
var email = this.state.email;
var password = this.state.password;
const errors = loginUserValidation(data);
if (errors) {
this.setState({errors: errors});
} else {
Meteor.loginWithPassword(email, password, (error) => {
if (error) {
this.setState({ loginError: error.reason });
}
});
}
});
}
render() {
return (
<div className="registration-form-container">
<Row>
<Col sm={8} smOffset={2} md={6} mdOffset={3}>
<div className="paper">
<Form onSubmit={this.handleSubmit}>
<section className="form-title">
<h3 className="text-center">Login</h3>
</section>
<hr />
<section className="form-content-login-or-registration">
{this.state.loginError &&
<div className="alert alert-danger">
<p>{this.state.loginError}</p>
</div>
}
<SingleInput
name={'email'}
inputType={'email'}
controlFunc={this.handleInputChange}
content={this.state.email}
placeholder={'Email'}
bsSize={null}
error={this.state.errors && this.state.errors.email}
/>
<SingleInput
name={'password'}
inputType={'password'}
controlFunc={this.handleInputChange}
content={this.state.password}
placeholder={'Password'}
bsSize={null}
error={this.state.errors && this.state.errors.password}
/>
</section>
<section className="form-buttons">
<Button type="submit" className="btn btn-primary" block>Login</Button>
</section>
</Form>
</div>
</Col>
</Row>
</div>
)
}
}
When Meteor.loginWithPassword changes your logginIn value to true, your <Public /> component unmounts your wrapped <LoginForm />
const Public = ({ loggingIn, authenticated, component, ...rest }) => (
<Route
{...rest}
render={(props) => {
if (loggingIn) return <div />; // <--- this right here
return !authenticated ?
(React.createElement(component, { ...props, loggingIn, authenticated })) :
(<Redirect to="/" />);
}}
/>
);
So loggingIn values changes causing <Public /> to re-render. Now that loggingIn is true, you render a div instead of the component that was, unmounting it and making setStateunavailable when the errors callback tries to invoke it.
EDIT: In response to your comment...
To prevent this you can handle the error display inside the <LoginForm />
Remove if (loggingIn) return <div />; from your <Route /> in your <Public /> component.
Inside your <LoginForm /> you can handle the error condition. Do this by making a component that displays your errors and include it in your <LoginForm /> component where you want it displayed. Make the <ErrorDisplay /> component return nothing if there are no errors.
Example <ErrorDisplay /> component
const ErrorDisplay = ({ errors }) => {
errors && <div className="error-container">{ errors }</div>
};
That is obviously barebones, but I hope it helps you understand!

Resources