React Nav Bar Not Rerendering With New State - javascript

I'm making a nav bar in react that shows different links based on whether or not the user is logged in. (We use firebase for account stuff.)
Here's what I have:
async function isLoggedIn() {
return await UsersManagement.isLoggedIn();
}
export default class Navigation extends React.Component {
constructor(props) {
super(props);
this.state = ({ loggedIn: isLoggedIn() });
}
componentDidMount() {
this.setState({ loggedIn: isLoggedIn()})
}
render() {
return (
{this.state.loggedIn ? (
<LoggedInLinks />
) : (
<NotLoggedInLinks />
)}
}
}
I tried removing state from this component, using the function UserManagement.isLoggedIn() directly. That function works. I can see through print statements that it returns the proper userid or null when not logged in.
So what am I missing? Is there a better way to create a nav bar in react that changes based on logged in status?
Thank you!

Related

login not working properly in Login state update not re-rendering

I'm integrating an axios login API for my app. app.js is the parent component,login.js is the child.
The idea was to declare a state isAuthenticated with false as defaultvalue. and then in the app.js with an if-else render all the components only if isAuthenticated is true. In else the login component will be displayed. isAuthenticated will be set to true after successfull login. but after successfull login login isAuthenticated is updated to true but the if part is not getting rendered. it just shows the login. shouldn't the state update initiate a re-render?
I'm just starting on React. What am I doing wrong guys. Any help would be much appreciated.
on clicking login post api the response is 200 ok. I'm using an if to change the isAuthenticated in the parent app.js. isAuthenticated is set to false by default. all works okay but the if statement in app.js that checks this.state.isAuthenticated is not re-rendering.
this is the main component App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state= {
isAuthenticated:false,
}
}
setAuth=this.setAuth.bind(this);
Logout=this.Logout.bind(this);
setAuth(auth){
console.log('setauth',auth);
this.state.isAuthenticated=true;
console.log('isauth',this.state.isAuthenticated)
//this.forceUpdate()
}
Logout(){
this.setState({isAuthenticated:false});
message.success('Logout Successful');
}
render() {
console.log(this.state.isAuthenticated,"before if");
if(this.state.isAuthenticated) {
return(
<div>
-------------content--------------
</div>
)} return (
<WrappedLogin setAuth={this.setAuth}/>
);
}
}
this is the child component
class Login extends React.Component {
constructor(props) {
super(props);
this.state= {
loginStatus:[],
}
}
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log(' values of form: ',values );
axios.post('http://192.168.137.1:8000/user/login/',values)
.then(res => {
console.log(res);
console.log(res.data.status);
if (res.data.status==200)
{this.props.setAuth(true)
console.log('if');}
else
console.log('password incorrect');})
render() {
return (
);
}
}
const WrappedLogin = Form.create({ name: 'normal_login' })(Login);
export default WrappedLogin;
this is what I'm getting on console
values of form: {outlook_id: "aashik1", password: "Be$t^197901*"}
{data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
200
setauth true
isauth true
if
You shouldn't modify state directly, it will not re-render your component. Instead of
this.state.isAuthenticated = true;
you should write
this.setState({ isAuthenticated: true });
https://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly

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

Get default state value by receiving prop data - React

I'm new to react.js.
I'd like to get default value of state following_status by receiving props.user.following_status.
I'm passing user object ( user = { following_status: 'following', id:123 } ) to ReactionButton component. ReactionButton component is looks like this:
class RelationButton extends React.Component {
constructor(props){
super(props);
console.log(props.user.following_status) # undefined!!!
this.state = {
following_status: props.user.following_status
}
...
render() {
if (this.state.following_status == 'following') {
<UnFollowBtn/>
} else {
<FollowBtn/>
}
}
RelationButton was called by UserCardHeader component.
const UserCardHeader = (props) => {
const user = props.user;
return(
<header className="user-card--full__header">
<RelationButton user={user}></RelationButton>
</header>
)
}
I don't understand why console.log(props.user.following_status) returns undefined. I googled many websites like those:
React component initialize state from props
accessing props inside react constructor
those answers suggest
class FirstComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
x: props.initialX
};
}
}
but this didn't work for me.
If I add componentWillReceiveProps to the codes above,
componentWillReceiveProps(props){
console.log(props.user.following_status) #=> "following"
this.setState({following_status: props.user.following_status})
}
everything works well. However I think it's weird solution and sometimes doesn't work. Why can't I receive object props in constructor(props) {} section?
Without the full code, we can't tell what's wrong but it is obvious that following_status comes asynchronously to the component and that's why is not accessible right away in the constructor.
To somehow fix it you can detect if props have changed and reset state accordingly in componentDidUpdate.
class RelationButton extends React.Component {
constructor(props){
super(props);
console.log(props.user.following_status) # undefined!!!
this.state = {
following_status: props.user.following_status
}
}
componentDidUpdate(prevProps) {
if(prevProps.user.following_status !== this.props.user.following_status) {
this.setState({ following_status: this.props.user.following_status })
}
}
render() {
// you forgot about return statements :
if (this.state.following_status == 'following') {
return <UnFollowBtn/>
} else {
return <FollowBtn/>
}
}
}

React Navigation: Call a function of screen from its Navigator - Is it possible?

