Re-Render in component on State Change in Redux Store - javascript

I wanted to show logoff button after user signs in and hide visibility of button after the user logged out. Menu items are coming from Menu.js file.
The current workflow of the app is, if user signed in, the auth state is updated in the store, based on that state, I wanted to show a logoff Button in Menu.
The auth state is correctly updated in Redux store on login and I can get the value of auth in Menu.js File from Redux store, but the change is not rendered in Siderbar.js until i refresh the page.
What should i need to do in sidebar.js to fetch the menu on auth state change in Store.
Siderbar.js
<pre><code>
import React, { Component } from 'react';
import { withNamespaces, Trans } from 'react-i18next';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link, withRouter } from 'react-router-dom';
import { Collapse, Badge } from 'reactstrap';
import SidebarRun from './Sidebar.run';
import SidebarUserBlock from './SidebarUserBlock';
import Menu from '../../Menu';
/** Component to display headings on sidebar */
const SidebarItemHeader = ({item}) => (
<li className="nav-heading">
<span><Trans i18nKey={item.translate}>{item.heading}</Trans></span>
</li>
)
/** Normal items for the sidebar */
const SidebarItem = ({item, isActive}) => (
<li className={ isActive ? 'active' : '' }>
<Link to={item.path} title={item.name}>
{item.label && <Badge tag="div" className="float-right" color={item.label.color}>{item.label.value}</Badge>}
{item.icon && <em className={item.icon}></em>}
<span><Trans i18nKey={item.translate}>{item.name}</Trans></span>
</Link>
</li>
)
/** Build a sub menu with items inside and attach collapse behavior */
const SidebarSubItem = ({item, isActive, handler, children, isOpen}) => (
<li className={ isActive ? 'active' : '' }>
<div className="nav-item" onClick={ handler }>
{item.label && <Badge tag="div" className="float-right" color={item.label.color}>{item.label.value}</Badge>}
{item.icon && <em className={item.icon}></em>}
<span><Trans i18nKey={item.translate}>{item.name}</Trans></span>
</div>
<Collapse isOpen={ isOpen }>
<ul id={item.path} className="sidebar-nav sidebar-subnav">
{ children }
</ul>
</Collapse>
</li>
)
/** Component used to display a header on menu when using collapsed/hover mode */
const SidebarSubHeader = ({item}) => (
<li className="sidebar-subnav-header">{item.name}</li>
)
class Sidebar extends Component {
state = {
collapse: {},
isLoggedIn: ''
}
componentDidMount() {
// pass navigator to access router api
SidebarRun(this.navigator.bind(this));
// prepare the flags to handle menu collapsed states
this.buildCollapseList()
}
/** prepare initial state of collapse menus. Doesnt allow same route names */
buildCollapseList = () => {
let collapse = {};
Menu
.filter(({heading}) => !heading)
.forEach(({name, path, submenu}) => {
collapse[name] = this.routeActive(submenu ? submenu.map(({path})=>path) : path)
})
this.setState({collapse});
}
navigator(route) {
this.props.history.push(route);
}
routeActive(paths) {
paths = Array.isArray(paths) ? paths : [paths];
return paths.some(p => this.props.location.pathname.indexOf(p) > -1)
}
toggleItemCollapse(stateName) {
for (let c in this.state.collapse) {
if (this.state.collapse[c] === true && c !== stateName)
this.setState({
collapse: {
[c]: false
}
});
}
this.setState({
collapse: {
[stateName]: !this.state.collapse[stateName]
}
});
}
getSubRoutes = item => item.submenu.map(({path}) => path)
/** map menu config to string to determine what element to render */
itemType = item => {
if (item){
// console.log(item)
if (item.heading) return 'heading';
if (!item.submenu) return 'menu';
if (item.submenu) return 'submenu';
}}
render() {
return (
<aside className='aside-container'>
{ /* START Sidebar (left) */ }
<div className="aside-inner">
<nav data-sidebar-anyclick-close="" className="sidebar">
{ /* START sidebar nav */ }
<ul className="sidebar-nav">
{ /* START user info */ }
<li className="has-user-block">
<SidebarUserBlock/>
</li>
{ /* END user info */ }
{ /* Iterates over all sidebar items */ }
{
Menu.map((item, i) => {
// heading
if(this.itemType(item) === 'heading')
return (
<SidebarItemHeader item={item} key={i} />
)
else {
if(this.itemType(item) === 'menu')
return (
<SidebarItem isActive={this.routeActive(item.path)} item={item} key={i} />
)
if(this.itemType(item) === 'submenu')
return [
<SidebarSubItem item={item} isOpen={this.state.collapse[item.name]} handler={ this.toggleItemCollapse.bind(this, item.name) } isActive={this.routeActive(this.getSubRoutes(item))} key={i}>
<SidebarSubHeader item={item} key={i}/>
{
item.submenu.map((subitem, i) =>
<SidebarItem key={i} item={subitem} isActive={this.routeActive(subitem.path)} />
)
}
</SidebarSubItem>
]
}
return null; // unrecognized item
})
}
</ul>
{ /* END sidebar nav */ }
</nav>
</div>
{ /* END Sidebar (left) */ }
</aside>
);
}
}
Sidebar.propTypes = {
isLoggedIn: PropTypes.object.isRequired
};
const mapStateToProps = (state, ownProps) => {
// console.log("Sidebar: ", state.auth);
return{
isLoggedIn: state.auth,
}}
export default connect(mapStateToProps)(withRouter(Sidebar));
What should i do in Sidebar.js, that it re-renders the menu on auth state change.
Any help would be highly appreciated.
Menu.js File
import configureStore from './store/store';
const store = configureStore();
function select(state) {
return state.auth
}
let loggedIn = select(store.getState());
let Meu = [
{
name: 'Analytics',
icon: 'icon-speedometer',
translate: 'sidebar.nav.DASHBOARD',
submenu: [{
name: 'Overview',
path: '/dashboard',
translate: 'sidebar.nav.element.HOME'
},
{
name: 'Revenue',
path: '/revenue',
translate: 'sidebar.nav.element.REVENUE'
},
{
name: 'Source',
path: '/source',
translate: 'sidebar.nav.element.SOURCE'
},
{
name: 'Marketing',
path: '/marketing',
translate: 'sidebar.nav.element.MARKETING'
}
]
},
{
name: 'Tools',
icon: 'fa fa-briefcase',
translate: 'sidebar.nav.TOOLS',
submenu: [
{
name: 'Customer Service',
path: '/customer-service',
translate: 'sidebar.nav.element.CUSTOMER-SERVICE'
}
]
},
{
name: 'Settings',
icon: 'icon-settings',
translate: 'sidebar.nav.SETTINGS',
submenu: [{
name: 'Profile Settings',
path: '/profile',
translate: 'sidebar.nav.element.PROFILE-SETTINGS'
},
{
name: 'Company Settings',
path: '/company-settings',
translate: 'sidebar.nav.element.SETTINGS'
}
]
},
{(loggedIn.authResponse) ?
({
name: 'Logoff',
icon: 'icon-lock',
path: '/logoff'
}) : null
}
];
const Menu = Meu.filter(word => word !== null)
export default (Menu)

