React Conditional Rendering with states - javascript

I am working in a component where if i click on the NavItem i render an other list of elements
Function changing the state
handleClick() {
this.setState({
isActive: !this.state.isActive
});
};
The Conditional Rendering
if (isActive) {
SubList = <List hasIcons style="secondary"><ListItem><NavItem href={desktopUrl} title={title}><Icon name={name} />{title}</NavItem></ListItem></List>
}
The List NavItem and the {{SubList}}
<ListItem>
<NavItem isActive href={desktopUrl} title={title} onClick={this.handleClick}>
<Icon name={name} />
{title}
</NavItem>
{SubList}
</ListItem>
Here the whole component
export default class SportNavItem extends React.Component {
constructor() {
super();
this.state = { isActive: false };
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
isActive: !this.state.isActive
});
}
render() {
const { title, desktopUrl, isActive, name } = this.props.data;
// props.childItems; { title, name, isActive, url }
// const itemId = `nav-${slugEn}`;
const style = isActive ? "primary" : "default";
let SubList = null;
if (isActive) {
SubList = (
<List hasIcons style="secondary">
<ListItem>
<NavItem isActive href={desktopUrl} title={title}>
<Icon name={name} />
{title}
</NavItem>
</ListItem>
</List>
);
}
return (
<List hasIcons style={style}>
<ListItem>
<NavItem isActive href={desktopUrl} title={title} onClick={this.handleClick}>
<Icon name={name} />
{title}
</NavItem>
{SubList}
</ListItem>
</List>
);
}
}
The Component exported
const sampleData = {
title: 'Football',
name: 'football',
isActive: true,
desktopUrl: '#'
}
ReactDOM.render(
<div>
<SportNavItem data = { sampleData }/>
</div>,
document.getElementById('app')
);
If i manually change the status isActive to false i can render the SubList. I can not achieve to handle the status onClick and i do not see error in the console. What is possibly wrong? Is there a better way?

You are trying to read isActive from this.props.data:
const { title, desktopUrl, isActive, name } = this.props.data;
...but isActive is in this.state. Change to this instead:
const { title, desktopUrl, name } = this.props.data;
const { isActive } = this.state;

Firstly as #Jacob suggested, your variable isActive is a state and not a prop. You should use it like
const { title, desktopUrl, name } = this.props.data;
const {isActive} = this.state;
Secondly, If NavItem is a custom component created by you then onClick={this.handleClick} will not set an onClick event on the NavItem rather than pass onClick as a prop to the NavItem component. In the NavItem component you can have an onClick event on a div that will in turn call the onClick function from the props resulting in this.handleClick being called. If you look at the NavItem component in ReactBootstrap. It has an onClick prop which will basically be doing the same thing as I suggested above
So technically you cannot have an onClick event on to any component. You can wrap the NavItem inside a div and then set an onClick event on that div
<ListItem>
<div onClick={this.handleClick}>
<NavItem isActive href={desktopUrl} title={title} >
<Icon name={name} />
{title}
</NavItem>
{SubList}
</div>
</ListItem>

You cannot onClick on custom component. Wrap NavItem with a div that has the onClick method.

Related

Where to declare the handler function in child to get the clicked item details

