Calling a function from a conditional variable - ReactJS - javascript

I have this Button A, with content "All categories". When I click this button, I want a div with content "show more" above Button A to display 6 other buttons, each consisting of "category 1", "category 2" up to 6. (and "show more" to disappear). Clicking any of these 6 buttons would hide the 6 buttons, and go back to being the "show more" div.
CODE
var React = require('react');
module.exports = React.createClass({
getInitialState: function() {
return ({
category: false,
selected: 'Total',
categories: [
{
name: 'Button 1',
id: 1,
},
{
name: 'Button 2',
id: 2,
},
{
name: 'Button 3',
id: 3,
},
{
name: 'Button 4',
id: 4,
},
{
name: 'Button 5',
id: 5,
},
{
name: 'Button 6',
id: 6,
}
]
})
},
selectCategory: function(event) {
(this.state.category) ? this.setState({category: false}) : this.setState({category: true})
},
render: function() {
if(this.state.category == true) {
var category = this.state.categories.map(function(category){
return (
<div
className="category-container"
onClick={this.selectCategory}>
<span> {category['name']} </span>
</div>
)
})
} else {
var category = (
<span id="showplease" onClick={this.selectCategory}> Show more </span>
)
}
console.log(this.state.selected)
return (
<div id="landing">
<div id="search-container">
<div id="category-selector-container">
{category}
<div id="selector" onClick={this.selectCategory}> Button A </div>
</div>
</div>
</div>
)
}
})
Simple enough. HERE IS MY ISSUE: Clicking any of the 6 buttons does not change the state, as if after being mapped, the individual components lost the 'onClick={this.selectCategory}' part. However, clicking the "Show more" div will run the selectCategory function, and will show the 6 buttons.
Any help? I can simply avoid the mapping and individually repeat the buttons, but I would like to know why this is not working.
Thank you in advance!

Your problem involves the this variable. Whenever you create a new function, a new context for this is created and the old one is lost.
There are a few ways around this. A common one is to assign this to a different variable, e.g. var self = this;. Another way is to use bind to ensure this is passed from the outer scope. i.e.
var category = this.state.categories.map(function (category) {
return (
<div
className="category-container"
onClick={this.selectCategory}
>
<span>{category['name']}</span>
</div>
);
}.bind(this));
If you're using ES6, the best way is to use arrow functions, which automatically bind this.
var category = this.state.categories.map((category) => (
<div
className="category-container"
onClick={this.selectCategory}
>
<span>{category['name']}</span>
</div>
));

Related

Vue how to set the active tab

I have a Vue tab component that console logs out the correct index of the selected tab but I don't know how to set the active tab so it is opened. Any help or direction would be appreciated.
Component:
<Tabs
:tabs="tabs"
:active-tab-index="activeTab"
#tab-changed="changeTab"
/>
Markup
<div v-show="activeTab === 0">
Show tab one content
</div>
<div v-show="activeTab === 1">
Show tab two content
</div>
Data:
data() {
return {
tabs: [
{ id: 0, name: 'Tab One' },
{ id: 1, name: 'Tab Two' },
],
activeTab: 0,
}
},
Method:
changeTab(index) {
console.log('Change activeTab', index)
},
it's not completely clear to me where the method and data is, but I assume it's in the parent, so in that case you'd simply need to change the function to something like this:
changeTab(index) {
this.activeTab = index
},
your component could be even cleaner if it would provide option to use v-model on it like: v-model:activeTabIndex="activeTab". To make this happen you'd just need to emit the data inside the component with update:activeTabIndex instead of tab-changed.

SetState inside useEffect is causing side effects on select input functionality

