onClick on <li> does't trigger - reactjs

I'm having a headache trying to understand why my onClick on a <li> doesn't trigger
I'm using react
I'm posting here the snippet of code:
This is the list creation:
<div className="countryList">
<ul>
{_adminCodes.map((item) => (
<li
style={{ cursor: "pointer" }}
onClick={handleListCountryClick}
className="countryLi">{item.name}
</li>
))}
</ul>
</div>
And this is the function handleListCountryClick:
const handleListCountryClick = () => {console.log("I'm a country!")}
Any advice would be appreciated!
EDIT:
I've tryied:
onClick={handleListCountryClick()}
onClick={() => handleListCountryClick()}
EDIT 2: This component (map) is included in a father component (graphs), if I try to trigger the onClick in the map component it runs the function, instead if I try to trigger the onClick in the father component (graphs) it doesn't work
Here's a stackblitz demo

Try calling the function with (), by using handleListCountryClick your are referencing the function instead of executing it.
onClick={() => {handleListCountryClick()}}
Dang ok are you using a class component then try:
onClick={() => {this.handleListCountryClick()}}
Sorry I tried I would need to see more code to help further.
Test this:
onClick={() => {console.log('I worked!!')}}
if that works then there is a reference error to the function.

You can try by updating your code to "onClick={() => handleListCountryClick}"
Edit:
This is what I have tried in codeSandbox and getting below results:
class Country extends React.Component {
render() {
const adminCodes = ["India", "USA", "UK", "UAE"];
return (
<div>
<ul>
{adminCodes.map((item) => (
<li
key={item}
style={{ cursor: "pointer" }}
onClick={() => handleListCountryClick(item)}
>
{item}
</li>
))}
</ul>
</div>
);
}
}
const handleListCountryClick = (item) => {
console.log(`I'm a ${item}!`);
};
export default Country;
Results (on click of listitems in same order):
I'm a India!
I'm a USA!
I'm a UK!
I'm a UAE!

usually the onClick is written onClick={() => onClickFunction()} SO the fact you tried these solutions makes me think there's a problem somewhere else. Can you please edit the question and write more of the component code so we can understand better? Thank You.

Related

REACT component can work with onClink event

I'm new in REACT, I'm trying to make a slider, so I need this <img> to act like a button, I tried to use an onClick event to test the functionality by sending a simple console.log, but it does nothing at all!, I really can´t find the problem, it sends no error, it just didn't work.
I have another <img> with an onClick event that works just fine, in another component, so I can't tell what's the problem here.
Also, I thought maybe was my console.log so I call an Alert, but no, that didn't work either. Could anyone help me?
The code looks something like this:
const SliderInstructors = () => {
const siguiente = () => {
console.log('Siguiente');
alert('You clicked me!');
};
const anterior = () => {
console.log('Anterior');
alert('You clicked me!');
};
return(
<> {/* Controles */}
<div className="controls">
<img onClick={() => anterior} src={ArrowLeft} style={{'pointer-events': "all"}} alt="Before"/>
<img onClick={() => siguiente} src={ArrowRight} style={{'pointer-events': "all"}} alt="After"/>
</div>
</>
)
}
export default SliderInstructors;
This is a component which I send to call to a Page, like this
import SliderInstructors from '../components/SliderInstructors';
function AboutUs() {
const { t} = useTranslation();
return (
<><div className="instructorsAbout">
<h2>{t('about.instructor')}</h2>
<div className="infoInstructors">
<SliderInstructors/>
</div>
</div>
</>
)
}
export default AboutUs;
I thought maybe was my styles but I deleted the styles from them component and nothing, also I tried to put the component directly in the App.js but nop, so I really don't know where to keep looking at.
<img onClick={() => anterior()} src={ArrowLeft} style={{'pointer-events': "all"}} alt="Before"/>
<img onClick={() => siguiente()} src={ArrowRight} style={{'pointer-events': "all"}} alt="After"/>
or
<img onClick={anterior} src={ArrowLeft} style={{'pointer-events': "all"}} alt="Before"/>
<img onClick={ siguiente} src={ArrowRight} style={{'pointer-events': "all"}} alt="After"/>
Try calling the anterior and siguiente functions without anonymous functions on onClick.
<img onClick={anterior} src={ArrowLeft} style={{'pointer-events': "all"}} alt="Before"/>
<img onClick={siguiente} src={ArrowRight} style={{'pointer-events': "all"}} alt="After"/>

React Rendering Different components based on hook