I want the category details in the parent categoryHandler function
from the child component. I don't know where to place this
props.categoryHandler function in the child component so that when it
is clicked I should get the details to the parent categoryHandler
function.
PARENT COMPONENT:
const categoryHandler = (catg) => {
console.log(catg);
}
<div className="categoryBox">
<Category categories={categories} categoryHandler={() => categoryHandler} />
</div>
CHILD COMPONENT:
export default function Category({ categories }) {
if (categories.length) {
const menu = recursiveMenu(categories);
return menu.map((item, key) => <MenuItem key={key} item={item} />);
} else {
return <div>No Menus</div>
}
}
const MenuItem = ({ item }) => {
const Component = hasChildren(item) ? MultiLevel : SingleLevel;
return <Component item={item} />;
};
const SingleLevel = ({ item }) => {
return (
<ListItem button>
<ListItemText className="category-link child" primary={item.title} />
</ListItem>
);
};
const MultiLevel = ({ item }) => {
const { items: children } = item;
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((prev) => !prev);
};
return (
<React.Fragment>
<ListItem button onClick={handleClick}>
<ListItemText className="category-link parent" primary={item.title} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{children.map((child, key) => (
<MenuItem key={key} item={child} />
))}
</List>
</Collapse>
</React.Fragment>
);
};
Your approach in the code is right, just you have to modify two thing to achieve what you are expecting.
In the parent component you have to modify passing the function as a props like this:
categoryHandler={categoryHandler}
In the child component you have to catch the function while destructuring the props and call in on the both list item with the single item as function parameter:
add the function in props destructuring and pass the function as another props to MenuItem
export default function Category({ categories, categoryHandler }) {
if (categories.length) {
const menu = recursiveMenu(categories);
return menu.map((item, key) => <MenuItem categoryHandler={categoryHandler} key={key} item={item} />);
} else {
return <div>No Menus</div>
}
}
Now again pass the function props to Single And MultiLevel List and call the function on both place:
const MenuItem = ({ item, categoryHandler }) => {
const Component = hasChildren(item) ? MultiLevel : SingleLevel;
return <Component item={item} categoryHandler={categoryHandler} />;
};
const SingleLevel = ({ item, categoryHandler }) => {
return (
<ListItem button onClick={handleClick}>
<ListItemText className="category-link child" primary={item.title} />
</ListItem>
);
};
const MultiLevel = ({ item, categoryHandler}) => {
const { items: children } = item;
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((prev) => !prev);
};
return (
<React.Fragment>
<ListItem button onClick={handleClick}>
<ListItemText className="category-link parent" primary={item.title} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{children.map((child, key) => (
<MenuItem key={key} item={child} />
))}
</List>
</Collapse>
</React.Fragment>
);
};
This solution should work fine!
When you call your function passed as a prop, you can pass data from a child to parent component.
Parent Component:
const categoryHandler = (catg) => {
console.log(catg);
}
<div className="categoryBox">
<Category categories={categories} categoryHandler={categoryHandler} />
</div>
Child Component:
export default function Category(props) {
props.categoryHandler(data);
}
You can need to pass the parameter to the function in your parent component like this:
<Category categories={categories} categoryHandler={categoryHandler} />
You need to pass the prop categoryHandler all the way to the SingleLevel like this:
categoryHandler={categoryHandler}
You can need to add onClick to the ListItem in your SingleLevel component with item parameter like this:
<ListItem button onClick={() => categoryHandler(item)}>
You can take a look at this sandbox for a live working example of this solution.

React Class Component is not changing with the change of its props [duplicate]

