StencilJS emit event to React - reactjs

I have a nested set of StencilJS components. I would like to attach a function to my nested component so that my React app, which hosts the parent component, can read.
Example
<pw-actionbar
actions={getActions}
/>
In this actionbar component, I have another nested button component. It looks like this
return (
<Host>
<div class="container">
{
// iterate through array
this.actions.map((action) => {
// take object.icon and make an icon
const XmlIcon = `${action.icon}`;
==> I WANT A FUNCTION ON PW-BUTTON THAT PASSES 'action' which my react app reads
return <pw-button-side-menu
// shade the selected pages button
isselected={action.onpage ? 'selected' : 'notselected'}
class="displace"
>
<span slot="label">{action.name}</span>
<i slot="icon">
<XmlIcon
class="icon-position"
fillcolor={this.iconfillcolor}
strokecolor={this.iconstrokecolor}/>
</i>
</pw-button-side-menu>
})
}
</div>
</Host>
);
}
My react app has some component
functionEmittedFromPwButton(action) {
console.log(action) <=== I WANT THIS TO WORK IN MY REACT APP WHICH IS EMITTED FROM THE PW-BUTTON COMPONENT NESTED IN THE PW-ACTIONBAR COMPONENT
}
return (
<MyComponent>
<pw-actionbar actions={getActions}/> <=== that takes an array of objects. I want to capture the 'action' object emitted by the pw-button nested in this component in my react app
</MyComponent>
)
I have tried all sorts of different methods like this one to try to emit the object from stencil to react

On the stenciljs side
import { Component, h, Host, Prop, Event, EventEmitter } from "#stencil/core";
#Component({
tag: "pw-actionbar",
styleUrl: "pw-actionbar.scss",
shadow: true,
})
export class PwActionbar {
#Prop() actions: any = [];
#Prop() iconfillcolor: "white" | "black" = "white";
#Prop() iconstrokecolor: "white" | "black" = "white";
#Event() emitAction: EventEmitter;
render() {
const handleClick = (action) => {
this.emitAction.emit(action);
};
return (
<Host>
<div class="container">
{
// iterate through array
this.actions.map((action) => {
// take object.icon and make an icon
const XmlIcon = `${action.icon}`;
// cast the button
return (
<pw-button-side-menu
// shade the selected pages button
isselected={action.onpage ? "selected" : "notselected"}
class="displace button-lines"
onClick={() => handleClick(action)}
>
<span slot="label">{action.name}</span>
<i slot="icon">
<XmlIcon
class="icon-position"
fillcolor={this.iconfillcolor}
strokecolor={this.iconstrokecolor}
/>
</i>
</pw-button-side-menu>
);
})
}
</div>
</Host>
);
}
}
On the react side
const handleAction = async (action, history, i18n) => {
Metrics.track("Changed Page", { action });
if ("sign-out" === action) {
await authActions.logout();
history.push(`/${i18n.locale}`);
} else if ("help-desk" === action) {
history.push(`/${i18n.locale}/zendesk`);
} else if ("advisors" === action) {
pageActionsObjAdmin[0].onpage = true;
history.push(`/${i18n.locale}/admin/advisors`);
} else if ("users" === action) {
pageActionsObjAdmin[1].onpage = true;
history.push(`/${i18n.locale}/admin/users`);
} else if ("forecast" === action) {
pageActionsObjAdmin[3].onpage = true;
history.push(`/${i18n.locale}/admin/forecast`);
} else if ("stats" === action) {
pageActionsObjAdmin[4].onpage = true;
history.push(`/${i18n.locale}/admin/stats`);
}
};
const Layout = ({ children }) => {
const { i18n } = useLingui();
const [, setContext] = useContext(StripeErrorContext);
const history = useHistory();
useEffect(() => {
const listener = (e) => {
// set page button to be "Active"
pageActionsObjAdmin.forEach((element) => {
element.onpage = false;
});
handleAction(e.detail.page, history, i18n, setContext);
};
// listen for events emitted form the action bar
document.body.addEventListener("emitAction", listener);
return () => {
document.body.removeEventListener("emitAction", listener);
};
}, []); // eslint-disable-line
// refs for the actionbar
const elementRef = useRef(null);
useEffect(() => {
if (elementRef.current !== null) {
elementRef.current.actions = pageActionsObjAdmin;
}
}, [elementRef]);
return (
<Wrapper>
<Header />
<BodyLayout>
<pw-actionbar
ref={(el) => (elementRef.current = el)}
style={{ paddingTop: "56px", zIndex: "99" }}
class="action-bar"
/>
<div className="main-layout" style={{ width: "100%" }}>
{children}
</div>
</BodyLayout>
</Wrapper>
);
};
export default Layout;