I have a functional component that is supposed to switch between the login view, and the Register view based on a hook state. React keeps passing the error: "Expected onClick listener to be a function, instead got a value of boolean type." even though I have worked on other components before the same way. Any help would be appreciated
const UserPane = () => {
const [newUser, setNewUser] = useState(false)
const toggleNewUser = () => setNewUser(!newUser)
return (
<>
<div className="container p-5 pb-2 mb-3">
{newUser ?
<Register /> :
<Login />
}
{newUser.toString()}
<a onClick={() => setNewUser(!newUser)} style={{ color: "#845ec2", cursor: "pointer" }} onClick>
Don't have an account? Click here to register
</a>
</div>
</>
)
}
You are seeing error because of the last onClick (scroll to end of the line)
<a onClick={() => setNewUser(!newUser)} style={{ color: "#845ec2", cursor: "pointer" }} onClick> //<---This
This last onClick essentially overwrites the previous onClick with onClick={true} which is not an acceptable onClick value
I hope, you are facing an issue with toggling the newUser.
To alter the state, you should use
setNewUser(prev => !prev)

Unexpected Behavior After State Change in React Component

RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
return(
<div className="results_wrapper">
{selected.map((r,i)=>{
let openState = (this.state.selectedImage==i)?true:false;
return(
<RenderPanel panelType={PanelType.large} openState={openState} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
this.setState({selectedImage:2})
console.log('wtfff'+this.state.selectedImage)
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})}
</div>
)
}
When I change the state of 'selectedImage', I expect the variable 'openState' to render differently within my map() function. But it does not do anything.
Console.log shows that the state did successfully change.
And what is even stranger, is if I run "this.setState({selectedImage:2})" within componentsDidMount(), then everything renders exactly as expected.
Why is this not responding to my state change?
Update
I have tried setting openState in my component state variable, but this does not help either:
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
let html = selected.map((r,i)=>{
return(
<RenderPanel key={i} panelType={PanelType.large} openState={this.state.openState[i]} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
let openState = this.state.openState.map(()=>false)
let index = i+1
openState[index] = true;
this.setState({openState:openState},()=>console.log(this.state.openState[i+1]))
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})
return(
<div className="results_wrapper">
{html}
</div>
)
}
https://codesandbox.io/s/ecstatic-bas-1v3p9?file=/src/Search.tsx
To test, just hit enter at the search box. Then click on 1 of 3 of the results. When you click 'Next', it should close the pane, and open the next one. That is what I'm trying to accomplish here.
#Spitz was on the right path with his answer, though didn't follow through to the full solution.
The issue you are having is that the panel's useBoolean doesn't update it's state based on the openState value passed down.
If you add the following code to panel.tsx, then everything will work as you described:
React.useEffect(()=>{
if(openState){
openPanel()
}else{
dismissPanel();
}
},[openState, openPanel,dismissPanel])
What this is doing is setting up an effect to synchronize the isOpen state in the RenderPanel with the openState that's passed as a prop to the RenderPanel. That way while the panel controls itself for the most part, if the parent changes the openState, it'll update.
Working sandbox
I believe it's because you set openState in your map function, after it has already run. I understand you think the function should rerender and then the loop will run once more, but I think you'll need to set openState in a function outside of render.
The problem is that even though you can access this.state from the component, which is a member of a class component, there's nothing that would make the component re-render. Making components inside other components is an anti-pattern and produces unexpected effects - as you've seen.
The solution here is to either move RenderImages into a separate component altogether and pass required data via props or context, or turn it into a normal function and call it as a function in the parent component's render().
The latter would mean instead of <RenderImages/>, you'd do this.RenderImages(). And also since it's not a component anymore but just a function that returns JSX, I'd probably rename it to renderImages.
I tire to look at it again and again, but couldn't wrap my head around why it wasn't working with any clean approach.
That being said, I was able to make it work with a "hack", that is to explicitly call openIt method for selectedImage after rendering is completed.
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter((x) =>
this.state.selectedGroups.includes(x.domain)
);
return (
<div className="results_wrapper">
{selected.map((r, i) => {
let openState = this.state.selectedImage === i ? true : false;
return (
<RenderPanel
key={i}
panelType={PanelType.medium}
openState={openState}
title={r.domain + ".TheCommonVein.net"}
preview={(openIt) => {
/* This is where I am making explicit call */
if (openState) {
setTimeout(() => openIt());
}
/* changes end */
return (
<div
className="result"
onClick={openIt}
style={{ boxShadow: theme.effects.elevation8 }}
>
<img src={r.url} />
</div>
);
}}
content={(closeIt) => (
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain, r.parent)}
<div
onClick={() => {
closeIt();
this.setState({
selectedImage: i + 1
});
}}
>
[Next>>]
</div>
<img src={r.url} />
</div>
)}
/>
);
})}
</div>
);
};
take a look at this codesandbox.

React State Hook - toggle a class

