How to handle onClick event to display text after the click - reactjs

Using react typescript and I’m confused that when I click a button I want some text to appear below the button or at-least anywhere so I made a function to handle the onClick from the button and returned a h1 from the function but turns out no h1 appears on screen after button click. Any idea why?
const handleOnClick=(id:any)=>{
console.log("button clicked" + id)
return(
<h1>Clicked it</h1>
);
}
My Function is this and in another function I have
<button onClick={()=>{handleOnClick(someId)}}>a</button>
I can see the console log but the h1 doesn’t work. Any ideas?

If you think about it, what your handleOnClick doing is returning a bunch of jsx, where do you think these jsx will appear since we didn't specify any location for them? Now if you try something like this:
<button>{ handleOnClick('someId') }</button>
You will see the h1 on the screen because you specify that's where you want to render it, right inside the button element.
A classic way in js to render out something on button click is like this:
const handleOnClick=(e)=>{
// create the element
const newEle = document.createElement('h1');
newEle.innerText = 'Hello';
// append it inside the button
e.target.appendChild(newEle);
}

export default function App() {
const [nameId, setNameId] = useState<String>("");
const handleClick = (id: String) => () => {
console.log("button clicked: ", id);
setNameId(nameId ? "" : id);
};
return (
<>
<button onClick={handleClick("Rohan")}>
{nameId ? "Hide" : "Greet"}
</button>
{!!nameId && <h1>Hello {nameId} Haldiya</h1>}
</>
);
}

When the click is triggered, you need to add the <h1> element into your JSX code, and returning it from the click handler is not enough because you need to tell it where is should be added.
A good way of doing that in React is by using a state which tells you if the button was clicked or not, and if it was, then you display the <h1> element onto the screen. See the code below:
const [isActive, setIsActive] = useState(false);
const handleOnClick = (id) => {
console.log("button clicked" + id);
setIsActive(true);
};
and in your JSX code, below the button, you just need to add the second line of the following:
<button onClick={()=>{handleOnClick(someId)}}>a</button>
{isActive && <h1>Button was clicked.</h1>}
And if you want to toggle the click, So the first time you click the <h1> is showing , but if you click again it disappears, then you could simply do this in the handleOnClick function instead of the above:
const handleOnClick = (id) => {
console.log("button clicked" + id);
setIsActive((prevState) => (prevState === false ? true : false));
};
Hope this helps!

Related

React-Bootstrap Modals cause problems for window EventListener

I have built a React app which uses React-Bootstrap Modals.
In my return() function I have button which onClick changes state and shows/hides a div element.
const [showInfo, setShowInfo] = useState(false);
const toggleInfo = React.useCallback(() => {
setShowInfo(!showInfo);
}, [showInfo]);
useEffect(() => {
if (showInfo) {
document.addEventListener("click", toggleInfo);
return () => {
document.removeEventListener("click", toggleInfo);
};
}
}, [showInfo, toggleInfo]);
return (
...
<Button onClick={() => toggleInfo()}>
...
)
After loading the page, pressing the button changes the state and the div element is shown/hidden depending on the state. If I click anywhere on the window it hides the div element.
Everything works fine until I open any Modal dialog.
After that, when I click my button that shows/hides div the document.addEventListener("click", toggleInfo) and document.removeEventListener("click", toggleInfo) execute immediately one after the other and the div element does not get displayed.
If I reload the page, it works again and I made sure that this problem occurs only after opening the Modal dialog.
Any help or tips would be greatly appreciated
Fixed the issue by adding e.stopPropagation() to the toggleInfo() function:
const toggleInfo = React.useCallback(
(e) => {
e.stopPropagation();
setShowInfo(!showInfo);
},
[showInfo]
);
return (
...
<Button onClick={(e) => toggleInfo(e)}>
...
)

clickOutside hook triggers on inside select

