I am working on a component right now that is a mapped stack of divs. Each one should have a tooltip but for the life of me I can't get the tooltip to appear
class App extends Component {
constructor() {
super();
this.state = {
options: [
{
id: '1',
label: 'Industrial Truck and Tractor Operators',
value: '53-7051',
tooltip_text: 'Operate industrial trucks or tractors equipped to move materials around a warehouse, storage yard, factory, construction site, or similar location. Excludes “Logging Equipment Operators" (45-4022).',
},
{
id: '2',
label: 'Order Clerks',
value: '43-4151',
tooltip_text: 'Receive and process incoming orders for materials, merchandise, classified ads, or services such as repairs, installations, or rental of facilities. Generally receives orders via mail, phone, fax, or other electronic means. Duties include informing customers of receipt, prices, shipping dates, and delays; preparing contracts; and handling complaints. Excludes "Dispatchers, Except Police, Fire, and Ambulance" (43-5032) who both dispatch and take orders for services.',
},
],
value: null,
className: '',
selectedClassName: '',
loading_State: true, loads
childrenCount: 0
};
this.setProps = this.setProps.bind(this);
}
setProps(newProps) { //this is going to update the state
this.setState(newProps);
}
render() {
return (
<div>
<DashControlledContainer
setProps={this.setProps}
options = {this.state.options}
value = {this.state.value}
styles = {this.state.styles}
className = {this.state.className}
selectedClassName = {this.state.selectedClassName}
loading_State = {this.state.loading_State}
childrenCount = {this.state.childrenCount}
/>
</div>
)
}
}
export default App;
//the component being returned with the tooltip
render(){
return (
<div style={this.props.styles}>
{this.props.options.map(option => (
<div key = {option} id={option.id} style={option.style}
onClick = {e=>{ //updates the props with the clicked targets value if setProps is accessible
if(this.props.setProps){
this.props.setProps({value: e.target.value})
}else{
this.setState({value:e.target.value})
}
}}
>
<span id={option.id}> {option.label} </span>
<UncontrolledTooltip placement="right" target={option}>
{option.tooltip_text}
</UncontrolledTooltip>
</div>
))}
</div>
);
}
I'm not sure where to set my target for the tooltip maybe thats the issue? I haven't been able to find many resources online. Any help would be appreciated.
I think you should provide id as a target to your UncontrolledTooltip,
<UncontrolledTooltip placement="right" target={option.id}> //set id of span as a target here
hello
</UncontrolledTooltip>
I was able to figure it out, including a string in my id fixed it for some reason?
i made my div contain id={"option"+option.id}
then I referenced it like so:
<UncontrolledTooltip placement="right" target={"option"+option.id}>
{option.tooltip_text}
</UncontrolledTooltip>
Related
I have an Array with a list of objects , each object contains an ID ,title , job description and salary. I saved it in a separate file as below :
export const CareerList = [
{
id: 1,
title: "Junior Accountant",
salary: "1500$",
jobDescription: [
"Maintains financial records for subsidiary companies by analyzing balance sheets and general ledger accounts",
"Reconciles general and subsidiary bank accounts by gathering and balancing information",
"Provides financial status information by preparing special reports; completing special projects",
"Corrects errors by posting adjusting journal entries",
"Maintains general ledger accounts by reconciling accounts receivable detail and control accounts; adjusting entries for amortizations prepaids; analyzing and reconciling retainage and accounts payable ledgers; preparing fixed asset depreciation and accruals",
"Secures financial information by completing database backups; keeping information confidential",
"Maintains accounting controls by following policies and procedures; complying with federal, state, and local financial legal requirements",
"Updates job knowledge by participating in educational opportunities; reading professional publications",
"Accomplishes accounting and organization mission by completing related results as needed",
],
},
{
id: 2,
title: "Research Analyst",
salary: "3500$",
jobDescription: [
"Support the Director of Research & Impact and the Research Manager in implementing all phases of ANDE research projects",
"Design and administer surveys and conduct secondary data collection from online sources to aggregate data related to global SGB support.",
"Clean and analyze data to identify key trends, and develop reports communicating these insights to practitioners",
"Track new research developments related to SGBs and collect and synthesize this research for ANDE members.",
"Provide support in identifying and selecting consultants and interns to support research activities and coordinate with these consultants and interns to carry out research.",
"Manage the content of ANDE’s various online research portals, such as www.galidata.org, http://ecosystems.andeglobal.org, and www.andeglobal.org/knowledge-hub.",
"Manage administrative functions related to project funding (e.g. tracking expenses).",
],
},
I am trying to create two modals , one modal which only display the job titles with the button job details next to it , and if I clicked on a particular job button, the list modal should hide and another modal with that specific job details should show, any suggestion how it can be done?
import { CareerList } from "../data/Careers";
import ButtonMedium from "../UI/ButtonMedium";
import JobDetails from "./JobDetails";
const Backdrop = (props) => {
return <div className={classes.backdrop} onClick={props.onHide}></div>;
};
const CareerOverlay = () => {
const [showJobDetails, setShowJobDetails] = useState(false);
const displayJobDetails = () => {
setShowJobDetails(true);
};
return (
<>
<div className={classes.careerBox}>
{CareerList.map((job, index) => {
return (
<div className={classes.jobItem} key={index}>
<h2>{job.title}</h2>
<ButtonMedium onClick={displayJobDetails}>
Job Detail
</ButtonMedium>
{showJobDetails && (
<JobDetails careerList={CareerList} id={job.id} />
)}
</div>
);
})}
</div>
</>
);
};
const CareerOpportunities = (props) => {
return (
<>
{reactDom.createPortal(
<Backdrop onHide={props.onHide} />,
document.getElementById("backdrop")
)}
{reactDom.createPortal(
<CareerOverlay onShow={props.onShow} />,
document.getElementById("career")
)}
</>
);
};
export default CareerOpportunities;
import React from "react";
import classes from "./JobDetails.module.css";
const JobDetails = (props) => {
const particularJob = props.careerList.find((job) => job.is === props.id);
return (
<div className={classes.jobBox}>
<h1>{particularJob.title}</h1>
</div>
);
};
export default JobDetails;
Although the other answer seems alright, I'd also advise you to use portals for modals in React, and by that way you won't be bothered by stacking order when you're styling both modals.
Read more about portals here:
https://reactjs.org/docs/portals.html
You can split each item using new child component. So each item can manage modal state individually.
const CareerListItem = (props) => {
const [showJobDetails, setShowJobDetails] = useState(false);
const displayJobDetails = () => {
setShowJobDetails(true);
};
return (
<div className={classes.jobItem} key={index}>
<h2>{job.title}</h2>
<ButtonMedium onClick={displayJobDetails}>
Job Detail
</ButtonMedium>
{showJobDetails && (
<JobDetails job={props.job} />
)}
</div>
)
}
const CareerOverlay = () => {
return (
<>
<div className={classes.careerBox}>
{CareerList.map((job, index) => {
return (
<CareerListItem job={job} />
)
})}
</div>
</>
);
};
...
const JobDetails = (props) => {
return (
<div className={classes.jobBox}>
<h1>{props.job.title}</h1>
<div>details here...</div>
</div>
);
};
I'm developing a menu page, where users can see the menu's items based on their roles.
Currently, I have 3 pages: Bulletin Board, Info Hub and Settings
So naturally, I have 3 roles: Admin Role (can access all 3 of the pages), Bulletin Board (can only access Bulletin Board), Info Hub (can only access Info Hub)
So users can have a different roles, for example, if they have Bulletin Board and Info Hub, then they can access both of them, but not the Settings page (only "Admin Role" can see Settings), so I want to hide the Settings in this menu that I've already developed and rendered using map.
Or if the user has all 3 roles including Admin Role, then they can see everything as well.
I'm taking the loginList prop from an API and passing it into the AllAppsCentre.js to determine which menu items to show, but I just can't figure out the logic to do a filter or indexOf at the map.
In the codesandbox that I've created, the user has all 3 roles.
AllAppsCentre.js(map function to display the menu items)
useEffect(() => {
const loginListFromParent = loginList;
const showAll = loginListFromParent.some(
(item) =>
item.permissionName.includes("Admin Role") &&
item.permissionType.includes("view")
);
const showBulletin = loginListFromParent.some(
(item) =>
item.permissionName.includes("Bulletin Board") &&
item.permissionType.includes("view")
);
const showInfoHub = loginListFromParent.some(
(item) =>
item.permissionName.includes("Info Hub") &&
item.permissionType.includes("view")
);
if (loginListFromParent) {
setShowAll(showAll);
setShowBulletin(showBulletin);
setShowInfoHub(showInfoHub);
}
}, [loginList]);
return (
{AllAppsCentreData
.filter((item) => {
.map((item, index) => {
return (
<Col key={index} xs={6} md={3}>
<div className={item.className}>
<Link to={item.path}>
{item.icon}
<Row>
<span className='apps-title'>
{item.title}
</span>
</Row>
</Link>
</div>
</Col>
)
})}
)
AllAppsCentreData.js
import * as IoIcons from 'react-icons/io'
import * as MdIcons from 'react-icons/md'
export const AllAppsCentreData = [
{
title: 'Bulletin Board',
path: '/bulletinboard',
icon: <IoIcons.IoIosPaper size={80} />,
className: 'row text-center apps-centre-icon'
},
{
title: 'Info Hub',
path: '/infohub',
icon: <MdIcons.MdDeviceHub size={80} />,
className: 'row text-center apps-centre-icon'
},
{
title: 'Settings',
path: '/settings',
icon: <IoIcons.IoMdSettings size={80} />,
className: 'row text-center apps-centre-icon'
},
]
I've been trying to figure out how to deal with this but I just couldn't think of a solution, if all else fails, I might just remove the map method and just copy and paste my AllAppsCentreData's items and move it directly into the AllAppsCentre page instead so I can do ternary operations at the menu items.
If there is any better way to do this menu with the role-based display, feel free to let me know as I also want to learn the optimal way to do something like this.
To start off, there is no variable in your code that is responsible for holding current user's permissions (or I can't see one). So I made it up:
const currentUsersPermissions = useGetPermissions() // get users permissions
// the above can be "admin", "bulletin" or "info-hub"
Then, you need to first filter your array and then map it (currently, you are using map inside of filter). To do it easier and correctly, you could add a property called requiredPermission (or something simillar) to your AllAppsCentreData objects:
{
title: 'Bulletin Board',
path: '/bulletinboard',
icon: <IoIcons.IoIosPaper size={80} />,
className: 'row text-center apps-centre-icon',
requiredPermission: "bulletin" // and info-hub for Info Hub
},
Then, while rendering and mapping, you can do it like this:
return (
{AllAppsCentreData
.filter(
// below, you return true if permission is admin because he can see everything
// and true or false depending if permissions are other than that but
// the same as required by AllAppsCentreData array objects
(item) => currentUsersPermissions === "admin" ? true : currentUsersPermissions === item.requiredPermission
).map((item, index) => {
return (
<Col key={index} xs={6} md={3}>
<div className={item.className}>
<Link to={item.path}>
{item.icon}
<Row>
<span className='apps-title'>
{item.title}
</span>
</Row>
</Link>
</div>
</Col>
)
)
So, to sum up:
I have created a variable to hold current users' permission
I have added a property to each AllAppsCentreData's objects, which contains info about permission required to show it
I have filtered the array right before mapping it and rendering, so it contains only the data I want to render
Also, I think you are lost in using filter method, so perhaps read this and try doing some easier excercises to get a feeling how it works: MDN on Array.filter method. The thing you need to pay attention is that you pass a function into filter, and this function should return true or false depending on whether you want your item included in the output array.
Have a Parent Component which certain props such as name, queryValue and image to my Child Component which is a Custom Dropdown where as of now i am displaying the names based on a condition check for the default display in the dropdown, but now more data will be shown along with flags. I need to show the name based on the value from the parent .
Parent.jsx
<SelectCountry
options={[
{
name: 'English (USA)',
queryValue: 'en',
flagIcon: USA,
},
{
name: 'Bahasa Indonesia',
queryValue: 'id',
flagIcon: ID,
},
]}
value={localStorage.getItem('locale')} />
SelectCountry.jsx
<div className={`${baseClassName}__selected-flag-container`}>
<img
src={require(value === "en" ? '../../assets/USA.svg' : '../../assets/indonesia.svg')}
alt='desc'
/>
{value === "en" ? options[0].name : options[1].name}
</div>
//COde for the Custom DropDown will be here
As above, HAve put the condition check and its working since there were only two languages, now when more languages are added, not sure how to pick the name and also setting the src in img tag dynamically.
You are doing all in a bit of wrong way your Parent.jsx is the container component so it should hold all your logic so why not pass the appropriate data.
<SelectCountry
options={[
{
name: 'English (USA)',
queryValue: 'en',
flagIcon: USA,
src: '../../assets/USA.svg'
},
{
name: 'Bahasa Indonesia',
queryValue: 'id',
flagIcon: ID,
src: '../../assets/indonesia.svg'
},
]}
value={localStorage.getItem('locale')}
/>
& in your presentation component SelectCountry.jsx you just present.
const { options } = require('joi');
options.map((option) => {
<div className={`${baseClassName}__selected-flag-container`}>
<img src={option.src} alt='desc' />
{option.name}
</div>;
});
I haven't tested so you might need to adjust the code. Dan has some fantastic free courses on Redux where he teaches more about container & presentation component you can check it out here https://egghead.io/instructors/dan-abramov. Hope this will help you 🙂️🙂️
I'm currently working on an accordion component in react version 16.3.2, that receives props from another component and displays the accordion accordingly. Unfortunately I cannot use hooks.
This works pretty smoothly, what I want to implement now is a way so that only one accordion can be opened at the same time on the same page. So, once you open one accordion (while another is already opened) it should automatically close the already opened one.
I have an Id (string which describes the current section, e.g 'contact', 'info', etc.) and the state of an accordion gets saved in the state (set to true when you toggle the accordion).
I'm not quite sure on how I could implement this mechanism and am looking for tips on how I could solve this in a smart way. Any pointers?
example:
https://codesandbox.io/s/reactjs-accordion-automatic-close-mechanism-6dys1
(I didn't add all of the styling, animations since this is more about functionality)
You could do something like this, using the state hook in the App component
export default function App() {
const items = [
{ id: 1, title: 'First Accordion', content: 'Hello' },
{ id: 2, title: 'Click me', content: 'Hello 2' },
{ id: 3, title: 'Third Accordion Accordion', content: 'Hello 3' },
]
const [selectedItem, setSelectedItem] = useState(1)
const handleClick = id => {
setSelectedItem(id)
}
return (
<div className="App">
{items.map(x => {
return (
<Accordion
key={x.id}
id={x.id}
title={x.title}
open={x.id === selectedItem}
onClick={handleClick}
>
<p>{x.title}</p>
</Accordion>
)
})}
</div>
);
}
Then your accordion component is a bit simpler
class Accordion extends React.Component {
accToggle() {
this.props.onClick(this.props.id);
}
sectionClasses() {
let classes = "accordion";
classes += this.props.open ? " sec-on" : "";
classes += "sec-underway";
return classes.trim();
}
render() {
return (
<section className={this.sectionClasses()} id={this.props.id}>
<div className="acc-title" onClick={this.accToggle.bind(this)}>
<h3 className="acc-text">{this.props.title}</h3>
<div className="acc-nav">
<span className="acc-toggle" />
</div>
</div>
<div className="acc-content">{this.props.children}</div>
</section>
);
}
}
Accordion.defaultProps = {
open: false
};
Accordion.propTypes = {
id: PropTypes.number.isRequired,
children: PropTypes.any,
onFocus: PropTypes.func,
progress: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
PropTypes.bool
]),
title: PropTypes.string,
open: PropTypes.bool
};
export default Accordion;
The accordion calls a function on the app component, which updates the state and the display logic is passed down in the props
You can find solution for your problem in the following codesandbox
https://codesandbox.io/s/reactjs-accordion-automatic-close-mechanism-yejc0
Change prop names as it fits your code base, but the logic is solid
I am trying to pass props from two arrays that are contained in the state of the react component. One array is generated by the user and the other is already built in. This is a bit over my head as I'm still new to React and am unsure how to correctly pass props.
There are no errors here in the component, it's just that I don't know how to do it.
I will explain below what I'm looking for, any help would be greatly appreciated
Main Component
In this.state below you will see questions (which works perfectly) then hints. Questions is mapped over correctly however when I try to add in hints to map along with it, it returns all of the hints at once instead of in order and one by one. I've tried just adding (questions, hints) but it doesn't return it correctly.
export default class AutoFocusText extends Component {
constructor() {
super();
this.state = {
active: 0,
questions: [
'1. Enter A Proper Noun',
'2. Enter A Location',
'3. Enter A Proper Noun that Describes Evil',
'4. Describe Something Menacing',
'5. Describe a fortified area',
"6. A Woman's Name",
'7. Describe a large area of mass'
],
hints: [
"Hint: Use words like Rebel, Hell's Angels",
'Hint: Use a word such as Base, Bunker, Foxhole, Bedroom',
'Hint: Use words like Empire, Ottoman, Mongols',
'Hint: Freeze Ray, Leftover Fruitcake',
'Hint: Castle, Bunker, Planet',
'Hint: Astrid, Diana, Mononoke, Peach',
'Hint: Use words such as Galaxy, Planet, Wal Mart'
],
answers: []
};
I'd like to take the user's inputs of answers and have it passed along as props into another component such as properName1={this.state.value1} and so on, I know that's mapping an array of answers, I'm just unsure how to do this.
Below is the rest of the main component.
this.submitHandler = this.submitHandler.bind(this);
this.renderQuestion = this.renderQuestion.bind(this);
this.onChange = this.onChange.bind(this);
}
renderQuestion() {
const { questions, hints, active, value } = this.state;
if (active >= questions.length)
return <Crawler style={{ width: '500px', position: 'absolute' }} />;
return questions.filter((quest, index) => index === active).map(quest => ( // get next question // map over selected question, the key prop allows react to
<FormElement
key={active}
text={quest}
hint={hints}
value={value}
onChange={this.onChange}
/>
));
}
onChange(e) {
this.setState({ value: e.target.value });
}
submitHandler(e) {
e.preventDefault();
const answers = [...this.state.answers, this.state.value]; //push new value to answsers array without mutation
const value = ''; // clear input
const active = this.state.active + 1; // index pointer
this.setState({ answers, value, active });
}
render() {
return (
<MainContainer>
<DivStyle>
{/* Form Wrapper */}
<form onSubmit={this.submitHandler}>
{this.renderQuestion()}
<SubmitButton type="submit">Submit</SubmitButton>
</form>
<ul>
{this.state.answers.map((ans, index) => {
return (
<li key={index}>
{ans}
</li>
);
})}
</ul>
</DivStyle>
</MainContainer>
);
}
}
Child Component 1
This is the dumb component where the questions (and where I want the hints as well) generated
class FormElement extends Component {
constructor() {
super();
}
componentDidMount() {
//focus text input upon mounting component
this.textInput.focus();
}
render() {
const { text, hint, value, onChange } = this.props;
return (
<div>
<InputQuestion>
{text}
{hint}
</InputQuestion>
<input
className="inputstyling"
ref={el => {
this.textInput = el;
}}
onChange={onChange}
type="text"
value={value}
/>
</div>
);
}
}
export default FormElement;
At the moment, "hint" brings in all the hints at once, instead of one at a time and in order.
Child Component 2
Finally the props needed to pass go here. The array is throwing me as I've never passed props via an array
class Crawler extends Component {
constructor() {
super();
this.state = {
properName1: 'Rebel',
noun1: 'frog',
properName2: 'Empire',
properName3: 'DEATH STAR',
noun2: 'station',
noun3: 'planet',
personsName1: 'Leia',
noun4: 'starship',
pluralnoun1: 'people',
noun5: 'galaxy'
};
}
render() {
return (
<ContainLeft style={{ padding: 0 }}>
<CrawlHolder>
<div class="fade" />
<section className="star-wars">
<div className="crawl">
<div className="title">
<p>Episode IV</p>
<h1>A New Hope</h1>
</div>
<p>
It is a period of civil war.
{' '}
{this.props.properName1}
{' '}
spaceships, striking from a hidden
{' '}
{this.props.noun1}
, have won their first victory against the evil Galactic
{' '}
{this.props.properName2}
.
</p>
<p>
During the battle,
{' '}
{this.props.properName1}
{' '}
spies managed to steal secret plans to the
{' '}
{this.props.properName2}
's ultimate weapon, the
{' '}
{this.props.properName3}
, an armored
{' '}
{this.props.noun2}
{' '}
with enough power to destroy an entire planet.
</p>
<p>
Pursued by the Empire’s sinister agents, Princess
{' '}
{this.props.personsName1}
{' '}
races home aboard her starship, custodian of the stolen plans that can save her people and restore freedom to the
{' '}
{this.props.noun3}
…
</p>
</div>
</section>
</CrawlHolder>
</ContainLeft>
);
}
}
export default Crawler;
Thank you for your help
the function in map has an optional second param, the index of the current element so you could do:
return questions.filter((quest, index) => index === active).map((quest,i) => (
<FormElement
key={active}
text={quest}
hint={hints[i]}
value={value}
onChange={this.onChange}
/>
));
EDIT:
I see now you are rendering only one question at a time so a map isn't needed I think; since you have the index of the current question (active) I think you could just
return (
<FormElement
text={questions[active]}
hint={hints[active]}
value={value}
onChange={this.onChange}
/>
)