I have a small React app which has a header component shared across the app from the index.js where the routing is set. I want to check on a specific page (Admin.js) if I'm logged in (Facebook auth already done with Firebase and working fine) and if so, show on the header component the log out button and Facebook profile pic.
index.js (imports omitted):
const Root = () => {
return (
<div>
<Header />
<main>
<Router>
<Switch>
<Route path="/" component={App} exact />
<Route path="/admin" component={Admin} exact />
</Switch>
</Router>
</main>
</div>
);
};
render(<Root/>, document.querySelector('#root'));
Header.js:
import React from 'react';
class Header extends React.Component {
render() {
return (
<header className="header">Header Home
<img src={image prop here or something...} alt=""/>
</header>
)
}
}
export default Header
Admin.js
class Admin extends React.Component {
constructor() {
super();
this.addPicture = this.addPicture.bind(this);
// getinitialstate
this.state = {
pictures: [],
uid: null,
avatar: ''
}
}
// firebase syncing
componentWillMount() {
this.ref = base.syncState('pictures', {
context: this,
state: 'pictures',
});
}
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
this.authHandler(null, { user });
}
})
}
authenticate() {
firebase.auth().signInWithPopup(provider).then(() => {this.authHandler});
}
logout = () => {
firebase.auth().signOut().then(() => {
this.setState({
uid: null,
avatar: ''
});
});
}
authHandler(err, authData) {
console.log(authData)
if (err) {
return;
}
this.setState({
uid: authData.user.uid,
avatar: authData.user.photoURL
});
}
renderLogin() {
return (
<nav>
<h2>Please log in to access the Admin Area</h2>
<button className="c-form__btn" onClick={() => this.authenticate()}>Log in</button>
</nav>
)
}
addPicture(e) {
e.preventDefault();
const picsRef = firebase.database().ref('pictures');
const picture = {
title: this.title.value,
url: this.url.value,
category: this.category.value
}
picsRef.push(picture);
this.picForm.reset();
}
removePicture = (key) => {
const pictures = {...this.state.pictures};
pictures[key] = null;
this.setState({ pictures });
}
renderTable = () => {
return Object
.keys(this.state.pictures)
.map(key => <Table key={key} index={key} details={this.state.pictures[key]} removePic={() => this.removePicture(key)}/>)
}
render() {
const logout = <button className="c-form__btn secondary" onClick={this.logout}>Log Out!</button>
// check if ther're no logged id at all
if(!this.state.uid) {
return <div>{this.renderLogin()}</div>
}
// check if they are the owner of the app
if(this.state.uid !== USER_UID) {
return (
<div>
<h3>Access not allowed!</h3>
{logout}
</div>
)
}
return (
<div>
<h1>Admin</h1>
<p>{logout}</p>
<img src={this.state.avatar} alt="User" style={{width: '50px'}}/>
<form ref={(input) => this.picForm = input} className="c-form" onSubmit={(e) => this.addPicture(e)}>
<div className="c-form__field"><input ref={(input) => this.title =input} type="text" placeholder="title" className="c-form__input"/></div>
<div className="c-form__field"><input ref={(input) => this.url =input} type="text" placeholder="Image url" className="c-form__input"/></div>
<div className="c-form__field">
<select ref={(input) => this.category =input} className="c-form__input">
<option value=" " disabled>Select a category</option>
{catOptions()}
</select>
</div>
<div className="c-form__field"><button className="c-form__btn" type="submit">Add Item</button></div>
</form>
<div className="table">
<div className="table__row t_header">
{tableHeader()}
</div>
{this.renderTable()}
</div>
</div>
)
}
}
export default Admin
How do I show the logout button and the Facebook profile pic (this.state avatar) on the Header component?
You need to lift the state up. For example:
class Root extends React.Component {
state = {
isLogged: false
img: null,
username: '',
}
login = (username, img) => {
this.setState({
img,
username,
isLogged: true,
});
}
logout = () => {
this.setState({
img: null,
username: '',
isLogged: false,
})
}
render() {
return (
<div>
<Header
username={this.state.username}
img={this.state.img}
isLogged={this.state.isLogged}
logout={this.logout}
/>
<main>
<Router>
<Switch>
<Route path="/" component={App} exact />
<Route
exact
path="/admin"
render={props => <Admin {...props} login={this.login} />}
/>
</Switch>
</Router>
</main>
</div>
);
}
};
Now you can update state in Root with updater function passed to Admin. From now on you just pass props to whatever you want. You get the idea...
Related
I want to pass a function to a component through a Route , I can do it with direct children but when it comes to Routes i can't figure it how to do it.
look at the code below , i want to pass "updateUserState" function to Profile Component , the function works properly in Header component but it's not working in Profile component which lives inside the Routes .
class App extends React.Component {
updateUserState = (currentUser) => {
if(currentUser != null) {
this.setState({
currentUser: currentUser
})
} else {
this.setState({
currentUser: null
});
}
return this.state.currentUser;
}
render() {
return (
<div className="App">
<Header updateUserState={this.updateUserState} />
<Routes>
<Route path='/profile' element={<ProfilePage updateUserState={this.updateUserState} />}/>
</Routes>
</div>
);
}
}
this is the code in Profile component which is completely similar to Header Component :
const ProfileCard = ({updateUserState}) => {
const signout = () => {
handleLogout()
updateUserState()
}
return (
<div className='profile-card'>
<a onClick={() => signout()}>
Sign Out
</a>
</div>
)
}
Update :
solved thanks to Abir Taheer !
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentUser: null
}
this.updateUserState = this.updateUserState.bind(this);
}
updateUserState = (currentUser) => {
if(currentUser != null) {
this.setState({
currentUser: currentUser
}, () => console.log(this.state.currentUser))
} else {
this.setState({
currentUser: null
}, () => console.log(this.state.currentUser));
}
return this.state.currentUser;
}
render() {
return (
<div className="App">
<Header currentUser={this.state.currentUser} updateUserState={this.updateUserState} />
<Routes>
<Route path='/profile' element={<ProfilePage updateUserState={this.updateUserState}
currentUser={this.state.currentUser} />}
/>
</Routes>
</div>
);
}
}
then inside ProfilePage :
const ProfilePage = ( {currentUser, updateUserState} ) => {
return (
<div>{
currentUser ?
<div>
<ProfileCard id={currentUser.id} updateUserState={updateUserState} />
</div>
:
<h1>No User Signed In</h1>
}</div>
)
}
And ProfileCard :
const ProfileCard = ({id, updateUserState}) => {
const signout = () => {
handleLogout()
updateUserState();
}
return (
<div className='profile-card'>
<a onClick={() => signout()}>
Sign Out
</a>
</div>
)
}
The issue arises because of the this keyword. When you're passing a function to another component you need to bind the this keyword to the parent instance otherwise it may not work properly.
This behavior is described in the React Docs here: https://reactjs.org/docs/faq-functions.html
and more specifically further down in the page here: Why is binding necessary at all?
When you bind this to the parent instance then it refers to the correct state and the function should work.
You need to update your component like such:
class App extends React.Component {
constructor(props) {
super(props);
// Make sure to initialize your state accordingly
this.state = {
currentUser: null,
};
// --- This is the line you need ---
this.updateUserState = this.updateUserState.bind(this);
}
updateUserState(currentUser) {
if (currentUser != null) {
this.setState({
currentUser: currentUser,
});
} else {
this.setState({
currentUser: null,
});
}
return this.state.currentUser;
}
render() {
return (
<div className="App">
<Header updateUserState={this.updateUserState} />
<Routes>
<Route
path="/profile"
element={<ProfilePage updateUserState={this.updateUserState} />}
/>
</Routes>
</div>
);
}
}
The way you do it seems like you are rendering the component instead of passing a reference.
How I would suggest is to wrap the component in another function and return with your function passed in as a prop. So basically making another react component with the method passed in. Use that wrapper component instead:
const wrappedProfilePage = () => <ProfilePage updateUserState={this.updateUserState} />;
..
.
.
<Route path='/profile' element={wrappedProfilePage}/>
I am new to react . I am hared coded the username and password into js pages . I am trying to redirect to user into admin pages on the text fields values. Here is mentioned that username and password Admin then i want to redirect the user into admin page else into home page but is not working . I also defined the router as well into app.js files.
Here is the app.js .
import React from 'react';
import { Router, Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { history } from '../_helpers';
import { alertActions } from '../_actions';
import { PrivateRoute } from '../_components';
import { HomePage } from '../HomePage';
import { LoginPage } from '../LoginPage';
import { RegisterPage } from '../RegisterPage';
import CreateEmployeeComponent from '../EmployeeComponets/CreateEmployeeComponent';
import ViewEmployeeComponent from '../EmployeeComponets/ViewEmployeeComponent';
import AdminComponent from '../EmployeeComponets/AdminComponent';
class App extends React.Component {
constructor(props) {
super(props);
history.listen((location, action) => {
// clear alert on location change
this.props.clearAlerts();
});
}
render() {
const { alert } = this.props;
return (
<div className="jumbotron">
<div className="container">
<div className="col-sm-8 col-sm-offset-2">
{alert.message &&
<div className={`alert ${alert.type}`}>{alert.message}</div>
}
<Router history={history}>
<Switch>
<PrivateRoute exact path="/" component={HomePage} />
<Route path = "/add-employee/:id" component = {CreateEmployeeComponent} />
<Route path = "/view-employee/:id" component = {ViewEmployeeComponent} />
<Route path="/login" component={LoginPage} />
<Route path="/register" component={RegisterPage} />
<Route path ="/admin" component={AdminComponent} />
<Redirect from="*" to="/" />
</Switch>
</Router>
</div>
</div>
</div>
);
}
}
function mapState(state) {
const { alert } = state;
return { alert };
}
const actionCreators = {
clearAlerts: alertActions.clear
};
const connectedApp = connect(mapState, actionCreators)(App);
export { connectedApp as App };
Here is the code for Login.js
import React from 'react';
import { Link, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { userActions } from '../_actions';
class LoginPage extends React.Component {
constructor(props) {
super(props);
// reset login status
this.props.logout();
this.state = {
username: '',
password: '',
submitted: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit(e) {
e.preventDefault();
this.setState({ submitted: true });
const { username, password } = this.state;
if (username && password) {
this.props.login(username, password);
}
else if(username==="Admin" && password==="Admin"){
localStorage.setItem("token" , "shjsshhbhbh")
this.setState({
loggingIn:true
})
}
}
render() {
if(this.state.loggingIn){
return <Redirect to ="/admin" />
}
const { loggingIn } = this.props;
const { username, password, submitted } = this.state;
return (
<div className="col-md-6 col-md-offset-3">
<h2>Login</h2>
<form name="form" onSubmit={this.handleSubmit}>
<div className={'form-group' + (submitted && !username ? ' has-error' : '')}>
<label htmlFor="username">Username</label>
<input type="text" className="form-control" name="username" value={username} onChange={this.handleChange} />
{submitted && !username &&
<div className="help-block">Username is required</div>
}
</div>
<div className={'form-group' + (submitted && !password ? ' has-error' : '')}>
<label htmlFor="password">Password</label>
<input type="password" className="form-control" name="password" value={password} onChange={this.handleChange} />
{submitted && !password &&
<div className="help-block">Password is required</div>
}
</div>
<div className="form-group">
<button className="btn btn-primary">Login</button>
{loggingIn &&
}
<Link to="/register" className="btn btn-link">Register</Link>
</div>
</form>
</div>
);
}
}
function mapState(state) {
const { loggingIn } = state.authentication;
return { loggingIn };
}
const actionCreators = {
login: userActions.login,
logout: userActions.logout
};
const connectedLoginPage = connect(mapState, actionCreators)(LoginPage);
export { connectedLoginPage as LoginPage };
Here is the private route code
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('user')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
Here is the admin page .
import React, { Component } from 'react'
import { Redirect } from 'react-router-dom';
import EmployeeService from '../services/EmployeeService';
class AdminComponent extends Component {
constructor(props) {
super(props)
const token =localStorage.getItem("token")
let loggedIn = true
{
if(token == null){
loggedIn - false
}
this.state ={
loggedIn
}
}
this.state = {
// step 2
id: this.props.match.params.id,
firstName: '',
lastName: '',
emailId: ''
}
this.changeFirstNameHandler = this.changeFirstNameHandler.bind(this);
this.changeLastNameHandler = this.changeLastNameHandler.bind(this);
this.saveOrUpdateEmployee = this.saveOrUpdateEmployee.bind(this);
}
// step 3
componentDidMount(){
// step 4
if(this.state.id === '_add'){
return
}else{
EmployeeService.getEmployeeById(this.state.id).then( (res) =>{
let employee = res.data;
this.setState({firstName: employee.firstName,
lastName: employee.lastName,
emailId : employee.emailId
});
});
}
}
saveOrUpdateEmployee = (e) => {
e.preventDefault();
let employee = {emailId: this.state.emailId,firstName: this.state.firstName, lastName: this.state.lastName};
console.log('employee => ' + JSON.stringify(employee));
// step 5
if(this.state.id === '_add'){
EmployeeService.createEmployee(employee).then(res =>{
this.props.history.push('/employees');
});
}else{
EmployeeService.updateEmployee(employee, this.state.id).then( res => {
this.props.history.push('/employees');
});
}
}
changeFirstNameHandler= (event) => {
this.setState({firstName: event.target.value});
}
changeLastNameHandler= (event) => {
this.setState({lastName: event.target.value});
}
changeEmailHandler= (event) => {
this.setState({emailId: event.target.value});
}
cancel(){
this.props.history.push('/employees');
}
getTitle(){
if(this.state.id === '_add'){
return <h3 className="text-center">Add Employee</h3>
}else{
return <h3 className="text-center">Update Employee</h3>
}
}
render() {
if(this.state.loggedIn === false)
{
return <Redirect to ="/login" />
}
return (
<div>
<h1>Welcome to adimin </h1>
<br></br>
<div className = "container">
<div className = "row">
<div className = "card col-md-6 offset-md-3 offset-md-3">
{
this.getTitle()
}
<div className = "card-body">
<form>
<div className = "form-group">
<label> Email Id: </label>
<input placeholder="Email Address" name="emailId" className="form-control"
value={this.state.emailId} onChange={this.changeEmailHandler}/>
</div>
<div className = "form-group">
<label> First Name: </label>
<input placeholder="First Name" name="firstName" className="form-control"
value={this.state.firstName} onChange={this.changeFirstNameHandler}/>
</div>
<div className = "form-group">
<label> Last Name: </label>
<input placeholder="Last Name" name="lastName" className="form-control"
value={this.state.lastName} onChange={this.changeLastNameHandler}/>
</div>
<button className="btn btn-success" onClick={this.saveOrUpdateEmployee}>Save</button>
<button className="btn btn-danger" onClick={this.cancel.bind(this)} style={{marginLeft: "10px"}}>Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default AdminComponent
in your handleSubmit function always first if is true and after that javascript didn't check another else if.
Also you need add another state isAdmin and use it like this:
const { username, password } = this.state;
if (username==="Admin" && password==="Admin") {
this.props.login(username, password);
localStorage.setItem("token" , "adminsaddad")
this.setState({
loggingIn:true,
isAdmin:true
})
} else if (username && password) {
this.props.login(username, password);
localStorage.setItem("token" , "shjsshhbhbh")
this.setState({
loggingIn:true
})
}
Then check if the user is admin or not:
if(this.state.loggingIn){
if(this.state.isAdmin)
return <Redirect to ="/admin" />
else
return <Redirect to ="/" />
}
But this is not a safe way for the admin page, should also add the admin component in your PrivateRoute and check the token then redirect user.
I want to add roles to admin when login so admin can create new user. I dont know how to add the roles and make it works. Here's my code:
Login.js
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
}
}
onChangeInput = e => {
this.setState({
[e.target.name]: e.target.value
})
}
onLogin = async () => {
const { username, password } = this.state
const exist = this.props.listUsers.find(user => user.username === username)
if (exist) {
this.props.changeLogIn()
} else if(username === 'admin' && password === "123") {
const roles = "ADMIN";
this.props.changeLogIn()
}
}
render() {
if (this.props.statusLogin){
return <Redirect to="/about" />
}
return (
<div className="login">
<form className="login-form" method="POST">
<div className="container-log">
<h1 className="judul">Login</h1>
<RowInput value={this.state.username} label="Username" placeholder="Username" type="text" name="username" onChange={this.onChangeInput}/>
<RowInput value={this.state.password} label="Password" placeholder="Password" type="password" name="password" onChange={this.onChangeInput}/>
<Button onClickInput={this.onLogin}>Masuk</Button>
</div>
</form>
</div>
);
}
}
Body.js
showPage = () => {
const { changeLogIn, statusLogin } = this.props
return (
<Switch>
<Route path="/about" children={(props) => <About {...props} statusLogin={statusLogin} listUsers={this.state.userData} />} />
<Route path="/login">
<Login changeLogIn={changeLogIn} listUsers={this.state.userData} statusLogin={statusLogin} />
</Route>
<Route path="/register">
<Register listUsers={this.state.userData} tambahUser={this.addUsers} />
</Route>
</Switch>
)
}
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
page: "login",
isLoggedIn: false
}
}
changeLogIn = () => {
this.setState(oldState => ({ isLoggedIn: !oldState.isLoggedIn }))
}
Yes, I know this code wouldn't work (esp Login.js), but again, I dont know how to fix it. Like, somewhat I have the solutions on my head, but I can't code it. Thank you before!
Admin , user roles is something that we should do it in the backend but if you are trying to do it in front end then , There should be one super admin (he an add new admins or user ) , admins (he can add users)
store the login details in cookie or localstorage , and redirect the user if he is not authorized , I can code if you ask me something specific
I have an App.jsx parent component and a TopBar.js child. What I would like to do is get the appId parameter from the url and pass it into my TopBar child component. The problem is that I don't know how to use match.params.appId like CategoryPage (<CategoryPage categoryId = {match.params.categoryId} />) because I get an error message: match is not defined, which seems normal since my child component is not included in a <Route> component. I looked at the documentation and I can only find the classic case, is there another way to retrieve a route parameter and for example store it in a state in order to reuse it?
Thank you in advance for any help or advice, I am new to this project and I am gradually integrating the particularities of the code.
App.jsx
export default class App extends PureComponent {
static childContextTypes = {
apiKEY: PropTypes.string,
apiURL: PropTypes.string,
appName: PropTypes.string,
loginToken: PropTypes.string,
userId: PropTypes.string,
};
constructor(props) {
super(props);
const parsed = queryString.parse(window.location.search);
const state = {
apiKey: null,
appName: null,
fetchApiKeyError: null,
fetchApiKeyPending: false,
fetchApiKeyDone: false,
};
['auth_token', 'userId'].forEach((key) => {
state[key] = parsed[key] || localStorage.getItem(key);
if (parsed[key]) localStorage.setItem(key, parsed[key]);
});
this.state = state;
this.handleErrorAuth = this.handleErrorAuth.bind(this);
}
getChildContext() {
const {
auth_token: loginToken, userId, apiKey, appName,
} = this.state;
return {
apiURL: process.env.REACT_APP_API_URL,
loginToken,
userId,
apiKEY: apiKey,
appName,
};
}
renderRedirect = () => {
const isLogged = localStorage.auth_token && localStorage.userId;
// This is a private app, so we need to be logged all time
if (!isLogged) {
window.location = `${process.env.REACT_APP_AUTH_URL}?redirect_uri=${window.location}`;
return null;
}
return null;
}
fetchApiKey = (appId, authToken) => {
if (!authToken) return;
this.setState({ fetchApiKeyPending: true });
const storageKey = `apiKey_${appId}`;
const nameStorageKey = `name_${appId}`;
const apiKey = localStorage.getItem(storageKey);
const appName = localStorage.getItem(nameStorageKey);
// ApiKey and appName already in localStorage
if (apiKey && appName) {
this.setState({
fetchApiKeyPending: false,
fetchApiKeyDone: true,
apiKey,
appName,
});
return;
}
// flush all previous keys
Object.keys(localStorage)
.filter((val) => val.indexOf('apiKey_') + 1 || val.indexOf('name_') + 1)
.forEach((val) => localStorage.removeItem(val));
// get ApiKey
fetch(`${process.env.REACT_APP_API_URL}/apps/${appId}/infos`, {
headers: {
Authorization: `Bearer ${authToken}`,
},
}).then((data) => {
if (!data.ok) throw new Error(data.status);
return data;
})
.then((data) => data.json())
.then(({ key, name }) => {
localStorage.setItem(storageKey, key);
localStorage.setItem(nameStorageKey, name);
this.setState({
fetchApiKeyPending: false,
fetchApiKeyDone: true,
fetchApiKeyError: null,
apiKey: key,
appName: name,
});
})
.catch((e) => this.setState({
fetchApiKeyPending: false,
fetchApiKeyDone: true,
fetchApiKeyError: e,
apiKey: null,
appName: null,
}));
};
getLastAppId = () => {
const storageKey = Object.keys(localStorage).filter(
(val) => val.indexOf('apiKey_') + 1,
)[0];
return storageKey ? storageKey.split('apiKey_')[1] : null;
};
switch = (location) => {
const {
fetchApiKeyPending,
fetchApiKeyDone,
apiKey,
auth_token: loginToken,
fetchApiKeyError,
} = this.state;
return (
<Switch location={location}>
{apiKey && [
<Route
key="1"
path="/:appId/categories/:categoryId/new_article"
render={({ match }) => (
<DefaultLayout>
<NewArticlePage categoryId={match.params.categoryId} />
</DefaultLayout>
)}
/>,
<Route
key="2"
path="/:appId/articles/:articleId"
render={({ match }) => (
<DefaultLayout>
<ModifyArticlePage articleId={match.params.articleId} />
</DefaultLayout>
)}
/>,
<Route
key="3"
path="/:appId/createCategory"
render={({ match }) => (
<CenteredLayout
backButtonProps={{
to: `/${match.params.appId}/categories`,
}}
>
<NewCategoryPage />
</CenteredLayout>
)}
apikey
/>,
<Route
key="4"
path="/:appId/categories/:categoryId/modify"
render={({ match }) => (
<CenteredLayout>
<NewCategoryPage categoryId={match.params.categoryId} />
</CenteredLayout>
)}
/>,
<Route
key="5"
path="/:appId/categories/:categoryId"
render={({ match }) => (
<CenteredLayout
backButtonProps={{
to: `/${match.params.appId}/categories`,
}}
>
<CategoryPage categoryId={match.params.categoryId} />
</CenteredLayout>
)}
/>,
<Route key="6" path="/:appId/categories" component={CategoriesPage} />,
]}
<Route path="/welcome" component={WelcomePage} />
<Route path="/app_not_found" render={() => <AppNotFoundPage titleKey="press" />} />
<Route
path="/:appId/"
exact={false}
render={({ match }) => {
if (fetchApiKeyError) {
return <Redirect to="/app_not_found" />;
}
if (!fetchApiKeyDone || fetchApiKeyPending) {
return (
[
<OnMountExecuter
key="1"
execute={this.fetchApiKey}
params={[match.params.appId, loginToken]}
/>,
<Loading key="2" style={{ fontSize: 36 }} />,
]
);
}
return <Redirect to={`/${match.params.appId}/categories`} />;
}}
/>
<Route
path="/"
render={() => {
if (localStorage.auth_token && localStorage.userId) {
const appId = this.getLastAppId();
if (appId) {
return <Redirect to={`/${appId}/categories`} />;
}
}
return <Redirect to="/welcome" />;
}}
/>
</Switch>
);
};
handleErrorAuth() {
this.setState({
auth_token: null,
userId: null,
});
localStorage.clear();
}
renderContent(location) {
return (
<HttpsRedirect>
<IntlProvider locale={language} messages={messages[language]}>
<div id="container">
<div id="content-container">
<AuthorizeChecker onError={this.handleErrorAuth} />
<UserContextProvider>
<UserContext.Consumer>
{(user) => <TopBar user={user} />}
</UserContext.Consumer>
{this.switch(location)}
</UserContextProvider>
</div>
</div>
</IntlProvider>
</HttpsRedirect>
);
}
render() {
return (
<BrowserRouter>
<Route
render={({ location }) => this.renderRedirect(location) || this.renderContent(location)}
/>
</BrowserRouter>
);
}
}
You could access the match object from any grand child component of a Router component using the context created by the react router, it is found under, context =>router=>match. From there you can do whatever you want with it.
You can access the context under this.context in any class based component, in a hooks component you have to use the hook useContext
Also note that newer versions of react router have hooks that might help you, like
useRouteMatch and useParams.
I am new in React and I will appreaciate much any help. I am using create-react-app, react-router-dom and express server. When I try to submit a comment to a blog post (child component called Details), it gets stored in the database, however the component does not seem to update and i do not see the new comment.As a result, I can see the new comment only after i refresh the page but not on form submit. I guess I am not setting componentDidUpdate properly but I do not have a clue how to do it, so i can see the comment immediately.
Here is my App.js:
class App extends Component {
constructor(props) {
super(props)
this.state = {
userId: null,
username: null,
isAdmin: false,
isAuthed: false,
jwtoken: null,
posts: [],
filtered: [],
}
this.handleSubmit = this.handleSubmit.bind(this)
}
static authService = new AuthService();
static postService = new PostService();
static commentService = new CommentService();
componentDidMount() {
const isAdmin = localStorage.getItem('isAdmin') === "true"
const isAuthed = !!localStorage.getItem('username');
if (isAuthed) {
this.setState({
userId: localStorage.getItem('userId'),
username: localStorage.getItem('username'),
isAdmin,
isAuthed,
})
}
this.getPosts()
}
componentDidUpdate(prevProps, prevState, posts) {
if (prevState === this.state) {
this.getPosts()
}
}
handleChange(e, data) {
this.setState({
[e.target.name]: e.target.value
})
}
handleCommentSubmit(e, data) {
e.preventDefault();
e.target.reset();
App.commentService.createComment(data)
.then(body => {
this.getposts()
if (!body.errors) {
toast.success(body.message);
}
else {
toast.error(body.message);
}
}
)
.catch(error => console.error(error));
}
getPosts() {
App.postService.getPost()
.then(data => {
this.setState({
posts: data.posts.length? data.posts : []
});
}
)
.catch(e => this.setState({ e }))
}
render() {
return (
<Fragment>
<Header username={this.state.username} isAdmin={this.state.isAdmin} isAuthed={this.state.isAuthed} logout={this.logout.bind(this)} />
<Switch>
<Route exact path="/" render={(props) => (
<Home
posts={this.state.posts}
handleSearchSubmit={this.handleSearchSubmit.bind(this)}
handleChange={this.handleSearchChange.bind(this)}
{...props} />
)} />
<Route path="/posts/:id" render={(props) =>
<Details handleSubmit={this.handleCommentSubmit.bind(this)}
isAdmin={this.state.isAdmin}
isAuthed={this.state.isAuthed}
posts={this.state.posts}
handleChange={this.handleChange}
{...props} />} />
</Switch>
<Footer posts={this.state.posts} formatDate={this.formatDate} />
</Fragment>
);
}
}
export default withRouter(App);
Here is my Details.js:
class Details extends Component {
constructor(props) {
super(props);
this.state = {
post: null,
comment: null
}
this.handleChange = props.handleChange.bind(this);
}
componentDidMount() {
const { posts, match } = this.props;
this.setState({
post: posts.length
? posts.find(p => p._id === match.params.id)
: null,
userId: localStorage.getItem('userId')
})
}
componentDidUpdate(prevProps) {
const { posts, match, isAuthed } = this.props;
if (JSON.stringify(prevProps) === JSON.stringify(this.props)) {
return;
}
this.setState({
post: posts.length
? posts.find(p => p._id === match.params.id)
: null
});
}
render() {
const { post } = this.state;
const { isAdmin, isAuthed } = this.props;
if (!post) {
return <span>Loading post ...</span>;
}
return (
<section className="site-section py-lg">
<form onSubmit={(e)=> this.props.handleSubmit(e, this.state)} className="p-5 bg-light">
<div className="form-group">
<label htmlFor="message">Message</label>
<textarea name="comment" id="message" onChange={this.handleChange} cols={30} rows={10} className="form-control" defaultValue={ ""} />
</div>
<div className="form-group">
<input type="submit" defaultValue="Post Comment" className="btn btn-primary" />
</div>
</form>}
</section>
);
}
}
export default Details;
Any help will be much appreciated!
You are doing a mistake that will be done by any new React developer. Just remember one thing that:-
UI is a function of state
So your UI will only be updated if your state is update.
After submitting a comment don't fetch all your comments again, just concat your new comment to current state and you will see your comment as soon as you submit it successfully