react-router-dom v5 and React 16
My loading app component contains:
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<BrowserRouter>
<StartApp />
</BrowserRouter>,
</FirebaseContext.Provider>,
document.getElementById("root")
);
I have a route component which contains:
{
path: "/member/:memberId",
component: MemberForm,
layout: "/admin"
},
Admin component:
return (
<>
<div className="main-content" ref="mainContent">
<LoadingComponent loading={this.props.authState.loading}>
<AdminNavbar
{...this.props}
brandText={this.getBrandText(this.props.location.pathname)}
/>
<AuthDetailsProvider>
<Switch>{this.getRoutes(routes)}</Switch>
</AuthDetailsProvider>
<Container fluid>
<AdminFooter />
</Container>
</LoadingComponent>
</div>
</>
)
this.getRoutes in the Switch contains the reference route above.
Now from one of my component pages I can navigate to /member/{memberid} this works fine.
the route loads a component called MemberForm
inside MemberForm I have a row that contains this method:
<Row>
{ this.displayHouseholdMembers() }
</Row>
displayHouseholdMembers = () => {
const householdDetails = this.state.family;
if (householdDetails) {
return householdDetails.map((key, ind) => {
if (key['uid'] != this.state.memberKeyID) {
return (
<Row key={ind} style={{ paddingLeft: '25px', width: '50%'}}>
<Col xs="5">
<Link to={ key['uid'] }>
{ key['first'] + " " + key['last'] }
</Link>
</Col>
<Col xs="4">
{ key['relation'] }
</Col>
<Col xs="3">
<Button
color="primary"
size="sm"
onClick={(e) => this.removeHouseRelation(key)}
>
Remove
</Button>
</Col>
</Row>
);
}
});
}
};
MemberForm:
in componentDidMount I do an firebase call to check for the data pertaining to the user using the uid aka memberId in the URL.
class MemberForm extends React.Component {
constructor(props) {
super(props);
this.state = {
...INITIAL_STATE,
currentOrganization: this.props.orgID,
householdRelation: ['Spouse', 'Child', 'Parent', 'Sibling'],
householdSelected: false,
};
}
componentDidMount() {
let urlPath, personId;
urlPath = "members";
personId = this.props.match.params.memberId;
// if it is a member set to active
this.setState({ statusSelected: "Active" })
this.setState({ memberSaved: true, indiUid: personId });
// this sets visitor date for db
const setVisitorDate = this.readableHumanDate(new Date());
this.setState({ formType: urlPath, visitorDate: setVisitorDate }, () => {
if (personId) {
this.setState({ memberSaved: true, indiUid: personId });
this.getIndividualMemberInDB(
this.state.currentOrganization,
personId,
this.state.formType,
INITIAL_STATE
);
}
});
}
...
return (
<>
<UserHeader first={s.first} last={s.last} />
{/* Page content */}
<Container className="mt--7" fluid>
<Row>
...
<Row>
{ this.displayHouseholdMembers() }
</Row>
</Form>
</CardBody>
) : null}
</Card>
</Col>
</Row>
<Row>
<Col lg="12" style={{ padding: "20px" }}>
<Button
color="primary"
onClick={e => this.submitMember(e)}
size="md"
>
Save Profile
</Button>
{ this.state.indiUid ? (
<Button
color="secondary"
onClick={e => this.disableProfile()}
size="md"
>
Disable Profile
</Button>
) : null }
</Col>
</Row>
</Container>
</>
);
When I click on the Link it shows the url has changed 'members/{new UID appears here}' but the page does not reload. I believe what's going on is that since it's using the same route in essence: path: "/member/:memberId"it doesn't reload the page. How can I get it to go to the same route but with the different memberId?
You are correct that the MemberForm component remains mounted by the router/route when only the path param is updating. Because of this the MailForm component needs to handle prop values changing and re-run any logic depending on the prop value. The componentDidUpdate is the lifecycle method to be used for this.
Abstract the logic into a utility function that can be called from both componentDidMount and componentDidUpdate.
Example:
getData = () => {
const urlPath = "members";
const { memberId } = this.props.match.params;
// this sets visitor date for db
const setVisitorDate = this.readableHumanDate(new Date());
this.setState(
{
// if it is a member set to active
statusSelected: "Active",
memberSaved: true,
indiUid: memberId,
formType: urlPath,
visitorDate: setVisitorDate
},
() => {
if (memberId) {
this.setState({ memberSaved: true, indiUid: memberId });
this.getIndividualMemberInDB(
this.state.currentOrganization,
memberId,
this.state.formType,
INITIAL_STATE
);
}
}
);
}
The lifecycle methods:
componentDidMount() {
this.getData();
}
componentDidUpdate(prevProps) {
if (prevProps.match.params.memberId !== this.props.match.params.memberId) {
this.getData();
}
}
For react-router-dom v6, can you try with simple routing? Create a Test.js with
const Test = ()=> <h1>Test Page</h1>
Then, create a Home.js with
const Home = ()=> <Link to="/test">Test</Link>
Then, add them to route.
<BrowserRouter>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/test" element={<Test />} />
</Routes>
</BrowserRouter>
Does your component structure look like this? For index route, look more at https://reactrouter.com/docs/en/v6/getting-started/overview.

Match Q&A with React?

