Customizing react-day-picker navbarElement - javascript

I'm using react-days-picker for accessibility reasons.
I would like to replace the current span tags inside the navbar with button tags.
I could extend it with the navbarElement prop but then my custom component keeps re-rendering each time I click or press a key to change months. It would lose focus.
To change a month, I could only press the key down key once or I had to .press the tab key each time to get the focus again
I'm even following the code included in the documentation (https://react-day-picker.js.org/examples/elements-navbar) but the result is the same:
function Navbar({
nextMonth,
previousMonth,
onPreviousClick,
onNextClick,
className,
localeUtils,
}) {
const months = localeUtils.getMonths();
const prev = months[previousMonth.getMonth()];
const next = months[nextMonth.getMonth()];
const styleLeft = {
float: 'left',
};
const styleRight = {
float: 'right',
};
return (
<div className={className}>
<button style={styleLeft} onClick={() => onPreviousClick()}>
← {prev.slice(0, 3)}
</button>
<button style={styleRight} onClick={() => onNextClick()}>
{next.slice(0, 3)} →
</button>
</div>
);
}
export default function Example() {
return (
<div>
<DayPicker weekdayElement={<Weekday />} navbarElement={<Navbar />} />
</div>
);
}

Related

onClick not working on first click in react nested component

I have a parent component. This parent component combines a lot of if else control and a lot of mini jsx. I added my case. If I click the NextBtnText in the Modal component. It doesn't fire the first click. It needs a second click.
How can I fire the first click? What is wrong?
const StepperComponent = ({closeModal}) => {
/**
* there is some ,useState, useEffect and conditional functions
*/
const test = () => setActiveStep((prevActiveStep) => prevActiveStep + 1);
const NextBtnText = () => {
return (<Button
onClick={test}
disabled={firmType}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.next")}</span>
<KeyboardArrowRightIcon />
</Button>
);
};
const BackBtnText = () => {
return (
<>
<span>{t("createFirm.buttons.back")}</span>
</>
);
}
const RequestAssignmentBtnText = () => {
return (
<Button
onClick={handleSubmit}
disabled={firmType}
className={styles.StepperRequestButton}
>
<span>{t("createFirm.buttons.requestAssignment")}</span>
</Button>
)
}
return (
<div className={styles.StepperContainer}>
<Stepper activeStep={activeStep} className={styles.Steps}>
{steps.map((label, index) => {
return (
<Step key={index}>
<StepLabel >{label}<span className={styles.StepCountMobile}>{`(${index + 1} / ${steps.length})`}</span></StepLabel>
</Step>
);
})}
</Stepper>
{getStepContent(activeStep)}
<div className={styles.StepperButtons}>
<Button
disabled={activeStep === 0}
onClick={handleBack}
className={styles.StepperBackButton}
>
<span>{t("createFirm.buttons.back")}</span>
</Button>
{activeStep === steps.length - 1 ? (<RequestAssignmentBtnText />) : (<NextBtnText />)}
</div>
</div>
);
}
Yo're not calling your function. Simply do:
<Button
onClick={(event) => handleSubmit(event)}
>
// Your function has to look like this
const handleSubmit = (event) => {
// your code...
}
Or you can do this:
<Button
onClick={handleSubmit()}
>
// Your function has to look like this
const handleSubmit = () => {
return (event) => {
// your code...
}
}
What is the default value you set in useState() for activeStep?
I believe the onClick handler is working but the state is not what you expect on the first click. Maybe set the default value to 1 or 0 (I am not sure what is suitable for your use case).
const [activeStep, setActiveStep] = useState(1);
If the onClick is actually not working with the first click, try using plain HTML <input /> with test onClick handler and see if that works. It might have something to do with the Button component itself.
I fixed the onClick problem. The cause is about rerendered after disabled={firmType}. My button has a disabled attribute. I need to control after the checkbox is true/false.
Before :
{activeStep === steps.length - 1 ? (<RequestAssignmentBtnText />) : (<NextBtnText />)}
Solution :
{activeStep === steps.length - 1
?
<Button
onClick={handleSubmit}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.requestAssignment")}</span>
</Button>
:
<Button
onClick={handleNext}
disabled={firmType}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.next")}</span>
<KeyboardArrowRightIcon />
</Button>
}
Actually, I want to know what is different between Before and Solution.
Maybe someone can explain the issue of solution.