I'm trying to build a sidebar navigation menu and thought I'd take advantage of the new State hook in React. I've read the docs but can't seem to find an example similar to what I need, which is quite simply to toggle a CSS class on click which will in turn open and close my menu.
Here's what I've tried:
const SidebarMenuItem = ({ component }) => {
const [ menuActive, setMenuState ] = useState(false);
return (
<li className="p-sidebar-menu-item">
menuActive:
{ menuActive }
<button className="p-sidebar-menu-item__link" onClick={() => setMenuState(!menuActive)}>{ component.component }</button>
{ component.children && (
<ul className="p-sidebar-menu">
<li><a href={`/${component.slug}`}>Overview</a></li>
{ component.children.map((subPage, key) => (
<li key={ key }>
<a href={`/${subPage.slug}`}>{ subPage.name }</a>
</li>
))}
</ul>
)}
</li>
)
}
export default SidebarMenuItem;
Any ideas where I'm going wrong?
Thanks
Just make the className dynamic, so instead of setting
<li className="p-sidebar-menu-item">
transform it in a template literal
<li className={`p-sidebar-menu-item`}>
and then add your class conditionally (the "yellow" class in my example)
<li className={`p-sidebar-menu-item ${menuActive ? "yellow" : ""}`}>
Take a look at this CodeSandbox: here I've just added your component and changed the way the className attribute is generated.
If you want to avoid the ternary operator you could use the classnames module and then update your code to
import c from "classnames";
...
...
...
<li className={c("p-sidebar-menu-item", {yellow: menuActive})}>
Another clean solution can be to generate the className string in advance, for example
let classes = "p-sidebar-menu-item";
if(menuActive) {
classes += " yellow";
}
<li className={classes}>
Let me know if you need some more help 😉
I think you just need
const [ menuActive, setMenuState ] = useState(false);
change the name of setState to setMenuState in your code also
Don't forget to use the prevState or you can have a bug.
<button
className="p-sidebar-menu-item__link"
onClick={() => setMenuState((prevMenuActive) => !prevMenuActive)}>
{component.component}
</button>

Reactjs: Replying comments not working in reactjs

Replying comments not working in reactjs.
Believe I have search through this venerable forum but most solution found could not resolve my issue.
This code works fine by displaying post and its corresponding comments from an array. Great Thanks to Stackoverflow Engineer Ryan for his help. Now I want to display each reply made on each comments as per code below
{props.comment.replys.map((reply, i) => (<div>{reply.reply}</div>))}
but it shows error
Cannot read property 'replys' of undefined
at Post
In angularjs I can just implement code below inside Post ng-repeat function.
<div class="post" ng-repeat='post in posts'>
<div>
{{ post.content }}
</div>
<!-- Comments -->
<div ng-repeat='comment in post.comments'>
<div class='comment'>{{ comment.comment }} <b>{{ comment.id }}</b></div>
<div ng-repeat='reply in comment.replys'>
<div>{{ reply.reply }}</div></div>
</div>
</div>
Here is the updated code in reactjs.
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
const Post = (props) => {
console.log(props.post);
console.log(props.comments);
return (<React.Fragment><li >
{props.post.id} - {props.post.content}
</li>
<div>
{props.post.comments.map((comment, i) => (<div>{comment.comment} --- {comment.id}</div>))}
{props.comment.replys && props.comment.replys.map((reply, i) => (<div>{reply.reply}</div>))}
</div>
</React.Fragment>
);
};
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = {
rec: [
{"id":"1","content":"first post","comments":[{"comid":"1","comment":"first comment","replys":[{"reply":"first comment reply1"},{"reply":"first comment second reply"}] }]},
{"id":"2","content":"second post","comments":[{"comid":"2","comment":"second comment", "replys":[{"reply":"second comment reply1"}] }]}
],
};
}
render() {
return (
<div>
<h3>Records</h3>
<ul>
{this.state.rec.map((post, i) => (
<Post post={post} key={i}/>
))}
</ul>
</div>
);
}
}
const Post = (props) => {
return (<React.Fragment>
<li>
{props.post.id} - {props.post.content}
</li>
<div>
{props.post.comments.map((comment, i) => (
<div key={"comment_" + comment.id}>
<div>{comment.comment} --- {comment.id}</div>
{comment.replys && comment.replys.map((reply, i) => (<div>{reply.reply}</div>))}// Here
</div>
))}
</div>
</React.Fragment>
);
};
You can take replays data in this way.
One comment on your second post in the sample data has no replies (replys) so it's quite expected that you see that error: you call map on undefined.
Try this instead (check if we have replies first and only map if some exist):
{props.comment.replys && props.comment.replys.map((reply, i) => (<div>{reply.reply}</div>))}
Edit
Here's one implementation of the Post method (I renamed it to renderPosts, you should do the same) that should work (better):
const renderPosts = (props) => {
return (
<>
<li>
{props.post.id} - {props.post.content}
</li>
<div>
{props.post.comments.map((comment) => {
return (
<div key={comment.comid}>
<div>{comment.comment} --- {comment.id}</div>
{comment.replys && comment.replys.map((reply) => <div key={reply.reply}>{reply.reply}</div>)}
</div>
)
})}
</div>
</>
);
};
Note that I've:
- replaced the outer element with the shorter notation for fragments
- moved the iteration of the replies inside of the iteration of the comments (each comment has a list of replies)
- added the mandatory key attribute (you should add an id to the replies on your datamodel, I used the reply.reply value for the moment but that's in general a bad id, because there's no guarantee that all replies are different)
Hope it helps!

Resources