Inside the parent component of your log out button you can conditionally render the log out button; take note this means your parent component is connected to redux via connect(mapStateToProps)
{ this.props.isLoggedIn ? <Button>Log Out</Button> : null }

Related

User preferred language saved but the page content language didn't change only after I reload

I'm using ant design pro.
The idea is In the platform we have 2 languages to choose from Fr(French) and En(English),
I want the user when he logs in and change the language to English for example when he logs out and log in again the language should be saved to English so he would be able to see the content in English, I managed to do it, when i login in the backend the preferedLanguage = en, the language toggle in the also changes to en, the only problem the web page content stays in French it's only change in English when i reload the page.
I think the issue is related to the login page, the login page is set to French as default , let's say my preferred language now is English, if i login from the login page. The page content loaded in French only changes when i reload.
-This is the umijs documentation : https://umijs.org/docs/max/i18n#setlocale-%E8%AE%BE%E7%BD%AE%E8%AF%AD%E8%A8%80
-LanguageDropdown (the toggle where you select the language (Fr or En)
import type { FC } from 'react';
import React, { useState, useEffect } from 'react';
import { Menu, Dropdown } from 'antd';
import { CaretDownOutlined, CaretUpOutlined, GlobalOutlined } from '#ant-design/icons';
import styles from './index.less';
import { setLocale, getLocale, getAllLocales } from 'umi';
interface Props {
setUpdateLang: any;
currentLang: string;
}
const LanguageDropdown: FC<Props> = ({ currentLang, setUpdateLang }) => {
const [langvisible, setLangVisible] = useState<boolean>(false);
const [localesList, setLocalesList] = useState<any[]>([]);
const [currentLocale, setCurrentLocale] = useState<string>('');
// useEffect(() => {
// if (currentLang) {
// setLocalesList(getAllLocales());
// setCurrentLocale(getLocale());
// setLocale(currentLang === 'fr' ? 'fr-FR' : 'en-US');
// }
// alert(currentLang);
// alert(getLocale());
// }, [currentLang]);
useEffect(() => {
setLocalesList(getAllLocales());
setCurrentLocale(getLocale());
if (currentLang) {
// alert(222);
const selectedLang = currentLang === 'fr' ? 'fr-FR' : 'en-US';
// setNewLang(selectedLang);
setLocale(selectedLang, false);
setCurrentLocale(getLocale());
}
}, [currentLang]);
const onLangVisibleChange = (visibleLang: boolean) => {
setLangVisible(visibleLang);
};
const langNameHandler = (lang: string) => {
if (lang === 'en-US') return 'EN';
else if (lang === 'fr-FR') return 'FR';
return 'FR';
};
const setNewLang = (lang: string) => {
setUpdateLang({ lang: langNameHandler(lang).toLocaleLowerCase(), updated: true });
setLocale(lang);
};
const Langmenu = (
<Menu>
{localesList?.map((lang: any) => (
<Menu.Item key={lang}>
<a onClick={() => setNewLang(lang)}>{langNameHandler(lang)}</a>
</Menu.Item>
))}
</Menu>
);
return (
<div className={styles.profileDropdownContainer}>
<Dropdown
overlay={Langmenu}
placement="bottomLeft"
trigger={['click']}
onVisibleChange={onLangVisibleChange}
className={styles.dropdown}
>
<div className={styles.langContainer}>
<span>
<GlobalOutlined /> {langNameHandler(currentLocale)}
</span>
{!langvisible ? <CaretDownOutlined /> : <CaretUpOutlined />}
</div>
</Dropdown>
</div>
);
};
export default LanguageDropdown;
-RightContext
import { Tag } from 'antd';
import type { Settings as ProSettings } from '#ant-design/pro-layout';
import React, { useEffect, useState } from 'react';
import type { ConnectProps } from 'umi';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { ConnectState } from '#/models/connect';
import Avatar from './AvatarDropdown';
import styles from './index.less';
import LanguageDropdown from '../languageDropdown';
import moment from 'moment';
export type GlobalHeaderRightProps = {
dispatch: Dispatch;
theme?: ProSettings['navTheme'] | 'realDark';
auth: any;
users: any;
platformLanguage: any;
data: any;
} & Partial<ConnectProps> &
Partial<ProSettings>;
const ENVTagColor = {
dev: 'orange',
test: 'green',
pre: '#87d068',
};
const GlobalHeaderRight: React.FC<GlobalHeaderRightProps> = (props) => {
const [updateLang, setUpdateLang] = useState<{ lang: string; updated: boolean }>({
lang: '',
updated: false,
});
const [currentLang, setCurrentLang] = useState<any>(null);
const { theme, layout, auth, platformLanguage, data, dispatch } = props;
let className = styles.right;
useEffect(() => setCurrentLang(platformLanguage), [platformLanguage]);
useEffect(() => {
if (updateLang.updated) {
const {
organization,
roles,
email,
deleted,
department,
createdById,
organizationId,
...rest
} = data;
const birthdate = moment(rest.birthdate).format('YYYY-MM-DD');
const workversary = moment(rest.workversary).format('YYYY-MM-DD');
dispatch({
type: 'users/updateMe',
payload: {
data: { ...rest, birthdate, workversary, platformLanguage: updateLang.lang },
userId: auth?.currentUser.id,
},
});
setUpdateLang({ ...updateLang, updated: false });
setCurrentLang(updateLang.lang);
}
}, [updateLang]);
if (theme === 'dark' && layout === 'top') {
className = `${styles.right} ${styles.dark}`;
}
return (
<div className={className}>
{currentLang ? (
<LanguageDropdown currentLang={currentLang} setUpdateLang={setUpdateLang} />
) : null}
<Avatar />
{REACT_APP_ENV && (
<span>
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
</span>
)}
</div>
);
};
export default connect(({ settings, auth, users }: ConnectState) => ({
theme: settings.navTheme,
layout: settings.layout,
auth,
users,
platformLanguage: auth?.currentUser?.membership?.platformLanguage,
data: auth?.currentUser?.membership,
}))(GlobalHeaderRight);
-LoginLayout
import React, { useEffect, useState } from 'react';
import type { ConnectState } from '#/models/connect';
import type { MenuDataItem } from '#ant-design/pro-layout';
import { getMenuData, getPageTitle } from '#ant-design/pro-layout';
import { useIntl, connect } from 'umi';
import type { ConnectProps } from 'umi';
import { Col, Row } from 'antd';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import LoginImage from '../assets/loginImage.png';
import SignUpAdminImage from '../assets/adminSignup.svg';
import styles from './LoginLayout.less';
import LanguageDropdown from '#/components/languageDropdown';
import SignupSideText from '#/components/SignupSideText';
export type UserLayoutProps = {
breadcrumbNameMap: Record<string, MenuDataItem>;
} & Partial<ConnectProps>;
const LoginLayout: React.FC<UserLayoutProps> = (props) => {
const [layoutImage, setLayoutImage] = useState(LoginImage);
const [updateLang, setUpdateLang] = useState<{ lang: string; updated: boolean }>({
lang: '',
updated: false,
});
const [currentLang, setCurrentLang] = useState<any>('');
useEffect(() => {
if (updateLang.updated) {
setUpdateLang({ ...updateLang, updated: false });
setCurrentLang(updateLang.lang);
}
}, [updateLang]);
useEffect(() => {
if (window.location.pathname === '/user/adminSignup/step1') {
setLayoutImage(SignUpAdminImage);
} else if (window.location.pathname === '/user/adminSignup/step2') {
setLayoutImage('TextSignup');
} else setLayoutImage(LoginImage);
}, [window.location.pathname]);
const {
route = {
routes: [],
},
} = props;
const { routes = [] } = route;
const {
children,
location = {
pathname: '',
},
} = props;
const { formatMessage } = useIntl();
const { breadcrumb } = getMenuData(routes);
const title = getPageTitle({
pathname: location.pathname,
formatMessage,
breadcrumb,
...props,
});
return (
<>
<HelmetProvider>
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
</Helmet>
<div className={styles.container}>
<Col
xl={{ span: 12, order: 1 }}
xs={{ span: 0, order: 2 }}
md={{ span: 0, order: 2 }}
lg={{ span: 0, order: 2 }}
style={{ backgroundColor: '#00bfa5' }}
>
{layoutImage === 'TextSignup' ? (
<SignupSideText />
) : (
<img alt="logo" width="100%" height="100%" src={layoutImage} />
)}
</Col>
<Col
xl={{ span: 12, order: 2 }}
lg={{ span: 24, order: 2 }}
sm={{ span: 24, order: 1 }}
xs={{ span: 24, order: 1 }}
>
{' '}
<Row justify="end" className="languageRow">
<LanguageDropdown currentLang={currentLang} setUpdateLang={setUpdateLang} />
</Row>
{children}
</Col>
</div>
</HelmetProvider>
</>
);
};
export default connect(({ settings }: ConnectState) => ({ ...settings }))(LoginLayout);

Clicking link from focused side nav opens page scrolled down

I've inherited a react/node/prismic application which has a ScrollToTop.js file to make sure pages load at the top, which they do when accessed from our main navigation menu.
This application also has a little side nav that's hidden until you scroll down (controlled with tabIndex).
The bug: when you click a link from the side nav, the resulting page comes up however far down you had scrolled when opening the side nav. Instead, I want these to start at the top.
We have a Layout.js file for the overall layout, and a specific SideNav.js for that little side nav. I'm new to react/javascript, and I haven't been able to figure out how to either (a) apply the ScrollToTop logic to these sidenav links or (b) add an additional window.scrollTo(0,0) for this special case. Can anyone recommend how/where this can be updated?
SideNav.js:
import React from 'react'
import CTAButton from './CTAButton'
import { Link } from 'react-router-dom'
import { Link as PrismicLink } from 'prismic-reactjs'
import PrismicConfig from '../../../prismic-configuration'
import PropTypes from 'prop-types'
import * as PrismicTypes from '../Utils/Types'
const matchPaths = {
'about': 'About us',
'projects': 'Projects',
'news': 'News'
}
class SideNav extends React.Component {
constructor(props) {
super(props)
this.state = {
expanded: false
}
this.handleKey = this.handleKey.bind(this)
this.expandMenu = this.expandMenu.bind(this)
this.keypressed = undefined
this.main = undefined
this.bug = undefined
this.toggle = false
this.windowTop = 0
}
checkMobile() {
if (window.innerWidth <= 800) {
this.setState({scrollThreshold: 50})
}
}
componentDidMount() {
this.checkMobile()
this.keypressed = this.handleKey
window.addEventListener('keydown', this.keypressed)
this.main = document.getElementsByClassName('top-nav-logo')[0]
this.bug = document.getElementsByClassName('side-nav-bug-wrap')[0]
}
componentWillUnMount() {
window.removeEventListener('keydown', this.keypressed)
}
handleKey(e) {
if (e.keyCode === 27 && this.state.expanded) {
this.expandMenu()
}
}
expandMenu() {
const el = document.scrollingElement || document.documentElement
if (!this.state.expanded) {
this.windowTop = el.scrollTop
} else {
el.scrollTop = this.windowTop
}
this.setState({
expanded: !this.state.expanded
})
document.body.classList.toggle('nav-lock')
}
render() {
const expanded = this.state.expanded ? 'expanded' : 'contracted'
const tabIndex = this.state.expanded ? '1' : '-1'
if (this.state.scrollThreshold === 50 && this.state.expanded) {
this.props.removeListener()
this.main.setAttribute('style', 'display:none')
this.bug.setAttribute('style', 'display:none; position:absolute')
}
else if (this.state.scrollThreshold === 50 && !this.state.expanded) {
this.props.addListener()
this.main.setAttribute('style', 'display:inline')
this.bug.setAttribute('style', 'display:flex; position:fixed')
}
const menu = this.props.menu.map((menuItem, index) => {
const menuLink = PrismicLink.url(menuItem.link, PrismicConfig.linkResolver)
const label = menuItem.label[0].text
let marker
if (typeof this.props.location !== 'undefined') {
// Match label to window location to move indicator dot
marker = label === matchPaths[this.props.location] ? this.props.location : 'inactive'
}
return (
<li key={index} className="side-nav-li">
<Link to={label} className="side-nav-link" onClick={this.expandMenu} tabIndex={tabIndex}>{label}</Link>
<div className={`side-nav-marker ${marker}`}/>
</li>
)
})
return (
<div className='side-nav-wrapper'>
<Link to='/' className={`side-nav-logo-main ${this.props.visibility}`} alt=""/>
<div className={`side-nav-bug-wrap ${this.props.visibility}`} onClick={this.expandMenu}>
<div className='side-nav-bug-icon' />
</div>
<div className={`side-nav ${expanded}`}>
<div className={'side-nav-menu-wrap'}>
<div className="side-nav-control-wrap">
<Link to="/" className="side-nav-logo" alt="Count Me In logo" onClick={this.expandMenu} tabIndex={tabIndex}/>
<button className="side-nav-exit" onClick={this.expandMenu} tabIndex={tabIndex}/>
</div>
<nav>
<ul className="side-nav-menu-items">
{ menu }
<CTAButton />
</ul>
</nav>
</div>
</div>
<div className={`side-nav-overlay ${expanded}`} onClick={this.expandMenu}/>
</div>
)
}
}
SideNav.propTypes = {
removeListener: PropTypes.func,
addListener: PropTypes.func,
location: PropTypes.string,
visibility: PropTypes.string,
menu: PropTypes.arrayOf(PropTypes.shape({
label: PrismicTypes.PrismicTextTypes,
link: PropTypes.shape({
id: PropTypes.string,
isBroken: PropTypes.bool,
lang: PropTypes.string,
link_type: PropTypes.string,
slug: PropTypes.string,
tags: PropTypes.array,
type: PropTypes.string,
uid: PropTypes.string
})
}))
}
export default SideNav
Layout.js:
const matchPaths = {
'about': 'About us',
'projects': 'Projects',
'news': 'News'
}
class Layout extends React.Component {
constructor(props) {
super(props)
this.state = {
location: undefined,
displayMobile: false,
visibility: 'offscreen',
scrollThreshold: 100
}
this.onMobileClick = this.onMobileClick.bind(this)
this.logoClick = this.logoClick.bind(this)
this.scrollCheck = this.scrollCheck.bind(this)
this.addScrollCheck = this.addScrollCheck.bind(this)
this.removeScrollCheck = this.removeScrollCheck.bind(this)
this.addChildScroll = this.addChildScroll.bind(this)
this.removeChildScroll = this.removeChildScroll.bind(this)
this.checkCount = 0
this.childCheckCount = 0
this.scrollCheckToggle = true
this.childCheckToggle = true
}
getBodyScrollTop () {
const el = document.scrollingElement || document.documentElement
return el.scrollTop
}
// Need to do this on didMount so window object is available.
componentDidMount() {
const loc = window.location.pathname.substr(1)
this.setState({ location: loc })
//Hide the loader and display the site content.
setTimeout(function() {
document.body.classList.add('is-loaded')
}, 50)
this.addScrollCheck()
if (this.getBodyScrollTop() > this.state.scrollThreshold) {
this.setState({
visibility: 'onscreen'
})
}
}
addChildScroll() {
if (this.childCheckCount < 1 ) {
window.addEventListener('scroll', this.scrollCheck)
this.childCheckCount++
this.childCheckToggle = true
}
}
removeChildScroll() {
if (this.childCheckCount === 1) {
window.removeEventListener('scroll', this.scrollCheck)
this.childCheckCount--
this.childCheckToggle = false
}
}
addScrollCheck() {
if (this.checkCount < 1 ) {
window.addEventListener('scroll', this.scrollCheck)
this.checkCount++
this.scrollCheckToggle = true
}
}
removeScrollCheck() {
if (this.checkCount === 1) {
window.removeEventListener('scroll', this.scrollCheck)
this.checkCount--
this.scrollCheckToggle = false
}
}
componentWillUnMount() {
this.removeScrollCheck()
}
scrollCheck() {
const scrollPos = this.getBodyScrollTop()
const newVis = scrollPos > this.state.scrollThreshold ? 'onscreen' : 'offscreen'
const curVis = this.state.visibility
newVis !== curVis && (
this.setState({
visibility: scrollPos > this.state.scrollThreshold ? 'onscreen' : 'offscreen'
})
)
}
// Need to do it again on didUpdate to handle nav updates
componentDidUpdate() {
let loc = window.location.pathname
loc = loc.substr(1)
if (loc !== this.state.location) {
this.setState({ location: loc })
}
}
// this class assignment sets all the mobile menu style changes & transitions
onMobileClick() {
// but we only want to do this setting on mobile
if (window.innerWidth < 800) {
this.scrollCheckToggle ? this.removeScrollCheck() : this.addScrollCheck()
this.setState({
displayMobile: this.state.displayMobile === true ? false : true
})
const appContainer = document.getElementsByClassName('app-container')[0]
appContainer.classList.toggle('locked')
}
}
logoClick() {
if (this.state.displayMobile === true) {
this.setState({displayMobile: false})
const appContainer = document.getElementsByClassName('app-container')[0]
appContainer.classList.toggle('locked')
}
}
render() {
const mobileDisplay = this.state.displayMobile === true ? '-active' : '-inactive'
const menu = this.props.menu.map((menuItem, index) => {
const menuLink = PrismicLink.url(menuItem.link, PrismicConfig.linkResolver)
const label = menuItem.label[0].text
let marker
if (typeof this.state.location !== 'undefined') {
// Match label to window location to move indicator dot
marker = label === matchPaths[this.state.location] ? this.state.location : 'inactive'
}
return (
<li key={index} className="top-nav-li">
<Link to={label} className="top-nav-link" onClick={this.onMobileClick}>{label}</Link>
<div className={`top-nav-marker ${marker}`} />
</li>
)
})
return (
<div className="app-container">
<Loader />
<SideNav menu={this.props.menu} location={this.state.location} visibility={this.state.visibility} addListener={this.addChildScroll} removeListener={this.removeChildScroll}/>
<header className='top-nav-container CONSTRAIN'>
<Link to="/" className={`top-nav-logo ${this.state.visibility}`} onClick={this.logoClick} alt="Count Me In logo"/>
<div className="top-nav-mobile-wrapper">
<div className={'top-nav-mobile-title'} onClick={this.onMobileClick} tabIndex="0">
<span className={`top-nav-mobile-title-text${mobileDisplay}`}>Menu</span>
<div className={`top-nav-mobile-icon${mobileDisplay}`} />
</div>
</div>
<nav className={`top-nav-menu-container${mobileDisplay}`}>
<ul className="top-nav-ul">
{menu}
<CTAButton/>
</ul>
</nav>
</header>
<main>
{this.props.children}
</main>
<Footer projects={this.props.projects} footerData={this.props.footerData} />
</div>
)
}
}
Layout.propTypes = {
children: PropTypes.node,
menu: PropTypes.arrayOf(PropTypes.shape({
label: PrismicTypes.PrismicTextTypes,
link: PropTypes.shape({
id: PropTypes.string,
isBroken: PropTypes.bool,
lang: PropTypes.string,
link_type: PropTypes.string,
slug: PropTypes.string,
tags: PropTypes.array,
type: PropTypes.string,
uid: PropTypes.string
})
})),
projects: PropTypes.arrayOf(PropTypes.shape({
data: PropTypes.shape({
project_name: PrismicTypes.PrismicTextTypes,
learn_more_link: PropTypes.shape({
link_type: PropTypes.string,
target: PropTypes.string,
url: PropTypes.string
})
})
})),
footerData: PropTypes.arrayOf(PropTypes.shape({
label: PrismicTypes.PrismicTextTypes,
link: PropTypes.shape({
link_type: PropTypes.string,
target: PropTypes.string,
url: PropTypes.string
})
}))
}
export default Layout
ScrollToTop.js:
import React from 'react'
class ScrollToTop extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0)
}
}
componentDidMount() {
window.scrollTo(0, 0)
}
render() {
return this.props.children
}
}
export default (ScrollToTop)
router.js:
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import ScrollToTop from './app/Utils/ScrollToTop'
import routes from './routes'
export default (({prismicCtx, PRISMIC_UNIVERSAL_DATA}) => {
return (
<ScrollToTop>
<Switch>
{routes(prismicCtx, PRISMIC_UNIVERSAL_DATA).map((route, index) => {
const copyRoute = Object.assign({}, route)
if (copyRoute.render) delete copyRoute.component
return <Route key={`route-${index}`} {...copyRoute} />
})}
</Switch>
</ScrollToTop>
)
})
Looking at the different react lifecycle steps, I added the same scrollTo behavior for componentWillUpdate (in ScrollToTop.js), and these pages load at the top from this menu again!
componentWillUpdate() {
window.scrollTo(0, 0)
}