Hide multiple divs with useState hooks React

I am trying to hide multiple divs with useSate.
They will be rendered random on the page, not from a list.
I have managed to do so by setting up different variables but couldn't find a more generic solution:
https://stackblitz.com/edit/react-t3shrc?file=src%2FApp.js
Also is there a way to close them when clicking outside?
Can you help please.
export default function App() {
const [isVisible, setIsVisible] = useState(false);
const [isVisible2, setIsVisible2] = useState(false);
const showInfo = (e, setIsVisible) => {
e.preventDefault();
setIsVisible(true);
};
const hideInfo = (e, setIsVisible) => {
e.preventDefault();
setIsVisible(false);
};
return (
<div>
<button
onClick={(e) => {
showInfo(e, setIsVisible);
}}
>
Show info 1
</button>
{isVisible && (
<div className="info">
Info 1
<button
onClick={(e) => {
hideInfo(e, setIsVisible);
}}
>
Close
</button>
</div>
)}
<br></br>
<br></br>
<button
onClick={(e) => {
showInfo(e, setIsVisible2);
}}
>
Show info 2
</button>
{isVisible2 && (
<div className="info">
Info 2
<button
onClick={(e) => {
hideInfo(e, setIsVisible2);
}}
>
Close
</button>
</div>
)}
</div>
);
}
I'm not 100% sure what you mean by a more 'generic' solution. Here is what comes to my mind:
First of all, we create a more complex object to basically hold all the variables / sections we encounter and use this as our state.
const initialVisibleAreas = {
area1: true,
area2: false
};
const [visibleAreas, setVisibleAreas] = useState(initialVisibleAreas);
Please note that this is propabably something you want to generate from your data using Object.keys(...) or mapping an array.
Next up, we create the functions for the buttons to use this new state accordingly:
// shows the element by given key
const showInfo = (event, key) => {
event.preventDefault();
setVisibleAreas({ ...visibleAreas, ...{ [key]: true } });
};
// hides the element by given key
const hideInfo = (event, key) => {
event.preventDefault();
setVisibleAreas({ ...visibleAreas, ...{ [key]: false } });
};
// sets every key to false to hide them all at once
const hideAllInfo = (event) => {
event.preventDefault();
const allFalse = Object.assign(
...Object.keys(visibleAreas).map((key) => ({ [key]: false }))
);
setVisibleAreas(allFalse);
};
Last but not least, we use them in jsx. This is basically one 'section':
<button
onClick={(e) => {
showInfo(e, 'area2');
}}
>
Show info 2
</button>
{
visibleAreas['area2'] && (
<div className="info">
Info 2
<button
onClick={(e) => {
hideInfo(e, 'area2');
}}
>
Close
</button>
</div>
);
}
To answer the last question; nothing is holding you to call 'hideAllInfo' inside a onClick handler of your surounding div. Sample is included in the modified stackblitz.
Have a complete look at the modified stackblitz
EDIT: In the case you want to close all areas by clicking the surrounding div, make sure to not propagate the button click event with:
event.stopPropagation();
I updated the stackblitz once again.

MATERIAL-UI React - Popper of another Popper