Each select menu comes with a help text inside a box. Similar to a tooltip. User can close them when clicking 'close button' or clicking outside.
My solution works and they are being closed each time you click outside them.
The problem is that setState inside the useEffect has a side effect on the select menus.
The issue is when I close the info box using the 'close button' or click inside the info box. After I close it with the button or click inside it, if I try to change an option, I see the options flickering and I can't change selection, it would only work the second time.
Here is my code: https://stackblitz.com/edit/react-61rzle?file=src%2FSelect.js
export default function Select() {
const selectMenus = [
{
Label: 'Select 1',
Name: 'select1',
DefaultValue: '1',
HelpText: 'Help text',
Id: 'select_1',
Options: [
{
Value: '0',
Text: 'All age groups',
},
{
Value: '1',
Text: 'Less than 35',
},
{
Value: '2',
Text: '35 - 37 yrs',
},
{
Value: '3',
Text: '38 - 39 yrs',
},
{
Value: '4',
Text: '40 - 42 yrs',
},
{
Value: '5',
Text: '43 - 44 yrs',
},
{
Value: '6',
Text: '45 yrs +',
},
],
},
{
Label: 'Select 2',
Name: 'select2',
DefaultValue: '0',
HelpText: 'Help text',
Id: 'select_2',
Options: [
{
Value: '0',
Text: 'All',
},
{
Value: '1',
Text: 'Less than 35',
},
{
Value: '2',
Text: '43 - 44 yrs',
},
],
},
];
const [value, setValue] = useState({
select1: '',
select2: '',
});
// help texts setup
const initialVisibleHelpTexts = {
info0: false,
info1: false,
info2: false,
};
const [visibleHelpText, setVisibleHelpText] = useState(
initialVisibleHelpTexts
);
const showHelpText = (e, key) => {
e.preventDefault();
e.stopPropagation();
setVisibleHelpText({ ...initialVisibleHelpTexts, ...{ [key]: true } });
};
const hideHelpText = (e, key) => {
e.preventDefault();
e.stopPropagation();
setVisibleHelpText({ ...visibleHelpText, ...{ [key]: false } });
};
// close info on click outside
useEffect(() => {
document.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
if (
e.target.parentNode.className !== 'info__content' &&
e.target.parentNode.className !== 'info__content-header-text' &&
e.target.parentNode.className !== 'info__content-header'
) {
setVisibleHelpText(initialVisibleHelpTexts);
}
});
}, []);
const handleOnChange = (e) => {
const valueSelected = e.target.value;
setValue({
...value,
[e.target.name]: valueSelected,
});
};
return (
<form>
{selectMenus.length > 0 && (
<div className="selectors-container">
{selectMenus.map((select, i) => (
<div className="select" key={uuid()}>
<div className="select__label-container">
<div className="select__title">
<label className="select__label" htmlFor={select.Id}>
{select.Label}
</label>
<button
className="select__info"
onClick={(e) => {
showHelpText(e, `info${i}`);
}}
>
Show info
</button>
</div>
{visibleHelpText[`info${i}`] && (
<div className="info">
<div className="info__content">
<div className="info__content-header">
<span className="info__content-header-title">
{select.Label}
</span>
<button
onClick={(e) => {
hideHelpText(e, `info${i}`);
}}
>
Close info
</button>
</div>
<div className="info__content-header-text">
{select.HelpText}
</div>
</div>
</div>
)}
</div>
<div className="select__menu-btn-container">
<div className="select__container">
<select
name={select.Name}
id={select.Id}
value={value[`${select.Name}`]}
onChange={handleOnChange}
>
{select.Options.map((option) => (
<option value={option.Value} key={uuid()}>
{option.Text}
</option>
))}
</select>
</div>
</div>
</div>
))}
</div>
)}
</form>
);
}
The flickering happens because you have one huge component that re-renders each time you toggle the visibility of the info text. As soon as you click on the select, the whole component gets re-rendered which leads to the select being closed right away.
To solve this, you have to prevent the whole component from re-rendering. Separate it into smaller chunks, which can be rerendered separately.
Here is a simplified example to show how to isolate the info section into a self-managed component.
function InfoSection({ select }) {
const [isVisible, setIsVisible] = useState(false);
return (
<div className="select__label-container">
<div className="select__title">
<label className="select__label" htmlFor={select.Id}>
{select.Label}
</label>
<button
className="select__info"
onClick={(e) => {
setIsVisible(true);
}}
>
Show info
</button>
</div>
{isVisible && <InfoText setIsVisible={setIsVisible} />}
</div>
);
}
function InfoText({ setIsVisible }) {
function handleCLickOutside(e) {
setIsVisible(false);
}
useEffect(() => {
document.addEventListener('click', handleCLickOutside);
//this will remove the event listener, when the component gets unmounted. This is important!
return () => document.removeEventListener('click', handleCLickOutside);
}, []);
return (
<div className="info">
<div className="info__content">
<div className="info__content-header">
<span className="info__content-header-title">{'label'}</span>
<button onClick={console.log}>Close info</button>
</div>
<div className="info__content-header-text">{'select.HelpText'}</div>
</div>
</div>
);
}
Don't forget to remove your event listener, as soon as you don't need them anymore, e.g. when the component gets unmounted:
return () => document.removeEventListener('click', handleCLickOutside);
Otherwise, this could lead to bugs and performance issues.
Here is your stackblitz with the applied example.