Component Won't Rerender After Record Has Been Updated

I'm trying to show an icon based on whether or not the current user Id equals the accordion Id.
i.e. I'm trying to let users who made a card be the ones that can edit the card.
This starts to happen where it says //Update icon in code.
The record successfully updates (I can see in the database, and when you refresh the page, it shows correctly without errors), but when the component tries to re-render, I get this error: https://imgur.com/a/YVTDg82
I feel like this is something obvious, and I'm missing something easy.
import React from "react"
import PropTypes from "prop-types"
import { Accordion, Icon, Input, Form, Button, Modal, Dropdown } from 'semantic-ui-react'
//components
import UpdateCard from "./UpdateCard"
import LikeUnlike from "./LikeUnlike"
class Card extends React.Component {
constructor(props) {
super(props)
this.state = {
activeIndex: null,
search: ''
}
}
updateSearch = (event) => {
this.setState({ search: event.target.value.substr(0, 20) })
}
//Opens and closes the accordion
handleClick = (e, titleProps) => {
const { index } = titleProps
const { activeIndex } = this.state
const newIndex = activeIndex === index ? -1 : index
this.setState({ activeIndex: newIndex })
}
render() {
// const options = [
// { key: 'css', text: 'CSS', value: 'css' },
// { key: 'html', text: 'HTML', value: 'html' },
// { key: 'javascript', text: 'Javascript', value: 'javascript' },
// { key: 'rails', text: 'Rails', value: 'rails' },
// { key: 'react', text: 'React', value: 'react' },
// { key: 'ruby', text: 'Ruby', value: 'ruby' },
// ]
const { activeIndex } = this.state
const { showEditMenu } = this
let filteredCards = this.props.librarys.filter(
(library) => {
return library.title.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1 || library.desc.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
}
)
return (
<React.Fragment>
<div className='search-bar'>
<Input fluid icon={<Icon name='search' inverted circular link />} value={this.state.search} onChange={this.updateSearch} placeholder="Search Syntaxes" />
</div>
<ul>
{filteredCards.map((librarys, index) => {
return (
<div key={index} >
<Accordion>
<Accordion.Title >
<Icon
name='dropdown'
active={activeIndex === index}
index={index}
onClick={this.handleClick}
/>
<Icon name='trash alternate'
onClick={() => {
this.props.handleDelete(librarys.id)
}}
/>
{
//Update Icon
librarys.user.id === this.props.currentUser.id ?
<UpdateCard
handleUpdate={this.props.handleUpdate}
libraryId={librarys.id}
likes={librarys.likes}
librarys={librarys}
/>
: ''
}
Title: {librarys.title} Likes: {librarys.likes}
<LikeUnlike
handleUpdate={this.props.handleUpdate}
libraryId={librarys.id}
librarys={librarys}
/>
</Accordion.Title>
<Accordion.Content active={activeIndex === index}>
Description: <br></br>
{librarys.desc} <br></br>
Markdown: <br></br>
{librarys.markdown}
</Accordion.Content>
</Accordion>
</div>
)
})}
</ul>
</React.Fragment>
);
}
}
export default Card
You need to do a null check before comparing the values. Like below:
librarys.user && librarys.user.id === this.props.currentUser.id ?
// same stuff here