I have a card component which consists of 2 selects and a button, select1 is always shown and select2 is invisible until you press the button changing the state. I also have an onClickOutside hook that reverts the state and hides select2 when you click outside the card.
The problem Im having is that in the case when select2 is visible, if you use any select and click on an option it registers as a click outside the card and hides select2, how can I fix this?
Heres the relevant code from my card component:
const divRef = useRef() as React.MutableRefObject<HTMLInputElement>;
const [disableSelect2, setDisableSelect2] = useState(true);
const handleActionButtonClick = () => {
setDisableSelect2(!disableSelect2)
}
useOutsideClick(divRef, () => {
if (!disableSelect2) {
setDisableSelect2(!disableSelect2);
}
});
return (
<div ref={divRef}>
<Card>
<Select1>[options]</Select1>
!disableSelect2 ?
<Select2>[options]</Select2>
: null
<div
className="d-c_r_action-button"
onClick={handleActionButtonClick}
>
</Card>
</div>
);
};
And this is my useoutsideClick hook
const useOutsideClick = (ref:React.MutableRefObject<HTMLInputElement>, callback:any) => {
const handleClick = (e:any) => {
if (ref.current && !ref.current.contains(e.target)) {
callback();
}
};
useEffect(() => {
document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
};
});
};
Extra informtaion: Im using customized antd components and cant use MaterialUI
I tried to recreate your case from the code you shared. But the version I 'built' works.
Perhaps you can make it fail by adding in other special features from your case and then raise the issue again, or perhaps you could use the working code from there to fix yours?
See the draft of your problem I made at https://codesandbox.io/s/serverless-dust-njw0f?file=/src/Component.tsx

Testing click event in React Testing Library

Here is a simple subcomponent that reveals an answer to a question when the button is clicked:
const Question = ({ question, answer }) => {
const [showAnswer, setShowAnswer] = useState(false)
return (
<>
<article>
<header>
<h2 data-testid="question">{question}</h2>
<button onClick={() => setShowAnswer(!showAnswer)}>
{
!showAnswer ? <FiPlusCircle /> : <FiMinusCircle />
}
</button>
</header>
{
showAnswer && <p data-testid="answer">{answer}</p>
}
</article>
</>
)
}
export default Question;
I am trying to test that when the button is clicked, the onClick attached is called once and the a <p> element appears on the screen:
const onClick = jest.fn()
test('clicking the button toggles an answer on/off', () => {
render(<Question />);
const button = screen.getByRole('button')
fireEvent.click(button)
expect(onClick).toHaveBeenCalledTimes(1);
expect(screen.getByTestId('answer')).toBeInTheDocument()
fireEvent.click(button)
expect(screen.getByTestId('answer')).not.toBeInTheDocument()
screen.debug()
})
RTL says that onClick is not called at all (in the UI it is, as the result is as expected)
Also, if I want to test that this button really toggles the answer element (message should come on and off) how would I test for that?
If I add another fireEvent.click() to the test (simulating the second click on the button which should trigger the answer element off), and add
expect(screen.getByTestId('answer')).not.toBeInTheDocument()
RTL will just not find that element (which is good, I guess, it means it has been really toggled off the DOM). What assertion would you use for this test to pass for that case?
Couple of issues with your approach.
First, creating an onClick mock like that won't mock your button's onClick callback. The callback is internal to the component and you don't have access to it from the test. What you could do instead is test the result of triggering the onClick event, which in this case means verifying that <FiMinusCircle /> is rendered instead of <FiPlusCircle />.
Second, p is not a valid role - RTL tells you which roles are available in the DOM if it fails to find the one you searched for. The paragraph element doesn't have an inherent accessibility role, so you're better off accessing it by its content with getByText instead.
Here's an updated version of the test:
test('clicking the button toggles an answer on/off', () => {
render(<Question question="Is RTL great?" answer="Yes, it is." />);
const button = screen.getByRole('button')
fireEvent.click(button)
// Here you'd want to test if `<FiMinusCircle />` is rendered.
expect(/* something from FiMinusCircle */).toBeInTheDocument()
expect(screen.getByText('Yes, it is.')).toBeInTheDocument()
fireEvent.click(button)
// Here you'd want to test if `<FiPlusCircle />` is rendered.
expect(/* something from FiPlusCircle */).toBeInTheDocument();
expect(screen.queryByText('Yes, it is.')).not.toBeInTheDocument()
})
In my case this worked:
it('Does click event', () => {
const { container } = render(<Component />);
fireEvent.click(container.querySelector('.your-btn-classname'));
// click evt was triggered
});