Related

React recaptcha is displayed

I have this code, I would like to make recaptcha hidden,
how to do it, my recaptcha show general conditions with icon in the right lower corner.
Do I need to clean recaptcha?
window['recaptchaVerifier'].clear()
I won't to show this icon, recaptcha logo. I need it
to make 2FA authentication sms + email. It should work
without showing some elements, or is problem that I have
this div?
<div ref={recaptchaWrapperRef}>
<div id="recaptcha-container"></div>
</div>
import { Button } from '#material-ui/core'
import React, { useEffect, useRef } from 'react'
import { auth, provider } from '../firebase/firebase'
import useDeviceDetect from '../utils/useDeviceDetect'
import './Login.scss'
declare global {
interface Window { recaptchaVerifier: any; }
}
function Login(props: any) {
const val = useRef()
const recaptchaWrapperRef = useRef(null);
useEffect(() => {
initialiseRecaptcha()
}, [])
const { isMobile } = useDeviceDetect();
const initialiseRecaptcha = () => {
setTimeout(() => {
if (!window['recaptchaVerifier']) {
window['recaptchaVerifier'] = new auth.RecaptchaVerifier(
"recaptcha-container",
{
'size': "invisible"
}
);
}
}, 2000)
}
const TwoFactorAuthentication = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
smsVerification()
signIn(e)
}
const signIn = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault()
auth().signInWithPopup(provider)
.then(result => {
localStorage.setItem('user', JSON.stringify(result.user));
window.location.href = '/'
}).catch(error => {
alert(error.message)
})
}
const smsVerification = () => {
if (window['recaptchaVerifier'] != null) {
new auth.PhoneAuthProvider().verifyPhoneNumber("+421944777170", window['recaptchaVerifier'])
.then(function (verificationId) {
var verificationCode = window.prompt('Please enter the verification code' +
', that was sent on mobile device')
if (verificationCode != null) {
return auth.PhoneAuthProvider.credential(verificationId, verificationCode)
}
})
.then(function (phoneCredential) {
if (phoneCredential != null) {
console.log(phoneCredential)
return auth().signInWithCredential(phoneCredential)
}
})
.catch(function (error) {
})
}
}
return (<>
<div className="login">
<div className="login__logo">
<img
src="https://i.pinimg.com/originals/c6/f2/cd/c6f2cd5e0ebf33ff1ae0b01d0407224c.png"
alt="" />
<img
src="https://svgshare.com/i/PTv.svg"
alt="" />
</div>
<Button id="sign-in-button" type="submit" onClick={TwoFactorAuthentication}>
Signin
</Button>
<div ref={recaptchaWrapperRef}>
<div id="recaptcha-container"></div>
</div>
</div>
</>
)
}
export default Login
EDIT:
I have found that I need set visibility:hidden on css of grecaptcha div, but it always rerender and disable my setting.
const hiddenGRecaptcha = () => {
var head = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
if (head !== undefined) {
head.style.visibility = "hidden"
console.log(head)
}
}
this is my new function in typescript to hide recaptcha badge but css are always overwritten don't know where to put it or how to make it persistent.

React - How to send a custom function from a child to parent component

