I am having a navbar and a side bar for my page.
Navbar consists of home and blogs
Blogs will render BlogHome Component which will fetch links from db and on click of any link will render BlogContent component.
Lets say the side bar has Blog1,Blog2 and Blog3 listed. If I click Blog1 it renders Blog1's content properly to its side, but if I click Blog2 again it just changes URL but not the Blog2's content.
Please take a look at my code:
Navbar.js
<Router>
<Container className="p-0" fluid={true}>
<Navbar className="border-bottom" bg="transparent" expand="lg">
<Navbar.Brand>{global.config.appname}</Navbar.Brand>
<Navbar.Toggle className="border-0" aria-controls="navbar-toggle" />
<Navbar.Collapse id="navbar-toggle">
<Nav className="ml-auto">
<Link className="nav-link" to="/">Home</Link>
<Link className="nav-link" to="/blogs/main">Blogs</Link>
<Link className="nav-link" to="/contact">Contact</Link>
</Nav>
</Navbar.Collapse>
</Navbar>
</Container>
<Switch>
<Route exact path="/" component={Home}></Route>
<Route exact path="/blogs/main" component={BlogHome}></Route>
</Switch>
</Router>
BlogHome.js
export default class BlogHome extends Component {
constructor(props)
{
super(props);
this.state = { data: null,route:null };
}
componentDidMount = () => {
console.log("BlogHome");
BlogDataService.getAll().then(data => {
let data_temp = []
let cnt = 0;
for (let item of data.data) {
data_temp.push(
<MenuItem key={cnt++} icon={<FaBlog />}>
<Link to={"/blogs/main/" + item.id}>{item.title}</Link>
</MenuItem>
);
}
this.setState({ data: data_temp });
})
}
render() {
return (
<Router>
<div style={{ display: "flex" }}>
<ProSidebar>
<Menu iconShape="square">
{this.state.data}
</Menu>
</ProSidebar>
<Switch>
<Route exact path={"/blogs/main/:blogId"} component={BlogContent}></Route>
</Switch>
</div>
</Router>
);
}
}
BlogContent.js
export default class BlogContent extends Component {
constructor(props) {
super(props);
const contentState = convertFromRaw(content);
this.state = {
contentState,
item_id: this.props.match.params.blogId,
title:null
}
console.log(this.props.match);
}
onContentStateChange: function = (contentState) => {
this.setState({
contentState,
});
};
componentDidMount = () => {
BlogDataService.get(this.state.item_id).then(data => {
console.log(data);
this.setState({ title: data.data.title })
});
}
render() {
const { contentState } = this.state;
return (
<Router>
<div style={{padding:"10px"}}>
<div style={{padding:"50px",fontSize:"50px"}}>
{this.state.title}
</div>
<Editor
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onContentStateChange={this.onContentStateChange}
/>
<Route exact path={"/blogs/main/1"} component={BlogContent}></Route>
</div>
</Router>
);
}
}
Thank you for reading :)
your item_id is set only one time and it is not changing at all. On first time when component load it will work but when you are doing second time you are passing new item id but component is not aware about this change hence not able to do anything.
Try to create a function which fetch data. Same function call it in componentDidmount.
Now when it is getting new props it is time to check . Use componentDidUpdate.
componentDidUpdate(prevProps, prevState){
if(prevProps.blogId != this.props.blogId){
this.setState({
item_id: this.props.blogId
}, () => { call the function to get the data } )
}
}
Related
How to correctly pass callbacks and states to the Layout so that they can be used elsewhere? When I share this as below, I have errors and a white screen:
class Menu extends Component {
constructor(props) {
super(props);
this.onSearchF = this.onSearchF.bind(this)
}
state = {
searchBlock: false,
};
onSearchF = (keyword) => {
const filtered = this.state.data.filter((entry) =>
Object.values(entry).some(
(val) => typeof val === "string" && val.toLowerCase().includes(keyword.toLowerCase())
)
);
};
render() {
return (
<div className="content">
<Routes>
<Route path="/" element={<Layout searchBlock={this.state.searchBlock} onSearch={()=>this.onSearchF()}/>}>
<Route
index
element={
<Home data={this.state.data} num={this.state.data.length} />
}
/>
</Route>
</Routes>
</div>
);
}
}
export default Menu;
Here I pass the callback to the Header that I previously passed to the Layout:
const Layout = () => {
return (
<>
<Header sblock={this.props.searchBlock} onS = {this.props.onSearch}/>
</>
);
};
export default Layout;
I want to use the callback here:
function Header() {
return (
<header className="header">
<button onClick={()=>console.log(this.props.sblock)}>button</button>
</header>
);
}
export default Header;
Your Layout is a functional component, and you are trying to use this.props in it; this is incorrect. Get the props as part of arguments instead, like so:
import { Outlet } from "react-router-dom";
const Layout = ({searchBlock,onSearch}) => {
return (
<>
<Header sblock={searchBlock} onS={onSearch}/>
<Outlet/>
</>
);
};
export default Layout;
Issues
The Layout component isn't accepting any props.
The Layout component isn't rendering an Outlet for nested routes.
Solution
It seems that Layout only exists to render the Header component. I'd suggest rendering Header directly in the Main component.
Example:
class Menu extends Component {
state = {
data: [],
searchBlock: false,
};
onSearch = (keyword) => {
const filtered = this.state.data.filter((entry) =>
Object.values(entry).some((val) =>
typeof val === "string"
&& val.toLowerCase().includes(keyword.toLowerCase())
)
);
... do something with filtered ...
};
render() {
const { data, searchBlock } = this.state;
return (
<div className="content">
<Header sblock={searchBlock} onS={this.onSearch} />
<Routes>
<Route
path="/"
element={<Home data={data} num={data.length} />}
/>
</Routes>
</div>
);
}
}
export default Menu;
I am trying to change the navigation bar contents from Sign In/Register to other things such as Profile once the user logs in. My server sends a 401 when the user is not logged in and I have a HOC (RequireAuth.js) which checks the same for protected routes and redirects them to login if they have not logged in. However, I could not come up with a way to change the navbar contents with this logic and was wondering if there is a good way to do this (I do not want to use Redux for this purpose if possible).
RequireAuth.js
const RequireAuth = ( Component ) => {
return class Apps extends React.Component {
state = {
isAuthenticated: false,
isLoading: true
}
checkAuthentication = async() => {
const url = '/getinfo'
const json = await fetch(url, {method: 'GET'})
if (json.status !== 401) {
setTimeout(
function() {
this.setState({isAuthenticated: true, isLoading: false});}.bind(this), 1500);
} else {
setTimeout(
function() {
this.setState({isLoading: false});}.bind(this), 1500);
}
}
componentDidMount() {
this.checkAuthentication()
}
render() {
const style = {position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
console.log(this.state.isLoading)
const { isAuthenticated, isLoading } = this.state;
if(!isAuthenticated) {
return this.state.isLoading? <div style={style}><PacmanLoader color={'#36D7CC'}/></div> : <Redirect to="/" />
}
return <Component {...this.props} />
}
}
}
export { RequireAuth }
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const NotFoundComponent = () => <div>404 NOT FOUND</div>
return (
<div>
<Router>
<NavigationBar />
<Switch>
<Route exact path = '/'
component = {LandingPage}
/>
<Route exact path = '/register'
component = {Register}
/>
<Route exact path = '/Profile'
component = {RequireAuth(Profile)}
/>
<Route exact path = '/About'
component = {RequireAuth(About)}
/>
<Route exact path = '/Name'
component = {RequireAuth(Name)}
/>
<Route path="*" component = {NotFoundComponent}/>
</Switch>
</Router>
</div>
);
}
}
export default withRouter(App);
Navigation.js
class NavigationBar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Navbar bg="dark" variant="dark" expand="lg">
<Navbar.Brand >Hello</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
<Nav.Link as={Link} to='/'>Login</Nav.Link>
<Nav.Link as={Link} to='/register'>Register</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
)
}
}
export default withRouter(NavigationBar);
I have a Navigation bar in my project which I call from inside App.js. Based on if I am logged in or not, I want to render different views of NavBar. If logged in, I want the NavBar to have a logout button. And if logged out, I want the NavBar to have login button. I use a token in localStorage to check if I am logged in or not. When logged in, token is present in localStorage. On logout/before login, there is no token key in localStorage. I pass this token as a state to NavBar as shown:
export default function App() {
const [loggedIn, setLoggedIn] = useState(localStorage.getItem("token"));
return (
<div>
<Router>
<Navbar isAuth={loggedIn} />
<Route exact path="/" exact component={Home} />
<Route path="/login" component={Login} />
<PrivateRoute path="/dashboard" component={Dashboard} />
</Router>
</div>
);
}
Now from NavBar component, I use this prop to render different views of NavBar as shown below:
const NavBar = props => {
const classes = useStyles();
if (props.isAuth !== null) {
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" className={classes.title}>
<Link
href="/"
style={{ textDecoration: "none", color: "white" }}
>
Timetracker
</Link>
</Typography>
<Link href="/" style={{ color: "white" }}>
<Button color="inherit" onClick={auth.logout}>
Logout
</Button>
</Link>
</Toolbar>
</AppBar>
</div>
);
} else {
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" className={classes.title}>
<Link
href="/"
style={{ textDecoration: "none", color: "white" }}
>
Timetracker
</Link>
</Typography>
<Link href="/login" style={{ color: "white" }}>
<Button color="inherit">Login</Button>
</Link>
</Toolbar>
</AppBar>
</div>
);
}
};
export default NavBar;
The problem is that, the NavBar does not update itself as soon as I login. I have to manually refresh the page in order to render the new NavBar. Similarly on logout too, It does not update itself and updates only on manual refresh. What is the issue and how to solve this?
I found a simple solution:
use a componentDidMount() or useEffect() function which will render automatically upon loading the NavBar page.
Inside this function, use a setInterval() function to continually retrieve the auth status (say, an interval of 5000). This will continually refresh the NavBar, and change the button immediately.
I imagine you would have to put the auth check in the NavBar component itself, instead of using props. I put the specific buttons I wanted to change in a separate component called NavBarUser, which changes 'login | signup' to 'logout' and contains a user avatar. I then inserted this component into the NavBar itself at the appropriate place.
This is what my code looks like:
```
import React, { useState, useEffect } from 'react';
import Avatar from './Avatar';
import { BrowserRouter as Router, Link } from 'react-router-dom';
const NavBarUser = () => {
const [user, setUser] = useState({});
useEffect(() => {
{ /*
setInterval was used in order to refresh the page constantly
in order to have the "logout" button show immediately in place of
"login", as soon as user logs out.
*/}
setInterval(() => {
const userString = localStorage.getItem("user");
const user = JSON.parse(userString);
setUser(user);
}, [])
}, 5000);
const logout = () => {
return localStorage.removeItem("user");
}
if (!user) {
return (
<div className="navbar-nav ml-auto">
<Link to="/login" className="nav-item nav-link">Login</Link> <span
className="nav-item nav-link">|</span> <Link to="/SignUp" className="nav-item nav-
link">Sign Up</Link>
</div>
)
}
if (user) {
return (
<div className="navbar-nav ml-auto">
<Link to="/" className="nav-item nav-link" onClick={logout}>Logout</Link>
<Avatar img="/images/Eat-healthy.jpg" />
</div>
)
}
}
export default NavBarUser;
```
You need to add <Switch> as well. From the documentation:
Renders the first child or that matches the location.
<Switch> is unique in that it renders a route exclusively. In contrast, every <Route> that matches the location renders inclusively.
Just like the following:
<Router>
<Switch>
<Navbar isAuth={loggedIn} />
<Route exact path="/" exact component={Home} />
<Route path="/login" component={Login} />
<PrivateRoute path="/dashboard" component={Dashboard} />
</Switch>
</Router>
Read further here: Router
I hope that helps!
Your app's state won't update if you change the value of the token in localStorage.
You need to make sure you update the state, I've added a sandbox if it helps.
Here's how I solved this issue:
To start, I created a isLoggedIn state in my App class. I gave it a componentDidMount() method that would fetch the login state from a cookie on app start. Then I created globalLogin and globalLogout methods as arrow functions, which set the isLoggedIn state to true or false accordingly. I passed my Nav component the isLoggedIn state as a prop and passed the Login and Nav routes the globalLogin and globalLogout methods. These methods can then be called from Login or Nav with this.props.globalLogout(); or this.props.globalLogin();.
This is a simplified version of my App.js.
class App extends Component {
constructor(props){
super(props);
this.state = {
isLoggedIn: false,
}
}
componentDidMount() {
const token = Cookie.get("token") ? Cookie.get("token") : null;
if (token) {
this.setState({ "isLoggedIn": true });
}
}
globalLogin = () => {
this.setState({ "isLoggedIn": true });
}
globalLogout = () => {
this.setState({ "isLoggedIn": false });
}
render() {
return (
<Router>
<div className="App">
<Nav isLoggedIn={this.state.isLoggedIn} globalLogout={this.globalLogout}/>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/login" exact>
<Login globalLogin={this.globalLogin}/>
</Route>
</Switch>
</div>
</Router>
);
}
}
EDIT: using history.push didn't work in login module above so I added an intermediate to handle props
render() {
const LoginIntermediate = (props) => {
return (
<Login {...props} globalLogin={this.globalLogin}/>
)
}
return (
<Router>
<div className="App">
<Nav isLoggedIn={this.state.isLoggedIn} globalLogout={this.globalLogout}/>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/login" exact component={LoginIntermediate} />
</Switch>
</div>
</Router>
);
}
I am developing a Reactjs web application from scratch and encountered a tricky situation which i need help with. Whenever i navigate away from a particular url and navigate back, my redux store does not seem to be connected.
routes.js
const RouteList = () => (
<main>
<Switch>
<Route path="/abc/" exact component={withRouter(HomePage)} />
<Route path="/abc/xyz" exact component={withRouter(XYZPage)} />
<Redirect from="/" to="/abc/" />
<Route component={Error} />
</Switch>
</main>
);
export default RouteList;
App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render () {
return (
<Router history={browserHistory}>
<div>
<Header />
<RouteList />
<Footer />
</div>
</Router>
);
}
}
export default App;
Header.js
const Header = () => {
return (
<Navbar expand="md">
<NavbarBrand tag={NavLink} to="/">
<img src={brandImage} style={{marginRight: "0", width: "40px", height: "40px"}} /><strong style={{color: "#457B9D"}} >Datum</strong>
</NavbarBrand>
<Nav className="mr-auto" navbar>
<NavItem>
<NavLink className="nav-link" to={"/abc/xyz"} >XYZ</NavLink>
</NavItem>
</Nav>
</Navbar>
);
};
export default withRouter(Header);
When i hit the NavLink which will take me to url: /"abc/xyz", it will take me to XYZPage.js
XYZPage.js
class XYZPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
activeTab: "1"
};
this.toggle = this.toggle.bind(this);
}
toggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
render () {
return (
<main>
<div className="container-fluid pt-3">
<Nav tabs>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "1"})}
onClick={() => {this.toggle("1"); }} >
AAA
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "2"})}
onClick={() => {this.toggle("2"); }} >
BBB
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "3"})}
onClick={() => {this.toggle("3"); }} >
CCC
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1">
<Row>
<AAAPAge/>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<BBBPage/>
</Row>
</TabPane>
<TabPane tabId="3">
<Row>
<CCCPage/>
</Row>
</TabPane>
</TabContent>
</div>
</main>
);
}
}
export default withRouter(XYZPage);
Each of the AAAPage, BBBPage & CCCPage are components which needs to have some pre-populated dropdowns which i declared in my index.js below:
index.js
const store = configureStore();
store.dispatch(loadAAA());
store.dispatch(loadBBB());
store.dispatch(loadCCC());
render((
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
), document.getElementById('app'));
loadAAA, loadBBB & loadCCC are all thunks
The configureStore() method is as such:
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
composeWithDevTools(
applyMiddleware(thunk, reduxImmutableStateInvariant()),
)
);
}
To shorten this post i give a sample of my AAAPage as the others are of similar structure:
AAAPage.js:
class AAAPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {...};
}
componentWillReceiveProps(nextProps) {...}
render() {
[...]
return (
<Container fluid>
<Row>
<AAAInputForm
// Data from Store is passed here
/>
</Row>
{ChildComponent}
</Container>
);
}
}
AAAPage.propTypes = {
DATA: PropTypes.array
};
function mapStateToProps(state, ownProps) {
let DATA = [];
if (state.AAAReducer.length > 0) {
DATA = state.AAAReducer;
}
return {
DATA: DATA
};
}
export default withRouter(connect(mapStateToProps)(AAAPage));
AAAReducer.js:
export default function AAAReducer(state=initialState.AAAList, action) {
switch(action.type) {
case types.LOAD_AAA_SUCCESS:
return action.AAAList;
default:
return state;
}
}
AAAAction.js:
export function loadAAASuccess(AAAList) {
return {
type: types.LOAD_AAA_SUCCESS,
AAAList: AAAlList
};
}
// thunk
export function loadAAA() {
// A thunk will always return a function that accepts a dispatch
return function(dispatch) {
return apiCall("ALL").then(response => {
dispatch(loadAAASuccess(response.data.AAA));
}).catch(error => {
throw(error);
});
};
}
initialState.js:
export default {
AAAList: [],
BBBList: [],
CCCList: []
};
At this point i believe i provided enough background to my code. I followed tutorials when designing this redux store and I am not sure why when i navigate from "/abc/xyz" to "/abc" and back, or when i navigate to "/abc/xyz" from "/abc", my stores are empty although i called the loadAAA() method at my index.js. All the other pages are affected as well. However, when i hit "/abc/xyz" directly, my stores are connected and my dropdowns are populated. What is happening? Is it because of my lifecycle methods?
I am using react v15.6.2, redux v3.7.2 & redux-thunk v2.3.0.
Thanks for the guidance.
You only call loadAAA at the top level of index.js, which only executes once when your page loads. If you want to dispatch it every time your XYZPage page renders, put in XYZ's componentDidMount
#AKJ - #Andy Ray said it correctly, but I'll like to add that componentDidMount is the best place to load async calls, as it is called after render and about Store redux store keeps data until you refresh the page after refresh redux store is reinitialized, if you need store the data after refresh try redux-persist
I am building an ad website. I built a registration system, which works perfectly fine, but for some reason I can't update the NavBar based on the event that has happened. For example, I want to replace the NavLink called "LOGIN/REGISTER" with "LOGGED IN". I have passed the props of the User.ID from the parent component (App.js) into the other components without any problem, but cannot do this for the NavBar. If I try a console.log - it would say undefined. I am going to put a couple of codes demonstrating where it works and where it does not:
APP.JS
*imports, which I am skipping*
const cookies = new Cookies();
class App extends Component {
constructor(){
super();
this.state = {
}
this.LogUser = this.LogUser.bind(this);
this.LogoutUser = this.LogoutUser.bind(this);
}
LogUser(User, ID){
cookies.set('User', User, { path: '/' });
cookies.set('UserID', ID,{ path: '/'});
}
LogoutUser(){
cookies.remove('User')
}
render() {
return (
<div>
<div>
//MENU <- WHERE I CAN'T PASS THE PROPS OF USER AND USERID
<Menu render={(props) => <Menu {...props} User={cookies.get('User')} ID={cookies.get('UserID')} LogOutUser={this.LogoutUser} />}/>
</div>
<Router history = {history} >
<div>
//I have removed all other routes as they are not needed, but here is an example, in which the passing of props works
<Route path = "/Profile" render={(props) => <Profile {...props} User={cookies.get('User')} ID={cookies.get('UserID')} LogOutUser={this.LogoutUser} />}/>
</div>
</Router>
</div>
);
}
}
export default App;
And for example in Profile.jsx, I can do that:
PROFILE.JSX
export default class Profile extends Component {
constructor(props, context) {
super(props, context);
this.state = {
LoggedUser: '',
UserID: '',
};
this.LogOutClick = this.LogOutClick.bind(this);
}
LogOutClick(){
this.props.LogOutUser();
history.push('/Logout');
}
componentDidMount(){
if (this.props.User !== undefined)
{
this.setState({LoggedUser: this.props.User, UserID: this.props.ID})
}
else
{
history.push('/Login');
}
}
render() {
return (
<div>
Hello, {this.props.User}!
<div>
)}}
But when I try it in the Menu component, I can't manage it to update accordingly:
NAVBAR.JSX
export default class Menu extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
isOpen: false,
Title: '',
};
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
//here I tried to put something similar to the ComponentDidMount() in Profile.jsx, but it didn't work.
componentDidMount(){
if (this.props.User !== undefined)
{
this.setState({LoggedUser: this.props.User, UserID: this.props.ID})
this.setState({Title: "LOGGED IN"})
}
else
{
this.setState({Title: "LOGIN/REGISTER"})
}
}
render() {
console.log(this.state.User)
console.log(this.state.ID)
return (
<div>
<Navbar color="light" light expand="md">
<NavbarBrand href="/"><img src={require('./images/home.png')} width = "25px" height = "25px"/></NavbarBrand>
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.isOpen} navbar>
<Nav className="ml-auto1" navbar>
<NavItem>
<NavLink href="/Ads"><b>ADS</b></NavLink>
</NavItem>
<NavItem>
<NavLink href="/Profile"><b>YOUR PROFILE</b></NavLink>
</NavItem>
<NavItem>
//What I want to update
<NavLink href="/Login"><b>{this.state.Title}</b></NavLink>
</NavItem>
</Nav>
</Collapse>
</Navbar>
</div>
);
}
}
React will only update in response to a new state or new props. You are manipulating a cookie which can't cause a component re-render. Here's a solution:
In your App component change the Log methods to:
constructor(){
super();
this.state ={
currentUserId: cookies.get('UserID'),
currentUser: cookies.get('User')
};
this.LogUser = this.LogUser.bind(this);
this.LogoutUser = this.LogoutUser.bind(this);
}
LogUser(User, ID){
cookies.set('User', User, { path: '/' });
cookies.set('UserID', ID,{ path: '/'});
this.setState({
currentUserId: ID,
currentUser: User
});
}
LogoutUser(){
cookies.remove('User');
this.setState({
currentUserId: null,
currentUser: null
});
}
And your render will become:
render() {
return (
<div>
<div>
<Menu render={(props) => <Menu {...props} User={this.state.currentUser} ID={this.state.currentUserId} LogOutUser={this.LogoutUser} />}/>
</div>
<Router history = {history} >
<div>
//I have removed all other routes as they are not needed, but here is an example, in which the passing of props works
<Route path = "/Profile" render={(props) => <Profile {...props} User={this.state.currentUser} ID={this.state.currentUserId} LogOutUser={this.LogoutUser} />}/>
</div>
</Router>
</div>
);
}