I'm working on a calendar app.
The problem: clicking popper of a popper closes both poppers, because it fires the click outside event of the first popper which closes it.
I have a component <Event /> which uses Material-UI React <Popper /> and it works fine with it. combining it with the <AwayClickListener /> it closes when clicking out side, and stay open when clicking inside the popper. I created <Events /> which is a list of <Event />.
when click the + more text, popper with all the events in that day should appear, on top of the cell.
the popper children is also <Events />:
clicking an event should open a popper with the event details, as clicking it in the cell was.
since i use the same component <Events /> it does that, but not fully as expected:
clicking the event details popper closes both poppers.
That is the issue: the requirement is that clicking out side of the poppers will close the poppers, but clicking inside will leave them open and interactive
debugging shows that clicking the second popper, fires the outside clicked event of the first popper which closes it. also, taking out the click away listener function from the first popper leave the second popper open for most of the clicks - clicking some places in it, fires it's clicked away function which closes it. e.g: clicking the title closes it, clicking the location or summary divs does not.
I tried wrapping the entire cell with <ClickAwayListener />.
I tried wrapping the children of the popper with <ClickAwayListener />
Tried using the material-ui-popup-state npm, and gave the popper id attribute. than when click away, compare the target id to 'popper', and if equal stay open. but, the id that was extracted from the event object of onClickAway event was empty string. even when clicking the popper.
CODE
<Popper> - costume wrapper for the material ui popper
const popper = ({
placement,
open,
anchorEl,
handleClickAway=null,
title,
handleCloseClick=null,
children,
popperStyle = {},
calendarPopoverClass = ''
}) => {
const useStyles = makeStyles({
Popper: popperStyle
})
const styles = useStyles();
return (
<Popper modifiers={{
flip: {
enabled: false,
},
preventOverflow: {
enabled: false,
boundariesElement: 'scrollParent',
}
}}
className={styles.Popper}
placement={placement}
open={open}
anchorEl={anchorEl}
>
<ClickAwayListener onClickAway={handleClickAway}>
<CalendarPopover className={st(classes[calendarPopoverClass])} isShown withArrow={false} title={title} onClose={handleCloseClick}>
{children}
</CalendarPopover>
</ClickAwayListener>
</Popper>
)
}
<Event />
const event = ({ PROPS }) => {
const [expanded, setExpanded] = React.useState(null);
const closeExpanded = () => setExpanded(null)
return (
<>
<div
className={st(classes.Event, { isTimeShown, isNextWeekFirstFiller, isLastFiller, isMultiDay, isAllDay, isFiller })}
style={inlineStyle}
onClick={onEventClick}
>
<div className={classes.Time}>{timeToDisplay}</div>
<div className={classes.Title}>{title}</div>
</div>
<Popper
placement={popperPlacement}
title={title}
handleCloseClick={closeExpanded}
handleClickAway={closeExpanded}
open={Boolean(expanded)}
anchorEl={expanded}
popperStyle={popperStyle}
calendarPopoverClass='Event'
>
<ExpandedEvent
startDate={startDate}
endDate={endDate}
location={location}
summary={summary}
/>
</Popper>
</>
);
}
<Events />
const Events = ({ events, isTimeShown, localeToggle, popperPlacement, popperStyle, handleShowMoreClick=null }) => {
const eventsToShow: JSX.Element[] = [];
if (events.length > 0) {
let eventsToShowAmount = 3;
const moreEventsCount = events.length - eventsToShowAmount;
eventsToShowAmount = moreEventsCount > 0 ? eventsToShowAmount : events.length;
for (let i = 0; i < eventsToShowAmount; i++) {
eventsToShow.push(
<Event
key={events[i].id}
{...events[i]}
isTimeShown={isTimeShown}
popperPlacement={popperPlacement}
popperStyle={popperStyle}
/>
)
}
if (moreEventsCount > 0) {
eventsToShow.push(<ShowMore key='ShowMore' handleClick={handleShowMoreClick} moreEventsCount={moreEventsCount} />)
}
}
return (
<div className={classes.Events}>
{eventsToShow}
</div>
);
}
<MonthlyCell />
const MonthlyCell = ({
events,
isTimeShown,
popperPlacement,
popperStyle
}) => {
const [expandedEvents, setExpandedEvents] = React.useState(null);
const cell = React.useRef<HTMLDivElement>(null)
const eventsList = (handleShowMoreClick = null) => (
<Events
events={events}
isTimeShown={isTimeShown}
localeToggle={true}
popperPlacement={popperPlacement}
popperStyle={popperStyle}
handleShowMoreClick={handleShowMoreClick}
/>
);
const handleShowMoreClick = () => setExpandedEvents(eventsList());
const closeExpandedEvents = () => {
setExpandedEvents(null);
}
return (
<>
<div ref={cell} className={classes.MonthlyCell} >
{eventsList(handleShowMoreClick)}
</div>
<Popper
placement='left'
open={Boolean(expandedEvents)}
title='hello'
handleClickAway={closeExpandedEvents}
anchorEl={cell.current}
popperStyle={{ left: '17% !important' }}
handleCloseClick={closeExpandedEvents}
>
{eventsList()}
</Popper>
</>
);
}
hope it was clear enough. let me know if anything else is needed.
Thank you
EDIT 1
another attempt was giving the parent popper bigger z-index, but it didn't work
the solution was surrounding the popper children in a div.
component i used caused this un-wanted behaviour, because it didn't had forwardRef support. so adding div wrapper solved that.
also, dropping the modifiers attribute:
<Popper
// modifiers={{
// flip: {
// enabled: false,
// },
// preventOverflow: {
// enabled: false,
// boundariesElement: 'scrollParent',
// }
// }}
link for working solution: https://codesandbox.io/s/popper-in-a-popper-s6dfr?file=/src/Popper/Popper.js:372-519