I'm creating a custom steps wizard, please find the implementation below:
export const Wizard: React.FC<Props> = props => {
const {
steps,
startAtStep = 0,
showStepsNavigation = true,
prevButtonText = 'Back',
nextButtonText = 'Next',
onStepChange,
nextButtonTextOnFinalStep,
onNextClicked,
onPreviousClicked
} = props;
const [currentStep, setCurrentStep] = useState(startAtStep);
let CurrentStepComponent = steps[currentStep].Component;
const nextStep = () => {
setCurrentStep(currentStep + 1);
};
const previousStep = () => {
setCurrentStep(currentStep - 1);
};
const goToStep = (stepId: number) => {
const stepIndex = steps.findIndex(step => step.id == stepId);
if (stepIndex != -1) {
setCurrentStep(stepIndex);
}
};
const handlePreviousClick = () => {
if (onPreviousClicked && typeof onPreviousClicked == 'function') {
onPreviousClicked();
}
previousStep();
};
const handleNextClick = () => {
if (onNextClicked && typeof onNextClicked == 'function') {
onNextClicked();
}
nextStep();
};
return (
<article>
<section>
<CurrentStepComponent {...props} goToStep={goToStep} nextStep={nextStep} previousStep={previousStep} />
</section>
<footer>
<div className="wizard-buttons-container back-buttons">
<Button
className="wizard-button wizard-button--back"
secondary
onClick={handlePreviousClick}
disabled={steps[currentStep - 1] == null}
>
<i className="fas fa-chevron-left"></i>
{prevButtonText}
</Button>
</div>
<div className="wizard-buttons-container next-buttons">
<Button
className="wizard-button wizard-button--next"
onClick={handleNextClick}
disabled={steps[currentStep + 1] == null}
>
{steps[currentStep + 1] == null && nextButtonTextOnFinalStep ? nextButtonTextOnFinalStep : nextButtonText}
<i className="fas fa-chevron-right"></i>
</Button>
</div>
</footer>
</article>
);
};
The way I use it is as follows:
const steps = [
{
id: 1,
label: 'Directors and Owners',
Component: DirectorsAndOwnersStep
},
{
id: 2,
label: 'Bank Account',
Component: BankAccountStep
},
{
id: 3,
label: 'Company Documents',
Component: CompanyDocumentsStep
},
{
id: 4,
label: 'Review and Submit',
Component: ReviewAndSubmitStep
}
];
type Props = RouteComponentProps<MatchParams>;
export const EnterpriseOnboardingPage: React.FC<Props> = () => {
const onNext = () => {
console.log('Next Clicked');
};
const onPrevious = () => {
console.log('Previous Clicked');
};
return (
<section>
<Wizard
steps={steps}
nextButtonTextOnFinalStep="Submit"
onNextClicked={onNext}
onPreviousClicked={onPrevious}
/>
</section>
);
};
Now here is my problem, within the child components I want to handle what should happen when the user clicks Next, something like onNextClick execute this custom function in the child component rather than performing the default behaviour implemented in the Wizard component.
I've tried setting State in the wizard, "nextClbRegistered", and through a send the "setNextClbRegistered" to children to pass the custom function and execute it, then in the wizard in the "handleNextClick" if there is a function defined execute it. but it's always undefined.
Any ideas what is the best way to do it?
Thanks in Advance!
React is all about data flowing down in the components tree. If you want your Child to be able to show and/or modify a shared state between Child and Parent you should lift your state up and pass it down via props to it's children
const Parent = () =>{
const [title, settitle] = useState('foo')
return <Child title={title} setTitle={setTitle} />
}
const Child = ({ title, setTitle}) =>{
return <input value={title} onChange={e => setTitle(e.target.value)} />
}
In class based components
class Parent extends React.Component{
state = { title: '' }
setTitle = title => this.setState({ title })
render(){
const { title } = this.state
return <Child title={title} setTitle={this.setTitle} />
}
}
class Child extends React.Component{
render(){
const { title, setTitle } = this.props
return <input value={value} setTitle={e => setTitle(e.target.value)} />
}
}

Firebase/React/Redux Component has weird updating behavior, state should be ok