React JS Custom Sidebar Navigation

I have Created a toggled navigation in React APP. Everything working fine except the toggle. Below is the code of toggle. I am using the check of parentId if parentId exist then include the children id's and it will open the toggle. If not exist then add the main Id which one is parentId in children.
Problem is I parentId already exist and if I click the toggle it is not closing the old one and displaying the new one along with old.
const handleArrowClick = ( data: CoursesNav ) => {
const { id, parentId } = data;
let newtoggledMenus = [...toggledMenus];
if( parentId && newtoggledMenus.includes(parentId)){
if (newtoggledMenus.includes(id)) {
var index = newtoggledMenus.indexOf(id);
if (index > -1) {
newtoggledMenus.splice(index, 1);
}
} else {
newtoggledMenus.push(id);
}
settoggledMenus(newtoggledMenus);
}else{
settoggledMenus([id]);
}
};
I have created the nav structure given below in which under the main item children are added.
export const CourseMenu = ( book: Book ): CoursesNav[] => {
if( course ){
// Chapters from book
return book.chapters.map( c => {
return {
id: c.id,
name: c.name,
link: `/my-book/${course.id}/chapter/${c.id}/edit`,
parentId: c.id,
// get topics and put under children
children: c.topics.map( m => {
return {
id: m.id,
name: m.name,
link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/edit`,
parentId: c.id,
//get blocks and put under blocks
children: m.blocks.map( b => {
return {
id: b.id,
name: b.title,
link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/block/${b.id}/edit`,
parentId: m.id
}
})
}
})
}
});
}
}
Nav logic is added just to show you that what I am trying to create.
Every parent has button for toggle to show the sub items. If any sub item has children again the toggle will displayed.
Only I have the problem in opening and closing the correct toggle. Right now under a main children if I open its all child previous one are not closing because they are getting the parentId in toggledMenu state which is a numeric array.
yow broh, why don't you use aria. don't search for the parent of no body.
The user clicks your button/link whatever the user clicks, but you need to set an aria attribute call aria-controls in which you need to put the value of which eva you want to hide/show.
start by closing any open menu or so, then wait 150 milliseconds or so. just to prevent that your loop closes the one that needs to be opened
function App () {
/**
* togging menus
*/
function onClickHandler (e) {
e.preventDefault();
const btnPressed = e.target;
// first you need to check if there is a item toggled and stuff.
const openMenus = document.querySelectorAll("[aria-expanded]");
if (openMenus.length) {
Object.keys(openMenus).forEach(button => {
const toggler = openMenus[button];
const menuId = toggler.getAttribute("aria-controls");
const menuToClose = document.getElementById(menuId);
if (menuToClose && menuToClose !== null) {
menuToClose.classList.remove("show");
menuToClose.setAttribute("aria-hidden", "true");
menuToClose.classList.add("hidden");
// now the button
toggler.setAttribute("aria-expanded", "false");
}
})
}
//wait a minute to prevent conflicts
setTimeout(()=>{
const toOpen = document.getElementById(btnPressed.getAttribute("aria-controls"));
if (!toOpen) {return;}
btnPressed.setAttribute("aria-expanded", "true");
toOpen.classList.add("show");
toOpen.setAttribute("aria-hidden", "false");
toOpen.classList.remove("hidden");
},[150])
}
return (
<div className="accordion-example">
<ul aria-label="Accordion Control Group Buttons" className="accordion-controls">
<li>
<button onClick={onClickHandler} aria-controls="content-1" aria-expanded="false" id="accordion-control-1">Apples</button>
<div className="menu hidden" aria-hidden="true" id="content-1">
<p>Apples are a fine fruit often associated with good health, and fewer doctor's appointments.</p>
<p>Example. An apple a day keeps the doctor away.</p>
</div>
</li>
<li>
<button onClick={onClickHandler} aria-controls="content-2" aria-expanded="false" id="accordion-control-2">Lemons</button>
<div className="menu hidden" aria-hidden="true" id="content-2">
<p>Lemons are good with almost anything, yet are often have a negative connotation when used in conversation.</p>
<p>Example. The bread from the french bakery is normally very good, but the one we bought today was a lemon.</p>
</div>
</li>
<li>
<button onClick={onClickHandler} aria-controls="content-3" aria-expanded="false" id="accordion-control-3">Kiwis</button>
<div className="menu hidden" aria-hidden="true" id="content-3">
<p>Kiwis are a fun, under-appreciated fruit.</p>
</div>
</li>
</ul>
</div>)
}
ReactDOM.render( < App / > , document.getElementById("page"));
.hidden {
display: none;
}
.show {
display: blocK
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="page"></div>
I am guessing you have a 3 level hierarchy inside a Book -> chapter, topic & block. On click of (let's say) right button, you want that specific item's children to be toggled. This is evidently at chapter and topic level.
One solution is maintain the entire hierarchy which you shared (book.chapters) as a state, and have a showChildren flag at each level.
return book.chapters.map( c => {
return {
id: c.id,
name: c.name,
link: `/my-book/${course.id}/chapter/${c.id}/edit`,
parentId: c.id,
showChildren: false,
// get topics and put under children
children: c.topics.map( m => {
return {
id: m.id,
name: m.name,
link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/edit`,
parentId: c.id,
showChildren: false,
//get blocks and put under blocks
children: m.blocks.map( b => {
return {
id: b.id,
name: b.title,
link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/block/${b.id}/edit`,
parentId: m.id
}
})
}
})
}
});
}
Based on the click on the right arrow, toggle that particular state.
With the above approach, you can keep multiple levels in the nav hierarchy open to toggle.