I have a set of questions and a set of answers. Each answer is the only correct answer for one question. I need to highlight the right selected question or answer when it's clicked.
For example:
When a question is clicked, change that specific question's class to "active" (so the css changes)
When an answer is clicked, change that specific answer's class to "active"
Here's the main page:
constructor(props) {
super(props)
this.handleQAClick = this.handleQAClick.bind(this)
this.toggleColour = this.toggleColour.bind(this)
this.state = {
questions: [],
active: true
}
}
...
handleQAClick = (type, id) => {
console.log(id)
console.log(type)
this.toggleColour(id)
}
toggleColour = id => {
this.setState({active: !this.state.active})
console.log('should change colour')
}
...
<Card>
{this.state.questions.length
? (
<List>
{this.state.questions.map(question => (
<ListItem key={question._id}>
<MatchItem
id={question._id}
type="question"
active={this.state.active}
text={question.question}
handleClick={this.handleQAClick}
/>
</ListItem>
))}
</List>
)
: ('No questions found')
}
</Card>
<Card>
{this.state.questions.length
? (
<List>
{this.state.questions.map(question => (
<ListItem key={question._id}>
<MatchItem
id={question._id}
type="answer"
text={question.option1}
handleClick={this.handleQAClick}
/>
</ListItem>
))}
</List>
)
: ('No questions found')
}
</Card>
Here's the MatchItem component:
import React, {Component} from 'react'
export class MatchItem extends Component {
render() {
return (
<div className={`match-item${this.props.active ? "-active" : ""}`} data-id={this.props.id} data-type={this.props.type} onClick={() => this.props.handleClick(this.props.id, this.props.type)}>
{this.props.text}
</div>
)
}
}
One way to approach this can be:
Assign a unique id to each Card (Question).
Change classes this way
<Component
className={this.state.currQuestion === question.id ? 'active' : null}
onClick={this.handleClick(question.id)}
/>
And you can manage states this way:
handleClick = (id) => {
this.setState({
currQuestion: id
})
}

Isolating a function when data is mapped in react

