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.
Related
Well, I am trying to set the state of my App.js Component in the login.js component to true. The code I have in my App.js is this:
state = {
isAuth: false
};
And this is how I try to set the isAuth to false in my login.js Component:
statusCode:{
200: function(){
this.App.state.isAuth = true;
console.log('it's working!')
}
}
This is how I import the App Component:
import App from './App';
If the user successfully login the status code should be 200 and after receiving this status code the isAuth of the App.js component should be set to true. Don't know why this is not working. I'm getting the error TypeError: Cannot read property 'state' of undefined.
It's not possible to setState directly from another component. So, one solution is to pass a function that handles the setState to the other component. For example, in your case:
App.js
import Login from "path/to/Login"
class App extends Component {
state = {
isAuth: false
}
handleIsAuthChange = (isAuth) => {
this.setState({ isAuth })
}
render () {
return <Login handleIsAuthChange={this.handleIsAuthChange} />
}
}
Then you can access the function by props
Login.js
class Login extends Component {
setIsAuth = (isAuth) => {
this.props.handleIsAuthChange(isAuth)
}
// Your Code...
}
Another possible solution is to use the Context API, but this is certainly the easiest way to do it.
If you're still a little lost on this, I recommend you to take a look at this answer as well https://stackoverflow.com/a/55028734/11194008
Basically I am making a basic react App that is grabbing some data from a DB and I am stuck on the basic setup.
My intention is to have my state contain the response from my server querying my databse.
My response is 100% working and sending the data back as expected from the axios call, however the state is never getting update.
EDIT : I am attempting to pass the movies down the chain to a Component called MovieList, I have provided the code for that as well.
App.jsx
import React from 'react';
import axios from 'axios';
import MovieList from './MovieList.jsx';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {allMovies: []};
}
componentDidMount() {
var that = this;
axios.get('http://localhost:3000/api/movies')
.then( res => {
that.setState({allMovies: res.data});
})
.catch( err => {
console.log(`Err # [ App.jsx - componentDidMount ] ::: ${err}`);
});
}
render() {
return (
<div>
<h1>Hello World!</h1>
<MovieList movies={this.state.allMovies} />
</div>
)
}
}
MovieList.jsx
import React from 'react';
class MovieList extends React.Component {
constructor(props) {
super(props);
this.state = {};
console.log(this.props); //EMPTY OBJECT MOVIES DIDN'T GET INTO PROPS
}
render() {
return (
<div>
<h1>Test</h1>
</div>
)
}
}
export default MovieList;
NOTE : I also logged the props on mount and attempted to render them and they were empty.
Basically if I try to pass down this.state.allMovies or console.log it, its always just the initial empty array.
Maybe I don't understand how async setting the state can be done? I took a similar approach on my last school project and it seemed to work fine.
You don't await the axios promise to resolve, so you simply are logging what the state is when the component mounts. Use one of the following to log updated react state.
Use componentDidUpdate to log the updated state.
componentDidMount() {
console.log(this.state);
}
Use the setState callback function to log the state
componentDidMount() {
var that = this;
axios.get('http://localhost:3000/api/movies')
.then( res => {
console.log(res.data);
that.setState(
{ allMovies: [res.data] },
() => console.log(this.state), // <-- setState callback function
);
})
.catch( err => {
console.log(`Err # [ App.jsx - componentDidMount ] ::: ${err}`);
});
}
You'll never see it in your constructor, because when your component is instantiated, it's done so with an empty array.
You will see it if you do a console.log(this.props) in componentDidUpdate or render however.
This is because when App is mounted, your component passes a movies prop of [] to MovieList. After the movies return from the server (and you update the state of App), App will render again and pass the array returned from the server, causing your MovieList component to render again. It's constructor won't be called, because it's already instantiated, but MovieList will call componentDidUpdate and render again.
class MovieList extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
console.log(this.props); // you'll see this get logged twice - once with an empty array for movies and once with the movies returned from the server.
return (
<div>
<h1>Test</h1>
</div>
)
}
}
I have seen much more cases related to redirecting users in react applications and every case was just a different approach to the solution. There are some cases, where redirecting has occurred in actions like this`
export const someAction = (values, history) => async dispatch => {
const res = await someAsyncOperation(props);
history.push('/home');
dispatch(someAction);
}
In this example history object (form react-router) is being passed in react component. For me, this approach is not acceptable.
There is also a special Redirect from react-router.
After then I have already searched many articles and couldn't just find anything.
So in your opinion, what's the best practice for redirecting and where to handle such kind of processes ?
In React, you usually achieve redirects in the componentDidUpdate of your components.
In the case of async actions, you will check a flag stored in the Redux store, generally a boolean like isFetching, isCreating, isUpdating, etc…, which will be modified by the actions.
Simple example:
class EditUser extends Component {
compondentDidUpdate(prevProps) {
if (prevProps.isUpdating && !this.props.isUpdating) {
// ↑ this means that the async call is done.
history.push('/users')
}
}
updateUser() {
const modifiedUser = // ...
this.props.updateUser(modifiedUser)
// ↑ will change state.users.isUpdating from false to true during the async call,
// then from true to false once the async call is done.
}
render() {
// ...
<button onClick={this.updateUser}>Update</button>
// ...
}
}
const mapStateToProps = (state, props) => ({
userToEdit: state.users.items.find(user => user.id === props.userId)
isUpdating: state.users.isUpdating,
})
const mapActionsToProps = {
updateUser: usersActions.updateUser,
}
export default connect(mapStateToProps, mapActionsToProps)(EditUser)
The next step is usually to add another flag in your Redux store to track if the async calls are successful or not (e.g. state.users.APIError, in which you can keep the error returned by the API). Then you achieve the redirect only if there are no errors.
We mostly redirect a user due to when user logged in or when sign out. For example here's basic requireAuth HOC component to check if user is logged in or not and redirect him to another place.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};
There are two position to check if user is logged in
When the first time that component mount - in componentDidMount()
When user try to sign in , log in or sign out - in componentDidUpdate()
Also in your code sample, history.push is in an action creator. Action creators belongs to redux side. Keep redux & react separate.
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
I have recently been trying to play with APIs in React.js,
I believed the below code would have worked, based on a tutorial from https://reactjs.org/docs/faq-ajax.html, However when I run this code I keep getting
TypeError: Cannot read property 'map' of undefined
Below is my code, this is a component called DOTA and is exported to my App.js
import React from 'react';
const API_KEY ="some-api-key";
const DEFAULT_QUERY = 'redux';
class DOTA extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
info: [],
};
}
componentDidMount() {
fetch(API_KEY + DEFAULT_QUERY)
.then(response => response.json(console.log(response)))
.then((result) => {
console.log(result)
this.setState({
isLoaded: true,
info: result.info
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, info } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{this.props.info.map(item => (
<li key={ item.all_word_counts}>
</li>
))}
</ul>
);
}
}
}
export default DOTA
App.js
import React, { Component } from 'react';
import DOTA from './components/DOTA/DOTA'
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<DOTA />
</div>
);
}
}
export default App;
I have looked on here already for an answer but I can't seem to find someone with the same issue. Do I need to run .map() outside of JSX? Above the render? Or am I just missing something?
Thanks!
DOTA component does not get any props but you are trying to map over this.props.info. Actually you are destructing info from your state top of your render.
Here how this works. info is in the state and you are destructing it as:
const { error, isLoaded, info } = this.state;
This grabs error, isLoaded and info from state and assign it variables. So, you can use info instead of this.state.info. Actually you are using error like this top of if block.
So, use it in the related part like:
<ul>
{info.map(item => (
<li key={ item.all_word_counts}>
</li>
))}
</ul>
But, with your current code, there is not any value inside your lis. I hope your are just testing right now.
Update after comments
Your problem seems with your data fetching. As long as you have an empty info state this value can't be undefined and your map works. This is how we generally avoid those map of undefined errors. If we can't have an empty state then we do conditional rendering.
If you want to test this case just comment out your componentDidMount method and try your code. It works. Of course it says Loading since you are not setting it to true. If you manually set it to true then you will see an empty page but not an error.
Here is a working example with your code but with another fetch:
https://codesandbox.io/s/q9jx3ro7y9
So, you should debug your fetch.