I've been trying to call a function which is in a screen of a navigator from its screen.
To clarify the point, here is a snippet of my code...
//ScreenA.js
export default class ScreenA extends React.Component {
showWarning(){
this.setState({showWarning: true});
setTimeout(function() {
this.setState({showWarning: false});
}.bind(this), 3000);
}
render() {
return (
<View style={{backgroundColor : this.state.showWarning ? "#red" : "#blue"}}>
{this.state.showWarning && <Warning />}
</View>
)
}
}
//Nagigator.js
default export const Navigator = StackNavigator({
ScreenA: {screen: ScreenA},
ScreenB: {screen: ScreenB},
});
//App.js
export default class App extends React.Component {
handleSubmit(qrCode){
if(qrCodeIsInvalid){
this.navigator.ScreenA.showWarning();
//This is just a sudo code.
//How do we call ScreenA.showWarning here?
}
}
render() {
let props = {/*some props here*/};
return (//This "ref" stops the application without describing any reason
<Navigator screenProps={props} ref={nav => { this.navigator = nav; }}/>
);
}
}
There is an example of how to call a function from a navigation header, but not from the class which exports the navigator.
I thought that each screen can be accessed via ref, but this causes an error without explaining what's happening.
Has anyone encountered a similar situation?
Any advice will be appreciated.
P.S.
# Nimrod Argov
Here are details of what I've been trying to achieve.
ScreenA has a QR code reader and submit function, which submits QR codes to App.js.
App.js has handleSubmit function, where submitted QR codes are sent to a server and labelled as either valid or invalid.
If a submitted QR code turns out to be invalid, ScreenA has to show a warning message and change its background colour for 3 seconds.
It might be achieved by having App.js pass a prop {showWarning:true} to ScreenA and pass {showWarning:false} in 3 seconds.
However, I thought it would be ScreenA's responsibility to change its background colour. Thus, I set setTimeout and setState in the showWarning().
I did it this way:
ScreenA.js
While navigate to ScreenB, including the function you want to call.
export default class ScreenA extends React.Component {
constructor(props) {
super(props);
this.doSomething = this.doSomething.bind(this);
}
doSomething() {
this.setState({blah: true});
}
navigateToB() {
this.props.navigation.navigate('ScreenB', {
doSomething: this.doSomething,
});
}
}
ScreenB.js
So you can do this in ScreenB.
const { state, setParams, navigate } = this.props.navigation;
const params = state.params || {};
params.doSomething();

React update parent component when child changes the Context

I have four React components:
An overall parent (App or similar)
A Header child, called by App
A Profile page
A LogoutLink, called by Profile
I am implementing a User authentication/login system using Auth0. When the user logs in (via a Login button in the Header), App changes the Context of the User object to include all the data retrieved from Auth0. This user data is then accessible to any part of the system which requires it.
When logged in, the UI automatically updates (using Context changes) so that the Header is now showing "Hey there {name}" rather than "Login" as before. This is also a link leading to the Profile page/component (using React Router's <Link to="/profile></Link> element).
On the Profile page there is a LogoutLink. When clicked, this logs the user out, and returns to the home page. It should also update the UI automatically to change the message in the Header back from "Hey there {name}" to "Login". This is done by Context changes again. However, this feature doesn't actually work - the user is successfully logged out, but to see the change described just above, the user needs to refresh the whole page. this.context.user is not being updated and sent back to Profile and Header. I know this is because of Redux and it's one-way data flow (i.e data can only go downwards, not up), but I need to find a way around it.
Here is the basic code I have:
LogoutLink.js
export default class LogoutLink extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
user: null,
};
}
static propTypes = {
value: React.PropTypes.string,
}
static contextTypes = {
user: React.PropTypes.object,
} // get context so this.context is available to get initial user data (before logout)
static childContextTypes = {
user: React.PropTypes.object,
}
getChildContext() {
return {user: this.state.user}
} // these two are for updating context
componentWillMount() {
this.setState({ user: this.context.user });
} // set the internal LogoutLink state
onClick() {
this.setState({ user: null }); // set the internal user state to null following logout
}
renderLogoutLink() {
const {value} = this.props;
const {user} = this.state;
if (user != null) {
return <Link to="/profile" onClick={this.onClick}>{value}</Link>
} else {
return <span>You're already logged out!</span>
}
}
render() {
return <span>{this.renderLogoutLink()}</span>
}
}
Header.js:
export default class Header extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.showLock = this.showLock.bind(this); // lock is the Auth0 module responsible for Login, also passed down by context
}
static contextTypes = {
user: React.PropTypes.object,
lock: React.PropTypes.object,
}
showLock() {
const {lock} = this.context;
lock.show();
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
if (this.context.user == null && nextContext.user != null) {
return true;
} else if (this.context.user != null && nextContext.user == null) {
return true;
}
return false;
} // after the LogoutLink is clicked, this still returns false
renderLoginButton() {
const {user} = this.context;
if (user) {
const name = user.nickname;
return <Link to="/profile">Hey there {name}!</Link>
} else {
return <button onClick={this.showLock}>Login</button>
}
}
render() {
return (
<header>
{this.renderLoginButton()}
</header>
);
}
}
I am following the official React docs about Context and updating it, found here: https://facebook.github.io/react/docs/context.html
As I said I know why this is not working: data can only be sent one way. But I need to find a way to make this work, and to be honest I think I've been staring at this too long and am now out of options and my brain is a bit frazzled.
Found a solution to this issue. In my App.js code, which is setting the initial context for the User item, I have added a method onto the User to set the User to null, which then trickles down through the app. In the Logout link it calls this method. Here is the code:
In App.js:
profile.logout = () => {
this.setState({ profile: null });
}
the getChildContext() method then sets the user context from this state change:
getChildContext() {
return {
user: this.state.profile
};
}
In LogoutLink.js:
onClick() {
const {user} = this.context;
user.logout();
}
The React "context" feature is useful, but also complicated and quirky, which is why there's still a number of cautions around using it. In particular, I believe that components that return false from shouldComponentUpdate will cause lower components to not update if the context contents change.
My advice would be to use context as little as possible, and only for data that does not change. Put changing data, such as the current user, into your Redux xtate.

Categories

Resources