ReactJS How to actively pass state values to components in App.js - reactjs

In my App.js I have the following code
class App extends Component {
constructor(props) {
super(props);
this.state = {
url: config.Url,
tables : [],
};
}
componentWillMount() {
axios.get(this.state.url + '/tables')
.then(response => {
if (response.data.status === '200') {
const tables = response.data;
this.setState({ tables });
}
})
.catch((error) => {
console.log('error ' + error);
});
}
render() {
return (
<Router>
<div className="App">
<Header tables={this.state.tables}/>
<Route exact path='/' component={Login} />
<Route exact path='/dashboard' component={Dashboard}/>
</div>
</Router>
);
}
}
export default App;
While calling the Header component the header state is not set and hence the values are not receiving inside the header. What should I change to get the table state values in my Header component? I tried with componentWillMount and componentDidMount functions and both gives same response.

Your code looks fine other than you need to use componentDidMount instead of componentWillMount. setState will have no effect in componentWillMount, which is why you aren't seeing any changes.
Also, take a look at this article: https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/

So, independent of componentDidMount and componentWillMount, on the first step you need validade table with is empty array with if (!this.state.tables.length) {, see full code:
import React from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
url: config.Url,
tables : [],
};
}
componentWillMount() {
axios.get(this.state.url + '/tables')
.then(response => {
if (response.data.status === '200') {
const tables = response.data;
this.setState({ tables });
}
})
.catch((error) => {
console.log('error ' + error);
});
}
render() {
if (!this.state.tables.length) {
return <div>Loading</div>;
}
return (
<Router>
<div className="App">
<Header tables={this.state.tables}/>
<Route exact path='/' component={Login} />
<Route exact path='/dashboard' component={Dashboard}/>
</div>
</Router>
);
}
}
export default App;

What you should do is to set initial state of tables to null and in render method check whether 'tables' is null or not. Because you are fetching remote data and by the time React renders the page, data may not be fetched and assigned to tables.
render() {
let tablesToProps = <p>Loading..</p>
if(this.state.tables) {
tablesToProps = <Header tables={this.state.tables} />
}
return (
<Router>
<div className="App">
{tablesToProps}
<Route exact path='/' component={Login} />
<Route exact path='/dashboard' component={Dashboard}/>
</div>
</Router>
);
}

Related

How to solve routing roblem on React

I'm trying to set routes for my application and i have problems at the beginning..
I would achieve that when user is not logged in, application should redirect him to '/signIn' page.
If user is logged in, application should take him into '/'
I don't understand it but my currentUser is null. So app should redirect me to signIn (condition) but it still goes to HomePage
Any hints?
Below is my code what i've been trying so far
class App extends React.Component {
constructor() {
super();
this.state = {
currentUser: null,
};
}
unsubscribeFromAuth = null;
componentDidMount() {
this.unsubscribeFromAuth = auth.onAuthStateChanged(user => {
this.setState({ currentUser: user });
});
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
console.log('Current state:', this.state);
return (
<div className="App">
<Switch>
<Route exact path="/" render={() => <HomePage currentUser={this.state.currentUser} />} />
<Route
exact
path="/signIn"
render={() => (this.state.currentUser ? <Redirect to="/" /> : <SignInPage />)}
/>
</Switch>
</div>
);
}
}
You should add verification user in route home page not login page:
class App extends React.Component {
constructor() {
super()
this.state = {
currentUser: null
}
}
unsubscribeFromAuth = null
componentDidMount() {
this.unsubscribeFromAuth = auth.onAuthStateChanged(user => {
this.setState({ currentUser: user })
})
}
componentWillUnmount() {
this.unsubscribeFromAuth()
}
render() {
const redirection = this.state.currentUser ? <Redirect to="/" /> : <Redirect to="/signIn" />
return (
<div className="App">
{redirection}
<Switch>
<Route
exact
path="/"
render={() => (<HomePage currentUser=
{this.state.currentUser} />)}
/>
<Route exact path="/signIn" component={SignInPage} />
</Switch>
</div>
)
}
}

ReactJS Router not rendering component