I have data being mapped as a repeater. But I need to isolate the opening function (It's an accordion). I'm still learning my way through React. Basically, the accordions load with the state for open: false Once the ListItem is clicked, the HandleClick function toggles the state to open: true. A simple concept, I just need to isolate it so that it works independently. Whereas right now they all open and close at the same time.
Here is the state in a constructor and function
constructor(props) {
super(props);
this.state = {
open: true,
};
}
handleClick = () => { this.setState({ open: !this.state.open }); };
Here is my mapping script in ReactJS
{LicenseItems.map((item, index) => (
<div key={index}>
<ListItem
divider
button
onClick={this.handleClick}>
<ListItemText primary={<CMLabel>{item.accordion_name}</CMLabel>}/>
</ListItem>
<Collapse
in={!this.state.open}
timeout="auto"
unmountOnExit>
{item.content}
</Collapse>
</div>
))}
The in dictates whether it is open or not per MaterialUI-Next
Thanks in advance guys!
Not very pretty, but something like this should work:
constructor(props) {
super(props);
this.state = {
open: {},
};
}
handleClick = (idx) => {
this.setState(state => ({open: { [idx]: !state.open[idx]} }))
}
// in render
{LicenseItems.map((item, index) => (
<div key={index}>
<ListItem
divider
button
onClick={() => this.handleClick(index)}>
<ListItemText primary={<CMLabel>{item.accordion_name}</CMLabel>}/>
</ListItem>
<Collapse
in={!this.state.open[index]}
timeout="auto"
unmountOnExit>
{item.content}
</Collapse>
</div>
))}
It would be better to create separate Components for that, which have their own open state.
You should create two components for that:
Accordions.js
import React from 'react'
import Accordion from './Accordion'
const Accordions = props => {
return (
props.LicenseItems.map((item, index) => (
<Accordion key={index} item={item} />
))
);
}
export default Accordions;
Accordion.js
import React, { Component } from 'react'
class Accordion extends Component {
constructor(props) {
super(props);
this.state = {
open: true,
};
}
handleClick = () => { this.setState({ open: !this.state.open }); };
render() {
return (
<div>
<ListItem
divider
button
onClick={this.handleClick}>
<ListItemText primary={<CMLabel>{this.props.item.accordion_name}</CMLabel>}/>
</ListItem>
<Collapse
in={!this.state.open}
timeout="auto"
unmountOnExit>
{this.props.item.content}
</Collapse>
</div>
)
}
}
export default Accordion;

Native base list item click event not working

I have started learning react native 2 days back only. I am using list item component from Native Base UI framework.
According to their docs and examples to catch a click event on ListItem you need to add onPress and button option to the ListItem. But in my case its not working.
I have another element with also tracks click event, it works fine, but list element isn't catching click event.
Strange this is that if I trigger a alert, it works
<List button onPress={() => { Alert.alert('Item got clicked!') } }>
Below id my complete code
import React from 'react';
import {
Content,
List,
ListItem,
Body,
Thumbnail,
Text,
Badge,
View
} from 'native-base';
import { ActivityIndicator, TouchableHighlight, TouchableOpacity, Alert } from 'react-native';
export default class Questions extends React.Component{
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
Alert.alert("I am clicked");
// Call method from parent
this.props.onPress();
}
render() {
var items = this.props.items;
return (
<Content>
<List button onPress={() => { this.handleClick } }>
{Object.keys(items).map(function(eachQuestion) {
return (
<ListItem avatar key={items[eachQuestion].id} button onPress={() => { this.handleClick } } >
<Body>
<Text>{items[eachQuestion].question}</Text>
</Body>
</ListItem>
)
})}
</List>
<TouchableOpacity onPress={this.handleClick}>
<View><Text>Click me</Text></View>
</TouchableOpacity>
</Content>
);
}
}
Edit 1
render() {
var questions = {
"1" : "James",
"2" : "Smith",
"3" : "Doe",
"4" : "Smith"
};
return (
<Container>
<Content>
<List>
{Object.keys(questions).map(function(key) {
return (<ListItem button={true} onPress={this.handleClick}>
<Text>{questions[key]}</Text>
</ListItem>
)
})}
</List>
</Content>
</Container>
);
}
** Final Solution **
handleClick(){
Alert.alert("I got clicked");
}
render() {
var questions = this.props.questions;
return (
<Content>
<List>
{Object.keys(questions).map((eachQuestion) => {
return (
<ListItem key={questions[eachQuestion].id} button={true} onPress={this.handleClick} >
<Body>
<Text>{questions[eachQuestion].question}</Text>
</Body>
</ListItem>
)
})}
</List>
</Content>
);
}
Two errors:
You should brush up on your ES6 arrow function expressions. You aren't calling your handleClick function which is why nothing is happening vs your Alert example where it does work (since you are actually doing something).
You don't define the value for the button prop. The docs say that there is no default value, so it's good practice to define it as true or false.
So to fix your code, you should define your props for ListItem like so:
button={true}
onPress={() => { this.handleClick() }}
OR to make it shorter:
button={true}
onPress={this.handleClick}
I'm also not sure why you are defining button and onPress props on your List component since it's the ListItems that you are trying to click, not the entire List itself. But since that isn't part of the question, I won't address that.
Full example of working code:
import React, { Component } from 'react';
import { Container, Content, List, ListItem, Text } from 'native-base';
import { Alert } from 'react-native';
export default class App extends Component {
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
Alert.alert("I am clicked");
// Call method from parent
//this.props.onPress();
}
render() {
return (
<Container>
<Content>
<List>
<ListItem button={true} onPress={this.handleClick}>
<Text>Simon Mignolet</Text>
</ListItem>
<ListItem button={true} onPress={() => { this.handleClick() }}>
<Text>Nathaniel Clyne</Text>
</ListItem>
<ListItem button={true} onPress={this.handleClick}>
<Text>Dejan Lovren</Text>
</ListItem>
</List>
</Content>
</Container>
);
}
}

Categories