React: How to update one component, when something happens on another component

I have an application with a table, the table has a checkbox to set a Tenant as Active, this variable is a global variable that affects what the user does in other screens of the application.
On the top right of the application, I have another component called ActiveTenant, which basically shows in which tenant the user is working at the moment.
The code of the table component is like this:
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id,
TestSiteCollectionUrl: row.TestSiteCollectionUrl,
TenantName: row.TenantName,
Email: row.Email
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'TenantName',
dataIndex: 'TenantName',
key: 'TenantName',
},
{
title: 'TestSiteCollectionUrl',
dataIndex: 'TestSiteCollectionUrl',
key: 'TestSiteCollectionUrl',
},
{
title: 'Email',
dataIndex: 'Email',
key: 'Email',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].TenantName != undefined){
console.log(selectedRows[0].TenantName);
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant set to active',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not activated',
error
);
console.error(error);
});
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
And the code of the ActiveTenant component its also very simple
import React, { Component } from 'react';
import authAction from '../../redux/auth/actions';
import { adalApiFetch } from '../../adalConfig';
class ActiveTenant extends Component {
constructor(props) {
super(props);
this.state = {
tenant: ''
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant/GetActiveTenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ tenant: responseJson.TenantName });
}
})
.catch(error => {
this.setState({ tenant: '' });
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
return (
<div>You are using tenant: {this.state.tenant }</div>
);
}
}
export default ActiveTenant;
The problem is, if I have multiple tenants on my database registered and I set them to active, the server side action occurs, and the state is changed, however on the top right its still showing the old tenant as active, UNTIL I press F5 to refresh the browser.
How can I achieve this?
For the sake of complete understandment of my code I will need to paste below other components:
TopBar which contains the active tenant
import React, { Component } from "react";
import { connect } from "react-redux";import { Layout } from "antd";
import appActions from "../../redux/app/actions";
import TopbarUser from "./topbarUser";
import TopbarWrapper from "./topbar.style";
import ActiveTenant from "./activetenant";
import TopbarNotification from './topbarNotification';
const { Header } = Layout;
const { toggleCollapsed } = appActions;
class Topbar extends Component {
render() {
const { toggleCollapsed, url, customizedTheme, locale } = this.props;
const collapsed = this.props.collapsed && !this.props.openDrawer;
const styling = {
background: customizedTheme.backgroundColor,
position: 'fixed',
width: '100%',
height: 70
};
return (
<TopbarWrapper>
<Header
style={styling}
className={
collapsed ? "isomorphicTopbar collapsed" : "isomorphicTopbar"
}
>
<div className="isoLeft">
<button
className={
collapsed ? "triggerBtn menuCollapsed" : "triggerBtn menuOpen"
}
style={{ color: customizedTheme.textColor }}
onClick={toggleCollapsed}
/>
</div>
<ul className="isoRight">
<li
onClick={() => this.setState({ selectedItem: 'notification' })}
className="isoNotify"
>
<TopbarNotification locale={locale} />
</li>
<li>
<ActiveTenant />
</li>
<li
onClick={() => this.setState({ selectedItem: "user" })}
className="isoUser"
>
<TopbarUser />
<div>{ process.env.uiversion}</div>
</li>
</ul>
</Header>
</TopbarWrapper>
);
}
}
export default connect(
state => ({
...state.App.toJS(),
locale: state.LanguageSwitcher.toJS().language.locale,
customizedTheme: state.ThemeSwitcher.toJS().topbarTheme
}),
{ toggleCollapsed }
)(Topbar);
App.js which contains the top bar
import React, { Component } from "react";
import { connect } from "react-redux";
import { Layout, LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import { Debounce } from "react-throttle";
import WindowResizeListener from "react-window-size-listener";
import { ThemeProvider } from "styled-components";
import authAction from "../../redux/auth/actions";
import appActions from "../../redux/app/actions";
import Sidebar from "../Sidebar/Sidebar";
import Topbar from "../Topbar/Topbar";
import AppRouter from "./AppRouter";
import { siteConfig } from "../../settings";
import themes from "../../settings/themes";
import { themeConfig } from "../../settings";
import AppHolder from "./commonStyle";
import "./global.css";
import { AppLocale } from "../../dashApp";
import ThemeSwitcher from "../../containers/ThemeSwitcher";
const { Content, Footer } = Layout;
const { logout } = authAction;
const { toggleAll } = appActions;
export class App extends Component {
render() {
const { url } = this.props.match;
const { locale, selectedTheme, height } = this.props;
const currentAppLocale = AppLocale[locale];
return (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<AppHolder>
<Layout style={{ height: "100vh" }}>
<Debounce time="1000" handler="onResize">
<WindowResizeListener
onResize={windowSize =>
this.props.toggleAll(
windowSize.windowWidth,
windowSize.windowHeight
)
}
/>
</Debounce>
<Topbar url={url} />
<Layout style={{ flexDirection: "row", overflowX: "hidden" }}>
<Sidebar url={url} />
<Layout
className="isoContentMainLayout"
style={{
height: height
}}
>
<Content
className="isomorphicContent"
style={{
padding: "70px 0 0",
flexShrink: "0",
background: "#f1f3f6",
position: "relative"
}}
>
<AppRouter url={url} />
</Content>
<Footer
style={{
background: "#ffffff",
textAlign: "center",
borderTop: "1px solid #ededed"
}}
>
{siteConfig.footerText}
</Footer>
</Layout>
</Layout>
<ThemeSwitcher />
</Layout>
</AppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
}
}
export default connect(
state => ({
auth: state.Auth,
locale: state.LanguageSwitcher.toJS().language.locale,
selectedTheme: state.ThemeSwitcher.toJS().changeThemes.themeName,
height: state.App.toJS().height
}),
{ logout, toggleAll }
)(App);
I think this should be enough to illustrate my question.
You're not using redux correctly there. You need to keep somewhere in your redux state your active tenant; That information will be your single source of truth and will be shared among your components throughout the app. Every component that will need that information will be connected to that part of the state and won't need to hold an internal state for that information.
Actions, are where you would call your API to get your active tenant information or set a new tenant as active. These actions wil call reducers that will change your redux state (That includes your active tenant information) and those redux state changes will update your app components.
You should probably take some time to read or re-read the redux doc, it's short and well explained !

2 Props passed to Child Component with a Map Function

Hi how can I pass the Path into the map function! Have tried as nested array but can't get it
Navbar (parent component):
class Navbar extends Component {
constructor(props) {
super(props);
this.state = {
nav: [{
topic: 'Home',
path: '/',
},
{
topic: 'Bogen',
path: '/Bogen'
},
],
topics: ['Home', 'Bogen', 'Projekt', 'Nutzer'],
path: ['/', '/Bogen', '/Projekt', '/Nutzer'],
}
}
render() {
return (
<div className='navbar'>
<NavbarTopics topics={this.state.topics}
/>
</div>
)
}
}
NavbarTopics (child component):
const NavbarTopics = (props) => (
<ul className='ul_Ntopics'>
{props.topics.map((topic, index) => <NavTopic topic={topic} key={index} />)}
</ul>
)
NavTopic (child component):
const NavTopic = (props) => <li className='li_Ntopic'><Link className='Link_Ntopic' to=''>{props.topic}</Link></li>;
NavTopic.propTypes = {
topic: PropTypes.string.isRequired,
}
export default NavTopic;
How can I pass the state path to the map-function, so that I can pass it as prop to NavTopic?
Instead of passing topic as a prop, pass nav.
//Navbar
<NavbarTopics nav={this.state.nav}
Then iterate through nav array
// NavbarTopics
const NavbarTopics = (props) => (
<ul className='ul_Ntopics'>
{props.nav.map((nav, index) => <NavTopic nav={nav} key={index} />)}
</ul>
)
In NavTopic,
const NavTopic = (props) => <li className='li_Ntopic'>
<Link className='Link_Ntopic' to={props.nav.path}>{props.nav.topic}</Link>
</li>;

Categories

Resources