I am having trouble with the Route path <Route path="customers/:id" render={(props) => <CusDataForm {...props}/>}/> in the code below:
import CusDataCtrl from './cusdata/CusDataCtrl'
import CusDataForm from './cusdata/CusDataForm'
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/customers" component={CusDataCtrl} />
<Route path="customers/:id" render={(props) => <CusDataForm {...props}/>}/>
</Switch>
</BrowserRouter>
);
}
}
export default App;
if I use <Route exact path="/customers/:id" component={CusDataForm} /> the component does render correctly; however, I need to pass some props down to this component.
My calling component is defined like so:
class CusDataGrid extends Component {
constructor(props) {
super(props)
this.state = {data: []}
}
componentDidMount() {
let me = this;
dbFetch("customers",data => me.setState({data:data}));
}
callEdit = e => {
let recid = e.target.getAttribute("data")
this.props.history.push("/customers/"+recid);
}
render() {
const rows = this.state.data.map((row, ndx) => {
return (
<div key={ndx}><button data={row.recordid} className="waves-effect waves-light btn-small" onClick={this.callEdit}>Edit</button></div>
);
});
return (
<div id="cusdata"><div className="data-scrollable">{rows}</div></div>
);
}
};
export default CusDataGrid;
and my target component is:
class CusDataForm extends Component{
componentDidMount = () =>{
this.setState({id: this.props.id ? this.props.id : ""});
}
render(){
return(<div>HELLO</div>)
}
}
export default CusDataForm;
Please let me know what I am doing incorrectly. Thanks!
you can use hook useParams for it
<Switch>
<Route path="/:id" children={<Child />} />
</Switch>
function Child() {
// We can use the `useParams` hook here to access
// the dynamic pieces of the URL.
let { id } = useParams();
return (
<div>
<h3>ID: {id}</h3>
</div>
);
}
official documentation

How to set global prop in react apollo client

I am working with react and react-apollo client.
Here is my main route file
const PrivateRoute = ({ component, isAuthed, ...rest }) => {
console.log({ isAuthed })
return (
<Route {...rest} exact
render = {(props) => (
isAuthed ? (
<div>
{React.createElement(component, props)}
</div>
) :
(
<Redirect
to={{
pathname: '/login',
state: { from: props.location }
}}
/>
)
)}
/>
)
}
class App extends Component {
state = {
isAuthed: false
}
async componentWillMount() {
this.setState({ isAuthed: true })
}
render() {
const { isAuthed } = this.state
return (
<div style={{ direction: direction }}>
<Header {...this.props} history={history}/>
<Router history={history}>
<Switch>
<Route exact path="/" render={() => <Redirect to="/dashboard" />} />
<Route path="/login" component={Login}/>
<PrivateRoute isAuthed={isAuthed} path="/dashboard" component={Dashboard} />
<PrivateRoute isAuthed={isAuthed} path="/AdminManagement" component={Admin} />
</Switch>
</Router>
</div>
)
}
}
export default compose(
graphql(SET_SESSION, { name: 'setSession' })
)((withNamespaces)('common')(App))
Now when I do login inside the login component I need to set isAuthed to true which is inside my main route file(above one)
How can I do this with react apollo client?
Unfortunately, there's no such thing as a "global" prop in React or Apollo. If you want to achieve something similar to your example (i.e. update the state on the root component from your login component), have you considered passing a method down to said component and firing it when your GraphQL mutation resolves?
I'm going to take a stab at this, but please note this is all pseudo code and just outlines one of the many approaches you could take to address this:
App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userInfo: {},
authed: false,
}
this.modifyUserInfo = this.modifyUserInfo.bind(this);
}
modifyUserInfo(userInfo) {
this.setState(state => ({
...state,
userInfo,
authed: true,
}))
}
render() {
return (
// Render all your routes and everything...
<LoginComponent loginCb={modifyUserInfo} />
)
}
}
Login Component
const Login = props => {
return (
<Mutation mutation={loginMutation}>
{(login) => {
<form onSubmit={(e) => {
e.preventDefault();
login()
.then(res => {
if (res.userInfo) {
props.loginCb(res.userInfo);
}
})
.catch(err => {
console.log(err)
})
}}>
{/* Add the rest of your login form */}
<button type="submit"/>
</form>
}}
</Mutation>
)
}
Rather than storing your user authentication information in your root state, have you considered using your Apollo Cache and injecting the user information into the relevant components? Like I said, there are many, many different ways to approach this.

Programmatically routing after login in React