increment/decrement click component. How to save data to db.json?

I got a react functional component that can increment or decrement a number.
When the page is loaded, i want this number to be read in the db.json file from my JSON-SERVER and displayed in my view. I also want to update the number in the db.json file when i increment or decrement the value in my page.
I tried to console.log the data coming from the db.json, and what i see is that console.log is displayed 2 times in my console :
first time an empty []
second time it is good [{"clicks":20,"id":1},{"clicks":50,"id":2}]
What i tried so far was to display the clicks value with { clicked[0].clicks }
but the '.clicks' leads me to an undefined error...
What am i doing wrong ? Thanks for your help!
const CountClick = (props) => {
const componentTitle = 'Count Click Component';
const [clicked, setClicked] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'http://localhost:4000/clicked'
);
setClicked(result.data);
};
fetchData();
},[]);
console.log('clicked array', JSON.stringify(clicked));
return (
<div>
<span className="result">Counter value: { clicked.clicks }</span>
<button onClick={ () => {setClicked(clicked.clicks + 1);saveClicks(clicked.clicks + 1)} }>+1</button>
<button onClick={ () => {setClicked(clicked.clicks - 1);saveClicks(clicked.clicks - 1)} }>+1</button>
</div>
);
I except to display the "clicks" value in my view
Assuming save and load functions work correctly is a simple check for the display, you can use sth like lodash isEmpty or check length of the array if more than 1 item display count.
IsEmpty(Clicked) ? Loading : clicked[0].clicks
UseEffect works in a similar pattern to component did mount. The data is loaded after the component renders to screen so at the time your clicked value is empty and no clicks can be displayed aka undefined

onClick event not called when clicking

React onClick event not working when clicking on glyphicon.
const SortableItem = SortableElement(({value, parentId}) =>
<ListGroupItem bsStyle='success' style={{width:'300px',textAlign:'left'}}>
{value}
{parentId === null && <span className='glyphicon glyphicon-remove' style={{float:'right',width:'10px',height:'10px'}}
onClick={e => {e.preventDefault(); console.log('yes')}}/>}
</ListGroupItem>
);
I ran into something similar. My onClick events on my <a> elements were not getting triggered when a user clicked them.
This is what I was doing wrong and maybe you are doing the same mistake as what I was doing. Without more context, it's impossible to diagnose what your actual problem is.
(code is basically saying, when I click the background, stop propagation of the click)
// Example of what I was doing WRONG
const Dialog = ({ onClose, children }) => {
const $overlay = React.useRef(null)
// on mount
React.useEffect(() => {
const handleBackgroundClick = (e) => {
e.stopPropagation() // <<-- This was the line causing the issue
onClose()
})
$overlay.current?.addEventListener('click', handleBackgroundClick)
// on unmount
return () => {
$overlay.current?.removeEventListener('click', handleBackgroundClick)
}
}, [])
return (
<div className="Dialog" ref={$overlay}>{children}</div>
)
}
Basically what I'm saying is this:
Do not use event.stopPropagation() directly on DOM elements in React
The events need to be able to bubble all the way to the top level or in-built React event's will stop working.
I ended up getting around the issue by adding an extra div inside the dialog to act as the overlay.
// How I fixed the issue
const Dialog = ({ onClose, children }) => {
return (
<div className="Dialog">
<div className="Dialog__overlay" onClick={()=> onClose()}></div>
<div className="Dialog__content">{children}</div>
</div>
)
}
Note: These are simplified examples to demonstrate a point. The exact code used was more complex. Using this code as displayed here would cause accessibility issues on your website.

Resources