how to select All checkbox based group in reactjs

I did initial state for select single checkbox .Here is my intial state
this.state = {
fruites: [
{ id: 1 , value: "banana", isChecked: false },
{ id: 2, value: "apple", isChecked: false },
{ id: 3,value: "mango", isChecked: false },
{ id: 4, value: "grap", isChecked: false }
]
};
}
Method: I just this for selected all checkbox
handleAllChecked = id => event => {
let fruites = this.state.fruites;
fruites.forEach(fruite => {
data.filter(item =>
fruite.isChecked = event.target.checked;
});
});
this.setState({ fruites: fruites });
};
I just this method for individual checkbox .
handleCheckChieldElement = event => {
let fruites = this.state.fruites;
fruites.forEach(fruite => {
if (fruite.value === event.target.value)
fruite.isChecked = event.target.checked;
});
this.setState({ fruites: fruites });
};
Render:Here is my UI, I want to select All checkbox based on group . For example , I have got two group of value - such as Group , Topgroup. The problem is that , When I click on the group , it will select All checkbox including Topgroup and also I click banana , it will select all banana , I don't want to get all banana when click on one item. I don't to want to get topgroup checkbox when I select on the group.
{[{ id: 1, name: "group" }, { id: 2, name: "topGropup" }].map(item => (
<div>
<input
type="checkbox"
onChange={this.handleAllChecked(item.id)}
value="checkedall"
/>{" "}
{item.name}
<ul>
{this.state.fruites.map((fruite, index) => {
return (
<CheckBox
key={index}
handleCheckChieldElement={this.handleCheckChieldElement}
{...fruite}
/>
);
})}
</ul>
</div>
))}
</div>
How can I resolve this problem . Here is my codesanbox : https://codesandbox.io/s/react-multi-select-checkbox-or6ko
Here, I edited your codesandbox: https://codesandbox.io/s/react-multi-select-checkbox-bbuky
Basically you have 8 checkboxes, even though its 4 items displayed, duplicated for each group.
I added the 4 missing items in your state, but you'd actually want some kind of factory function that lets you create your state given the groups you have.
I had to edit some of your values since you were relying on stuff that now is not unique anymore, like value and use the group's id for example to create a unique identifier groupId-itemId.
Memory pointer to the same list
The groups in the app have the same pointer to memory list of fruits.
because of that the updates will affect on both groups.
See how I fixed it:
https://codesandbox.io/s/react-multi-select-checkbox-r29d1
I found some things in the app that can be improve so I improve them for example:
label to input checkbox to be able to click also on the text
I am here if you have any problem, I suggest you to learn Hooks.