React returns older state value onClick

I am adding a component onclick and keeping track of the components using useState Array. However when I go to remove one of the added components, it doesn't recognize the full component Array size, only the state that was there when that component was initially added.
Is there a way to have the current state recognized within that delete function?
https://codesandbox.io/s/twilight-water-jxnup
import React, { useState } from "react";
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => deleteSpan(props.index)}>DELETE</button>
Length: {spans.length}
</div>
);
};
//set initial span w/ useState
const [spans, setSpans] = useState([<Span key={0} index={Math.random()} />]);
//add new span
const addSpan = () => {
let key = Math.random();
setSpans([...spans, <Span key={key} index={key} />]);
};
//delete span
const deleteSpan = index => {
console.log(spans);
console.log(spans.length);
};
//clear all spans
const clearInputs = () => {
setSpans([]);
};
return (
<>
{spans}
<button onClick={() => addSpan()}>add</button>
<button onClick={() => clearInputs()}>clear</button>
</>
);
}
UPDATE - Explaining why you are facing the issue descibed on your question
When you are adding your new span on your state, it's like it captures an image of the current values around it, including the value of spans. That is why logging spans on click returns you a different value. It's the value spans had when you added your <Span /> into your state.
This is one of the benefits of Closures. Every <Span /> you added, created a different closure, referencing a different version of the spans variable.
Is there a reason why you are pushing a Component into your state? I would suggest you to keep your state plain and clean. In that way, it's also reusable.
You can, for instance, use useState to create an empty array, where you will push data related to your spans. For the sake of the example, I will just push a timestamp, but for you might be something else.
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => setSpans(spans.filter(span => span !== props.span))}>DELETE</button>
Length: {spans.length}
</div>
);
};
const [spans, setSpans] = React.useState([]);
return (
<>
{spans.length
? spans.map((span, index) => (
<Span key={span} index={index} span={span} />
))
: null}
<button onClick={() => setSpans([
...spans,
new Date().getTime(),
])}>add</button>
<button onClick={() => setSpans([])}>clear</button>
</>
);
}
I hope this helps you find your way.

Is there a way to trigger an event when mapping out a Component so that it only applies a style to that specific component?

I'm building a quiz app and I have some components mapped out that when the user clicks an answer it highlights only the one they clicked but so far I've only gotten to work either on hover or just apply it to everybody.
export default function Quiz() {
const [currentQuestion, setCurrentQuestion] = useState(4);
const [currentAnswer, setCurrentAnswer] = useState("");
const handleClick = e => {
setCurrentAnswer(e.target.value);
};
const question = quiz.questions[currentQuestion];
return (
<div className="container">
<Progress total="3" current="1" />
<Question question={question.name} />
<Answers
question={question}
currentAnswer={currentAnswer}
handleClick={handleClick}
/>
<button className="btn btn-primary">Confirm and Continue</button>
</div>
);
}
function Answer(props) {
let classes = ["answer"];
if (props.selected) {
classes.push("selected");
}
console.log(props.selected);
console.log(classes.join(" "));
return (
<button
value={props.letter}
className={classes.join(" ")}
onClick={props.handleClick}
>
<span className="letter">{props.letter}</span>
{props.answer}
</button>
);
}
function Answers(props) {
return (
<>
{props.question.options.map((e, index) => {
const answer = index + 1;
return (
<>
<Answer
letter={answer}
answer={e.name}
selected={answer}
handleClick={props.handleClick}
/>
</>
);
})}
</>
);
}
This is how my project is setup so far and have been stuck with this problem for a while now
Just do an equality check!
Something along the lines of
selected={ props.currentAnswer === answer }
You just need to track whichever answer is selected and set the logic so that your Answer component knows it is selected. You are already doing the check of adding selected classname if selected is true.
EDIT: SideNote: Don't forget to add key props to your mapped elements! :D Read the documentation for the why.

Categories

Resources