I am new in React and I was trying to route the authenticated user to the main page. Unfortunately the user remains on the login page, although he logged in successfully. Here the relevant snippet of main.js which handles the routing part.
render(){
const {user} = this.props
{console.log("Logged in user: " + JSON.stringify(this.props.user.email))}
{console.log("Logged in : " + JSON.stringify(this.props.user.loggedIn))}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
this.props.user.loggedIn === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
}} />
)} />
)
<BrowserRouter>
<main>
<Button icon='log out' floated='right' color='blue' onClick={()=>this.userLogout(user)}/>
<Switch>
<PrivateRoute exact path='/' component={withRouter(StudentsMain)}/>
<Route exact path='/about' component={About}/>
<Route exact path='/login' component={withRouter(Login)}/>
<PrivateRoute exact path='/settings' component={withRouter(Settings)}/>
<PrivateRoute exact path='/assessment/:id' component={Assessment}/>
</Switch>
</main>
</BrowserRouter>
And here the relevant part of the login component...
onValidSubmit = (formData) => {
console.log("logging in")
this.props.logInUser(formData)
this.setState({loggedIn: true})
};
onFormInfo = () => {
this.setState({
showInfo:true
})
}
.....
render(){
if (this.state.loggedIn === true) {
console.log('Redirecting to Main Page')
return <Redirect to='/' />
}
Perhaps somebody can give me a hint, why the user remains on the login page. If I change the url in the browser manually the routing works as expected. Thanks, Arnold
In general, you should lift the state up so that you'll trigger a re-render in the right place. In this example, you can see the difference between triggering the state in different places.
import React, { Component } from 'react';
import { render } from 'react-dom';
class Child extends React.Component {
state = {
loggedIn: false,
}
toggle = () => {
this.setState({ loggedIn: !this.state.loggedIn })
}
render() {
return <p onClick={this.props.onClick || this.toggle}>I am {this.state.loggedIn ? 'in' : 'out'}</p>
}
}
class App extends Component {
state = {
loggedIn: false,
}
toggle = () => {
this.setState({ loggedIn: !this.state.loggedIn })
}
render() {
return (
<React.Fragment>
<Child onClick={this.toggle} />
<Child />
</React.Fragment>
);
}
}
render(<App />, document.getElementById('root'));
Here is a live example.

Show detailed item view using router path

My Movielist component looks a bit like:
componentDidMount() {
fetch('http://localhost:3335/movies')
.then(function(response) {
return response.json()
}).then((movies) => {
this.setState({ movies });
}).catch(function(ex) {
console.log('parsing failed', ex)
})
}
renderMovie(movie) {
return (
<Movie movie={movie} key={movie.id}></Movie>
);
}
render() {
return (
<div className="movies columns is-multiline">
{ this.state.movies.map(this.renderMovie) }
</div>
);
}
My Movie component has a <Link> :
shouldComponentUpdate() {
debugger;
if (this.props.params) {
let activeMovie = find(this.state.movies, {'id': this.props.params.id});
debugger;
this.setState({ movies: activeMovie });
}
}
render() {
return (
<Link to={`/movies/${this.props.movie.id}`}/>
...
In my index.js I've setup the following routes:
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/movies/(:id)" component={Movie}/>
</Route>
</Router>
),
document.getElementById('root')
);
I would like every time I click on The <Link>, to get the param.id and show only that Movie (with something like lodash find):
let activeMovie = find(this.state.movies, {'id': this.props.params.id});
Unfortunately this.props.params is undefined.
basically just have only one instance of the Movie component loaded in memory. (ideally without losing the previous movies so avoiding a new call everytime I go to the list view)
That's like a Todo app but with Movies instead of Todos..
Your route params are not passed to your Movie Component and you are trying to access it in Movie Component. The params only passed to Movielist Component.
MovieList
renderMovie(movie) {
return (
<Movie
params={this.props.params}
movie={movie}
key={movie.id}
></Movie>
);
}
You should remove the function shouldComponentUpdate in your Movie Component. Because when the url changed. The params will be passed to your MovieList Component and componentWillReceiveProps will receive the params. So you can directly update the MovieList there and you don't need to pass the params to your Movie Component.
MovieList
componentWillReceiveProps(nextProps) {
let activeMovie = find(this.state.movies, {'id': nextProps.params.id});
this.setState({ movies: activeMovie, }
}
But this will lose your movies state. So I think you need to add a nested route like below and have a separate page for Movie Details.
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/movies" component={MovieList}>
<Route path="/:id" component={Movie}>
</Route>
</Route>
</Router>
),
document.getElementById('root')
);
const {
Router,
Route,
IndexRoute,
Redirect,
Link,
IndexLink,
browserHistory
} = ReactRouter;
const {
Component,
} = React;
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="container">
{ React.Children.toArray(this.props.children) }
</div>
)
}
}
class MovieList extends Component {
constructor(props) {
super(props);
this.state = {
movies: []
};
this.renderMovies = this.renderMovies.bind(this);
}
componentDidMount() {
this.setState({
movies: [
{ id: '1', name: 'Fantastic Beasts And Where To Find Them'},
{ id: '2', name: 'Ouija: Origin Of Evil'},
{ id: '3', name: 'Marvel\'s Doctor Strange'}]
})
}
renderMovies() {
return this.state.movies.map(movie => <li key={movie.id}><Link to={`/js/${movie.id}`}>{movie.name}</Link></li>)
}
render() {
return (
<div>
<h1>Movies</h1>
<ul>
{this.renderMovies()}
</ul>
</div>
)
}
}
class Movie extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>{this.props.params.movieId}</div>
);
}
}
ReactDOM.render((
<Router history={browserHistory}>
<Route path="/js" component={App}>
<IndexRoute component={MovieList} />
<Route path="/js/:movieId" component={Movie} />
</Route>
</Router>
), document.getElementById('root'))
I have created a working jsbin example. Hope it helps.

Resources