How to render react component from a json object?

I made a menu component that accepts a json object with all menu itens.
For icons i use react-icons/io.
The json object is something like this:
const menus = {
Item1: { buttonText: 'Item 1 text', icon: { IoLogoAndroid }, path: 'item 1 path' },
Item2: { buttonText: 'Item 2 text', icon: { IoLogoAndroid }, path: 'item 2 path'},
};
This is the Menu function that will render the menu items as buttons:
const buttons = Object.keys(this.props.menus).map((menu) => {
return (
<a key={menu} href={this.props.menus[menu].path}
onClick={this.changeMenu.bind(this, menu)}>
{...this.props.menus[menu].icon} <- fail here
{this.props.menus[menu].buttonText}
</a>
);
})
I tried many ways to render the icon, but i am clueless on how this could work. Not sure if this is even possible. Any pointers?
If you are importing the icon from where you are defining the object then just tag it <IoLogoAndroid/>;, so react knows it should treat it as an element to render.
const menus = {
Item1: { buttonText: 'Item 1 text', icon: <IoLogoAndroid/> , path: 'item 1 path' },
Item2: { buttonText: 'Item 2 text', icon: <IoLogoAndroid/>, path: 'item 2 path'},
};
And then just call it directly (remove the ...)
<a key={menu} href={this.props.menus[menu].path}
onClick={this.changeMenu.bind(this, menu)}>
{this.props.menus[menu].icon}
{this.props.menus[menu].buttonText}
</a>
Alternatively you could just call React.createElement if you don't want to tag it in your object definition.
<a key={menu} href={this.props.menus[menu].path}
onClick={this.changeMenu.bind(this, menu)}>
{React.createElement(this.props.menus[menu].icon)}
{this.props.menus[menu].buttonText}
</a>
Here is a sample showing the 2 implementations https://codesandbox.io/s/pmvyyo33o0
I was just working with a similar project, and I managed to make it work, with a syntax like this
I here have an array of objects (like yours)
links: [
{
name: 'Frontend',
link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-web',
icon: <FaCode size={40} />,
id: 1
},
{
name: 'Backend',
link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-backend',
icon: <FaCogs size={40}/>,
id: 2
},
{
name: 'Mobile',
link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-app',
icon: <FaMobile size={40} />,
id: 3
}]
I then render my component by mapping, where I pass in the entire object as a prop
const projects = this.state.projects.map((project, i) => {
return(
<Project key={`Project key: ${i}`} project={project} />
)
})
I then use objectdestructing to get the prop
const { logo } = this.props.project
then it can just be displayed
//in my case I use antd framework, so I pass the FAIcon component in as a prop
<Meta
avatar={logo}
title={title}
description={description}
/>
I suppose you could do the same thing, by just passing the entire menu object in as a prop, and then accessing the icon?
You need to change the following:
icon: <IoLogoAndroid />
And in the code (remove the spread operator):
this.props.menus[menu].icon
Also, a few refactoring suggestions.
Do you really need to have an object of objects? Why not an array of objects, where every item has a "name" prop? It will be easier to iterate through, as you can access props directly from map, unlike with object keys.
You are creating a button list, so you should have ul and li tags aswell.
Consider passing only a reference to onClick such as:
onClick={this.changeMenu}
If you need to pass data around, you should use dataset for that. Pass a name/path then find it inside the change handler to avoid rebinding inside every re-render.
Refactored suggestion with an array of objects
changeMenu = e => {
const { menus } = this.props;
const { menuName } = e.target.dataset;
const menu = menus.find(menu => menu.name === menuName);
// Do something with menu
return;
};
renderMenu() {
return (
<ul>
{this.props.menus.map(menu => (
<li key={menu.name} style={{ listStyle: "none" }}>
<a
data-menu-name={menu.name}
href={menu.path}
onClick={this.changeMenu}
>
{menu.icon}
{menu.buttonText}
</a>
</li>
))}
</ul>
);
}

Categories

Resources