I am having a chat web app which is connected to firebase.
When I refresh the page the lastMessage is loaded (as the gif shows), however, for some reason, if the component is otherwise mounted the lastMessage sometimes flickers and disappears afterwards like it is overridden. When I hover over it, and hence update the component, the lastMessage is there.
This is a weird behavior and I spent now days trying different things.
I would be very grateful if someone could take a look as I am really stuck here.
The db setup is that on firestore the chat collection has a sub-collection messages.
App.js
// render property doesn't re-mount the MainContainer on navigation
const MainRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<MainContainer>
<Component {...props} />
</MainContainer>
)}
/>
);
render() {
return (
...
<MainRoute
path="/chats/one_to_one"
exact
component={OneToOneChatContainer}
/>
// on refresh the firebase user info is retrieved again
class MainContainer extends Component {
componentDidMount() {
const { user, getUserInfo, firebaseAuthRefresh } = this.props;
const { isAuthenticated } = user;
if (isAuthenticated) {
getUserInfo(user.id);
firebaseAuthRefresh();
} else {
history.push("/sign_in");
}
}
render() {
return (
<div>
<Navigation {...this.props} />
<Main {...this.props} />
</div>
);
}
}
Action
// if I set a timeout around fetchResidentsForChat this delay will make the lastMessage appear...so I must have screwed up the state / updating somewhere.
const firebaseAuthRefresh = () => dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user) {
localStorage.setItem("firebaseUid", user.uid);
dispatch(setFirebaseAuthUser({uid: user.uid, email: user.email}))
dispatch(fetchAllFirebaseData(user.projectId));
}
});
};
export const fetchAllFirebaseData = projectId => dispatch => {
const userId = localStorage.getItem("firebaseId");
if (userId) {
dispatch(fetchOneToOneChat(userId));
}
if (projectId) {
// setTimeout(() => {
dispatch(fetchResidentsForChat(projectId));
// }, 100);
...
export const fetchOneToOneChat = userId => dispatch => {
dispatch(requestOneToOneChat());
database
.collection("chat")
.where("userId", "==", userId)
.orderBy("updated_at", "desc")
.onSnapshot(querySnapshot => {
let oneToOne = [];
querySnapshot.forEach(doc => {
let messages = [];
doc.ref
.collection("messages")
.orderBy("created_at")
.onSnapshot(snapshot => {
snapshot.forEach(message => {
messages.push({ id: message.id, ...message.data() });
});
});
oneToOne.push(Object.assign({}, doc.data(), { messages: messages }));
});
dispatch(fetchOneToOneSuccess(oneToOne));
});
};
Reducer
const initialState = {
residents: [],
oneToOne: []
};
function firebaseChat(state = initialState, action) {
switch (action.type) {
case FETCH_RESIDENT_SUCCESS:
return {
...state,
residents: action.payload,
isLoading: false
};
case FETCH_ONE_TO_ONE_CHAT_SUCCESS:
return {
...state,
oneToOne: action.payload,
isLoading: false
};
...
Main.js
// ...
render() {
return (...
<div>{React.cloneElement(children, this.props)}</div>
)
}
OneToOne Chat Container
// without firebaseAuthRefresh I don't get any chat displayed. Actually I thought having it inside MainContainer would be sufficient and subscribe here only to the chat data with fetchOneToOneChat.
// Maybe someone has a better idea or point me in another direction.
class OneToOneChatContainer extends Component {
componentDidMount() {
const { firebaseAuthRefresh, firebaseData, fetchOneToOneChat } = this.props;
const { user } = firebaseData;
firebaseAuthRefresh();
fetchOneToOneChat(user.id || localStorage.getItem("firebaseId"));
}
render() {
return (
<OneToOneChat {...this.props} />
);
}
}
export default class OneToOneChat extends Component {
render() {
<MessageNavigation
firebaseChat={firebaseChat}
firebaseData={firebaseData}
residents={firebaseChat.residents}
onClick={this.selectUser}
selectedUserId={selectedUser && selectedUser.residentId}
/>
}
}
export default class MessageNavigation extends Component {
render() {
const {
onClick,
selectedUserId,
firebaseChat,
firebaseData
} = this.props;
<RenderResidentsChatNavigation
searchChat={this.searchChat}
residents={residents}
onClick={onClick}
firebaseData={firebaseData}
firebaseChat={firebaseChat}
selectedUserId={selectedUserId}
/>
}
}
const RenderResidentsChatNavigation = ({
residents,
searchChat,
selectedUserId,
onClick,
firebaseData,
firebaseChat
}) => (
<div>
{firebaseChat.oneToOne.map(chat => {
const user = residents.find(
resident => chat.residentId === resident.residentId
);
const selected = selectedUserId == chat.residentId;
if (!!user) {
return (
<MessageNavigationItem
id={chat.residentId}
key={chat.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
{residents.map(user => {
const selected = selectedUserId == user.residentId;
const chat = firebaseChat.oneToOne.find(
chat => chat.residentId === user.residentId
);
if (_isEmpty(chat)) {
return (
<MessageNavigationItem
id={user.residentId}
key={user.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
</div>
}
}
And lastly the item where the lastMessage is actually displayed
export default class MessageNavigationItem extends Component {
render() {
const { hovered } = this.state;
const { user, selected, chat, isGroupChat, group, id } = this.props;
const { messages } = chat;
const item = isGroupChat ? group : user;
const lastMessage = _last(messages);
return (
<div>
{`${user.firstName} (${user.unit})`}
{lastMessage && lastMessage.content}
</div>
)
}
In the end it was an async setup issue.
In the action 'messages' are a sub-collection of the collection 'chats'.
To retrieve them it is an async operation.
When I returned a Promise for the messages of each chat and awaited for it before I run the success dispatch function, the messages are shown as expected.

UIkit's modals in React: integration

I'm working on this project where the frontend is in React with UIkit for the user interface. The integration between the parts looks poorly implemented. I'm going to explain why. There is a Modal component, something like
export class Modal extends Component {
static getByName = name => UIkit.modal(`[data-modal-name='${name}']`)
static show = name => {
const modal = Modal.getByName(name)
if (modal) modal.show()
}
static hide = name => {
const modal = Modal.getByName(name)
if (modal) modal.hide()
}
render() {
// a modal
}
}
this is used in this way
export const LoginFormModal = props => (
<Modal name="login-form" className="login-form-modal" hideClose>
<LoginForm />
</Modal>
)
and show/hide is called programmatically where needed (even redux's actions)
Modal.hide("login-form")
this is in a Redux action, like this
export const login = credentials => {
return dispatch => {
dispatch(showLoader())
API.authentication.login(
credentials,
response => {
setCurrentUser(
Object.assign({}, response.user, { user_id: response.user.id })
)
Modal.hide("login-form")
dispatch(loginSucceded(response))
dispatch(hideLoader())
dispatch(push("/"))
dispatch(fetchNotificationsCounter())
},
error => {
dispatch(loginFailed(error))
dispatch(hideLoader())
}
)
}
}
This seems to work. Until you leave a component. When you come back to it, the second time the programmatically hide does not work anymore.
Anyone can lead me to how integrate the parts in a more react-appropriate way?
Using the parts of uikit which manipulate the dom (show, hide) is obviously hard to connect with React (and probably you shouldn't), however:
You need to move the call of the functions show and hide inside the Component by passing the bool of the state of the modal (eg. modalopen) . A good hook is the componentWillReceiveProps which can be used to check the previus props
componentWillReceiveProps(nextProps) {
if (nextProps.modalopen !== this.props.modalopen) {
if (nextProps.modalopen) {
getByName(...).show()
} else {
getByName(...).hide()
}
}
}
(this is inside the Modal class)
The thing I don't like and that is definitely not a "React-way" is that the code is mutating state directly from an action creator (!). From React docs:
For example, instead of exposing open() and close() methods on a
Dialog component, pass an isOpen prop to it.
So what if you had one modal that would be controlled by the redux state? Here is a possible implementation:
ModalWindow - will react to state changes and render depending what's in store:
import React from 'react';
import InfoContent from './InfoContent';
import YesOrNoContent from './YesOrNoContent';
import { MODAL_ACTION } from './modal/reducer';
class ModalWindow extends React.Component {
renderModalTitle = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return 'Info';
case MODAL_ACTION.YES_OR_NO:
return 'Are you sure?';
default:
return '';
}
};
renderModalContent = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return <InfoContent />;
case MODAL_ACTION.YES_OR_NO:
return <YesOrNoContent />;
default:
return null;
}
};
render() {
return (
this.props.isModalVisible ?
<div>
<p>{this.renderTitle()}</p>
<div>
{this.renderModalContent()}
</div>
</div>
:
null
);
}
}
export default connect((state) => ({
modalAction: state.modal.modalAction,
isModalVisible: state.modal.isModalVisible,
}))(ModalWindow);
modal reducer it will expose API to show/hide modal window in the application:
export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';
const INITIAL_STATE = {
isModalVisible: false,
modalAction: '',
};
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
case SHOW_MODAL:
return { ...state, isModalVisible: true, modalAction: action.modalAction };
case HIDE_MODAL:
return { ...state, isModalVisible: false };
default:
return state;
}
}
export const MODAL_ACTION = {
YES_OR_NO: 'YES_OR_NO',
INFO: 'INFO',
};
const showModal = (modalAction) => ({ type: SHOW_MODAL, modalAction });
export const hideModal = () => ({ type: HIDE_MODAL });
export const showInformation = () => showModal(MODAL_ACTION.INFO);
export const askForConfirmation = () => showModal(MODAL_ACTION.YES_OR_NO);
So basically you expose simple API in form of redux action-creators to control the state of your ModalWindow. Which you can later use like:
dispatch(showInformation())
...
dispatch(hideModal())
Of course, there could be more to it like optional configuration that would be passed to action creators or queue for modals.
I use a combination of a hook and a component for this.
Hook:
import { useState } from "react";
import UIkit from "uikit";
export default function useModal() {
const [isOpen, setIsOpen] = useState(false);
const [ref, setRef] = useState(null);
const open = (e) => {
UIkit.modal(ref).show();
setIsOpen(true);
};
const close = (e) => {
UIkit.modal(ref).hide();
UIkit.modal(ref).$destroy(true);
setIsOpen(false);
};
return [setRef, isOpen, open, close];
}
Component:
import React, { forwardRef } from "react";
const Modal = forwardRef(({ children, isOpen, full, close }, ref) => (
<div
ref={ref}
data-uk-modal="container: #root; stack: true; esc-close: false; bg-close: false"
className={`uk-flex-top ${full ? "uk-modal-container" : ""}`}
>
<div className="uk-modal-dialog uk-margin-auto-vertical">
<button
type="button"
className="uk-modal-close-default"
data-uk-icon="close"
onClick={close}
/>
{isOpen && children()}
</div>
</div>
));
export default Modal;
Consumption:
function Demo() {
const [ref, isOpen, open, close] = useModal();
return (
<div>
<button
type="button"
className="uk-button uk-button-default"
onClick={open}
>
upgrade
</button>
<Modal isOpen={isOpen} close={close} ref={ref} full>
{() => (
<div>
<div className="uk-modal-header">
<h2 className="uk-modal-title">title</h2>
</div>
<div className="uk-modal-body">
body
</div>
</div>
)}
</Modal>
</div>
);
}
Read more: https://reactjs.org/docs/integrating-with-other-libraries.html

React/Redux Why does specific component update, when its sibling’s child component updates, though its state doesn’t change

Update
The sidedrawers state is apparently different, but value does not change...
Details:
There is a layout component, which takes in routes from react router as children.
Inside the layout component code, two child components are rendered, Toolbar, and sidedrawer, and a main section that contains this.props.children.
One of the routes renders a component called page. Page renders another component called graphContainer, and passes it a click event, which is applied to the graphContainer’s button that it renders.
How it works is, I grab the first eight graphs and show 4 of them. When the button is clicked, it decides to either show the next 4 or grab the next eight.
This whole thing uses redux. There’s a page state, authentication state, navigation state, and a graph state. The only partial state changing when the button is clicked, is the graphs.
However, both the GraphContainer updates along with the sidedrawer component. As far as I can tell, nothing in the sidedrawer component is changing, so it should not trigger an update.
In the redux page for navigation state, the switch hits the default, which just returns state.
The graph redux portion works just fine, updates accordingly.
My workaround was to implement a dontUpdate prop in the navigation reducer state. And then use shouldComponentUpdate to check that prop, because the shallow check that was done by default, say with pureComponent, was seeing a different state or prop.
tl;dr: Any ideas why the sidedrawer component keeps updating, even though, as far as I can tell, there’s no prop or state change?
Reducers
const graphReducer = (state = initialState, action) => {
...
case SHOW_NEXTFOUR:
console.log('SHOW NEXT FOUR', state);
return {
...state,
ttlShown: action.ttlShown
};
default:
return state;
}
};
const navReducer = (state = initialState, action) => {
...
default:
return {...state, dontUpdate: true};
}
};
Layout Component
class Layout extends Component {
...
handleSideBarOpen = () => {
this.props.onSidebarToggle();
}
render () {
return (
<Aux>
<Toolbar
isAuth={this.props.isAuthenticated}
drawerToggleClicked={this.handleSideBarOpen}
/>
<SideDrawer
open={this.props.sidebarOpen}
closed={this.props.onSidebarToggle}
/>
<main className={classes.Content}>
{this.props.children}
</main>
</Aux>
)
}
}
const mapStateToProps = ({ navigation, auth }) => {
const { sidebarOpen } = navigation;
const { token } = auth;
return {
sidebarOpen,
isAuthenticated: token !== null
};
};
const mapDispatchToProps = {
onSidebarToggle, getNavTree
};
export default connect(
mapStateToProps, mapDispatchToProps
)(Layout);
Sidedrawer Component
class sideDrawer extends Component {
state = {
popupMenuOpen: false
}
shouldComponentUpdate ( nextProps, nextState ) {
if(nextProps.dontUpdate)
return false;
else return true;
}
…
render() {
…
let navitems = [];
if(this.props.navData && !this.props.error) {
navitems = (
<NavigationItems
showClients={this.props.showClientsBtn}
navData={this.props.navData}
curClientid={this.props.curClientid}
curSiteid={this.props.curSiteid}
curDashid={this.props.curDashid}
curPageid={this.props.curPageid}
closeSidebar={this.props.closed}
onPageClick={this.handlePageClick}
onCSDClick={this.handleOpenPopupMenu}
/>
);
} else
navitems = <p>Problem Loading Tree</p>;
return (
<Aux>
<div className={attachedClasses.join(' ')}>
<div className={classes.Logo}>
<div className={classes.CloseWrapper}>
<Chip onClick={this.props.closed} className={classes.CloseChip}>X</Chip>
</div>
<div className={classes.CrumbWrapper}>
<Breadcrumbs
backBtn={this.handleBackClick}
handleCrumbClick={this.handleCrumbClick}
breadcrumbs={this.props.breadcrumbs}
/>
</div>
</div>
<nav>
{navitems}
<Popover
style={{width: "90%"}}
open={this.state.popupMenuOpen}
anchorEl={this.state.anchorEl}
anchorOrigin={{horizontal: 'middle', vertical: 'bottom'}}
targetOrigin={{horizontal: 'middle', vertical: 'top'}}
onRequestClose={this.handleClosePopupMenu}
>
<Menu
style={{width: "87%"}}>
{MIs}
</Menu>
</Popover>
</nav>
</div>
</Aux>
);
}
};
const mapStateToProps = ({ navigation }) => {
const { dontUpdate, clientid, breadcrumbs,currentPage, selectedClient, selectedSite, selectedDash, selectedPage, navigationData, sidebarOpen, navError } = navigation;
...
}
return {
dontUpdate,
clientid,
showClientsBtn,
navData,
curClientid,
curSiteid,
curDashid,
curPageid,
parentPageid,
sidebarOpen,
navError,
breadcrumbs,
currentPage
};
};
const mapDispatchToProps = {
getNavTree,
onPageSelected,
onSwitchCSD,
onPageRoute
};
export default withRouter(connect(
mapStateToProps, mapDispatchToProps
)(sideDrawer));
Page Component
class Page extends Component {
componentWillMount () {
this.props.getCurPage();
}
render () {
let content = null;
if(this.props.location.state && this.props.location.state.currentPage)
content = (<GraphContainer pageid={this.props.location.state.currentPage} />);
return this.props.location.state && this.props.location.state.currentPage ? (
<Aux>
<p>A PAGE!</p>
{content}
</Aux>
) : (<Redirect to="/" />);
}
}
const mapStateToProps = ({pages}) => {
const { clientid, curPage } = pages;
return {
clientid, curPage
};
};
const mapDispatchToProps = {
getSelectedPage, getCurPage
};
export default connect(
mapStateToProps, mapDispatchToProps
)(Page);
Graph Container
class GraphsContainer extends Component {
componentWillReceiveProps(newProps) {
if(this.props.pageid !== newProps.pageid)
this.props.getFirstEight(newProps.pageid);
}
componentDidMount() {
if(this.props.pageid)
this.props.getFirstEight(this.props.pageid);
}
handleNextClick = (event) => {
event.preventDefault();
this.props.getNextEight(this.props.pageid, this.props.lastNum, this.props.ttlShown);
}
render() {
let graphcards = null;
let disableNext = null;
if (this.props.lastNum >= this.props.ttl)
disableNext = true;
if(this.props.graphs && this.props.graphs.length > 0) {
graphcards = ...
}
return (
<div className={classes.Shell}>
{graphcards}
{this.props.lastNum < this.props.ttl ? (
<div className={classes.NavBtns}>
<RaisedButton disabled={disableNext} onClick={this.handleNextClick}>{'V'}</RaisedButton>
</div>
):null}
</div>
);
}
}
const mapStateToProps = ({pageGraphs}) => {
const { graphs, ttl, lastNum, ttlShown } = pageGraphs;
return {
graphs, ttl, lastNum, ttlShown
};
};
const mapDispatchToProps = {
getFirstEight, getNextEight
};
export default connect(
mapStateToProps, mapDispatchToProps
)(GraphsContainer);
Actions
export const getFirstEight = (pageid) => {
let str = ...;
return (dispatch) => {
axios.get( str )
.then( response => {
let data = {};
let graphs;
let ttl;
let newLastNum = 0;
if((typeof response.data !== 'undefined') && (response.data !== null)) {
data = {...response.data};
ttl = data.total;
if(ttl <= 8) {
graphs = [...data.graphs];
newLastNum = ttl;
} else {
graphs = [...data.graphs].slice(0,8);
newLastNum = 8;
}
}
dispatch({type: GET_FIRSTEIGHT, payload: {ttl,graphs, lastNum:newLastNum}});
} )
.catch( error => {
console.log('ERROR FETCHING NAV TREE', error);
dispatch({type: GET_FIRSTEIGHT, payload: {}});
} );
};
};
export const getNextEight = (pageid, lastNum, ttlShown) => {
let str = ...;
let newLastNum = 0;
return (dispatch) => {
if(ttlShown < lastNum) {
dispatch({type: SHOW_NEXTFOUR, ttlShown: ttlShown+4});
} else {
axios.get( str )
.then( response => {
// console.log('[RESPONSE]', response);
let data = {};
let graphs;
let ttl;
if((typeof response.data !== 'undefined') && (response.data !== null)) {
data = {...response.data};
ttl = data.total;
if(ttl <= (lastNum+8)) {
graphs = [...data.graphs].slice(lastNum);
newLastNum = ttl;
} else {
graphs = [...data.graphs].filter((el,index) => {
return (index > (lastNum-1)) && (index < (lastNum+8));
});
newLastNum = lastNum+8;
}
}
dispatch({type: GET_NEXTEIGHT, payload: {ttl,graphs, lastNum:newLastNum, ttlShown: ttlShown+4}});
} )
.catch( error => {
console.log('ERROR FETCHING NAV TREE', error);
dispatch({type: GET_NEXTEIGHT, payload: {}});
} );
}
};
};

Resources