Using onClick to call the same function works for one thing and not another? - javascript

I'm creating an application where a user can search for a device with a search bar, or look through a nested menu. When the user finds the device they want, they click on the green plus to add it to their "bag". For some reason the addDevice function I've written only works for the search function, and not when it's called in the menu. It seems to partially work, but not correctly. Does anyone know what in my code could be causing this? Please see the images below for more details.
I'm also making two different API calls, one for the search bar (called in its own function) and one for the menu (called in componentDidMount). Could this possibly be what's causing the error?
I won't include every line of code because it's a lot, but please let me know if you'd like to see anything else.
class App extends React.Component {
state = {
devices: [],
bag: [],
objectKeys: null,
tempKeys: []
};
this is the function that gets called by the onClick's
addDevice = (e, deviceTitle) => {
const array = Array.from(this.state.bag || []);
if (array.indexOf(deviceTitle) === -1) {
array.push(deviceTitle);
} else {
return;
}
localStorage.setItem("list", JSON.stringify(array));
this.setState({
bag: array
});
};
This is the function that doesn't seem to work when addDevice is called
makeMenuLayer = layer => {
const { objectKeys } = this.state;
if (layer == null) {
return null;
}
const layerKeys = Object.entries(layer).map(([key, value]) => {
{/*If value has children, display an arrow, if not, do nothing*/}
var arrow = Object.keys(value).length ? (
<i
className="fas fa-angle-right"
style={{ cursor: "pointer", color: "gray" }}
/>
) : (
""
);
{/*If value has no children, display an plus sign, if not, do nothing*/}
var plus =
Object.keys(value).length === 0 ? (
<i
className="fas fa-plus"
style={{ cursor: "pointer", color: "green" }}
onClick={e => this.addDevice(e, this.value)}
/>
) : (
""
);
return (
<ul key={key}>
<div onClick={() => this.handleShowMore(key)}>
{key} {arrow} {plus}
</div>
{objectKeys[key] && this.makeMenuLayer(value)}
</ul>
);
});
return <div>{layerKeys}</div>;
};
this is where the addDevice that does work gets called
render () {
return(
<div className="search-results">
{(this.state.devices || []).map(device => (
<a key={device.title}>
<li>
{device.title}{" "}
<i
className="fas fa-plus plus input"
style={{ cursor: "pointer", color: "green" }}
onClick={e => this.addDevice(e, device.title)}
/>
</li>
</a>
))}
</div>
)}
This is what it looks like when devices are added from the search (works fine)
This is what happens when I try to add "Necktie" from the menu. It doesn't let me add anything else after that

In this line onClick={e => this.addDevice(e, this.value)}
The value of this points to the class itself. Thus, this.value is undefined, It's not not allowing you to add anything more because undefined is already in the array and undefined === undefined is actually true.
To fix this, you need to pass the correct value of the device title.

Related

React how to call function depends on specific element id

how I can tigress an function depends on id of an element. Right now all elements getting clicked if I click on any single element. how to prevent to show all element ? here is my code
const[showsubcat,setShowSubCat] = useState(false)
let subcategory=(()=>{
setShowSubCat(prev=>!prev)
})
my jsx
{data.map((data)=>{
return(
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >{data.main_category}</li>
{showsubcat &&
<li><i class="las la-angle-right" id="sub_category"></i> {data.sub_category}</li>
}
</>
)
see the screenshot. I am clicking on single items but it's showing all items.
Every li should have it own state
so it's either you create states based on number of li if they're just 2 elements max! but it's ugly and when you want to add more li it's gonna be a mess
so you just create a component defining the ListItem and every component has it own state.
function ListItem({data}) {
const[showsubcat,setShowSubCat] = useState(false)
const subcategory= ()=> setShowSubCat(prev=>!prev)
return (
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >
{data.main_category}
</li>
{showsubcat &&
<li>
<i class="las la-angle-right" id="sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
and you use it in the list component like this
data.map((datum, index) => <ListItem key={index} data={datum} />
EDIT AFTER THE POST UPDATE (misunderstanding)
the list item (or the block containing the li and the helper text) should be an independant component to manage it own state
function PostAds(data) => {
return (
<>
{
data.map((data, index) => <ListItem key={index} data {data}/>
}
</>
)
}
function ListItem({data}) {
const [showsubcat, setShowSubCat] = useState(false)
const subcategory = () => setShowSubCat(prev => !prev)
return (
<>
<li
class="list-group-item"
id={data.id}
onClick={subcategory}>
{data.main_category}
</li>
{
showsubcat &&
<li >
<i class = "las la-angle-right" id = "sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
The reason this is happening is because you are using the same variable showsubcat to check if the category was clicked or not.
A proper way to do this would be by either making showsubcat as an array that holds ids of those categories that were clicked like:
const[showsubcat,setShowSubCat] = useState([])
let subcategory=((categoryId)=>
showsubcat.includes(categoryId) ?
setShowSubCat(showsubcat.filter(el => el !== categoryId)) :
setShowSubCat([...showsubcat, categoryId]));
and then while mapping the data:
{data.map((category)=>
(
<>
<li class="list-group-item" id={category.id} key={category.id}
onClick={() => subcategory(category.id)}>
{category.main_category}
</li>
{showsubcat.includes(category.id) &&
<li>
<i class="las la-angle-right"
id="sub_category" key={`subCategory${category.id}`} />
{category.sub_category}
</li>
}
</>
)
}
The other method would be to add a new key in your data array as selectedCategory and change its value to true/false based on the click, but this is a bit lengthy, let me know if you still want to know that process.
Also, accept the answer if it helps!

map in react not displaying data with { } (curly brackets)

So I am trying to return the data from API by map and when I am using ( ) these brackets I am getting the data when I use { } to put if statement, I am getting nothing on my web page but still getting the data in console
const Addtocart = () => {
const data = useSelector((state) => state);
console.log("products", data.productData);
const dispatch = useDispatch();
useEffect(() => {
dispatch(productList());
}, []);
return (
<div id="addtocart-info">
<div className="products">
{data.productData.map((item) => { // here this bracket
if (item.id % 2 === 0 || item.id === 0) {
<div key={item.id} className="product-item">
<img src={item.photo} alt="" />
<div>Name : {item.name} </div>
<div>Color : {item.color} </div>
<button onClick={() => dispatch(addToCart(item))}>
ADD to Cart
</button>
</div>;
console.warn(item.id);
} else {
console.log(item.id);
}
})}
</div>
</div>
);
};
export default Addtocart;
Is there any way to put if statement with () or make this work
You are not getting anything because when u use {} you have to use a return keyword, but when you are using () you don't have to use a return keyword because the whole code inside this is considered as a single piece of code even if it's distributed in multiple lines
so change your code to ,
{data.productData.map((item) => { // here this bracket
if (item.id % 2 === 0 || item.id === 0) {
return (
<div key={item.id} className="product-item">
<img src={item.photo} alt="" />
<div>Name : {item.name} </div>
<div>Color : {item.color} </div>
<button onClick={() => dispatch(addToCart(item))}>
ADD to Cart
</button>
</div>
)
} else {
console.log(item.id);
}
})}
If you use curly brackets you also need to use a return statement. Basically if you don't use curly brackets in an arrow function the statement is returned automatically.
Example:
let x = someArray.map(x => x*2); // returns a new array with the expression applied
let x = someArray.map(x => {return x * 2}) // use the return here

Expand/Collapse all data

I am making a Accordion and when we click each individual item then its opening or closing well.
Now I have implemented expand all or collapse all option to that to make all the accordions expand/collapse.
Accordion.js
const accordionArray = [
{ heading: "Heading 1", text: "Text for Heading 1" },
{ heading: "Heading 2", text: "Text for Heading 2" },
{ heading: "Heading 3", text: "Text for Heading 3" }
];
.
.
.
{accordionArray.map((item, index) => (
<div key={index}>
<Accordion>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text expandAll={expandAll}>
<p className="text">{item.text}</p>
</Text>
</Accordion>
</div>
))}
And text.js is a file where I am making the action to open any particular content of the accordion and the code as follows,
import React from "react";
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
{this.props.expandAll ? (
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
) : (
<div className={`content ${this.props.text ? "open" : ""}`}>
{this.props.text ? this.props.children : ""}
{this.props.text
? this.props.render && this.props.render(this.props.text)
: ""}
</div>
)}
</div>
);
}
}
export default Text;
Here via this.props.expandAll I am getting the value whether the expandAll is true or false. If it is true then all accordion will get the class className={`content open`} so all will gets opened.
Problem:
The open class is applied but the inside text content is not rendered.
So this line doesn't work,
{this.props.render && this.props.render(this.props.text)}
Requirement:
If expand all/collapse all button is clicked then all the accordions should gets opened/closed respectively.
This should work irrespective of previously opened/closed accordion.. So if Expand all then it should open all the accordion or else needs to close all accordion even though it was opened/closed previously.
Links:
This is the link of the file https://codesandbox.io/s/react-accordion-forked-sm5fw?file=/src/GetAccordion.js where the props are actually gets passed down.
Edit:
If I use {this.props.children} then every accordion gets opened.. No issues.
But if I open any accordion manually on click over particular item then If i click expand all then its expanded(expected) but If I click back Collapse all option then not all the accordions are closed.. The ones which we opened previously are still in open state.. But expected behavior here is that everything should gets closed.
In your file text.js
at line number 9. please replace the previous code by:
{this.props.children}
Tried in the sandbox and worked for me.
///
cant add a comment so editing the answer itself.
Accordian.js contains your hook expandAll and the heading boolean is already happening GetAccordian.js.
I suggest moving the expand all to GetAccordian.js so that you can control both values.
in this case this.props.render is not a function and this.props.text is undefined, try replacing this line
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
by this:
<div className={`content open`}>
{this.props.children}
</div>
EDIT: //
Other solution is to pass the expandAll property to the Accordion component
<Accordion expandAll={expandAll}>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text>
<p className="text">{item.text}</p>
</Text>
</Accordion>
then in getAccordion.js
onShow = (i) => {
this.setState({
active: this.props.expandAll ? -1: i,
reserve: this.props.expandAll ? -1: i
});
if (this.state.reserve === i) {
this.setState({
active: -1,
reserve: -1
});
}
};
render() {
const children = React.Children.map(this.props.children, (child, i) => {
return React.cloneElement(child, {
heading: this.props.expandAll || this.state.active === i,
text: this.props.expandAll || this.state.active + stage === i,
onShow: () => this.onShow(i)
});
});
return <div className="accordion">{children}</div>;
}
};
Building off of #lissettdm answer, it's not clear to me why getAccordion and accordion are two separate entities. You might have a very valid reason for the separation, but the fact that the two components' states are interdependent hints that they might be better implemented as one component.
Accordion now controls the state of it's children directly, as before, but without using getAccordion. Toggling expandAll now resets the states of the individual items as well.
const NormalAccordion = () => {
const accordionArray = [ //... your data ];
const [state, setState] = useState({
expandAll: false,
...accordionArray.map(item => false),
});
const handleExpandAll = () => {
setState((prevState) => ({
expandAll: !prevState.expandAll,
...accordionArray.map(item => !prevState.expandAll),
}));
};
const handleTextExpand = (id) => {
setState((prevState) => ({
...prevState,
[id]: !prevState[id]
}));
};
return (
<>
<div className="w-full text-right">
<button onClick={handleExpandAll}>
{state.expandAll ? `Collapse All` : `Expand All`}
</button>
</div>
<br />
{accordionArray.map((item, index) => (
<div key={index}>
<div className="accordion">
<Heading handleTextExpand={handleTextExpand} id={index}>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text shouldExpand={state[index]}>
<p className="text">{item.text}</p>
</Text>
</div>
</div>
))}
</>
);
};
Heading passes back the index so the parent component knows which item to turn off.
class Heading extends React.Component {
handleExpand = () => {
this.props.handleTextExpand(this.props.id);
};
render() {
return (
<div
style={ //... your styles}
onClick={this.handleExpand}
>
{this.props.children}
</div>
);
}
}
Text only cares about one prop to determine if it should display the expand content.
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
<div
className={`content ${this.props.shouldExpand ? "open" : ""}`}
>
{this.props.shouldExpand ? this.props.children : ""}
</div>
</div>
);
}
}

How to fix error of hiding and showing <div> in React

I am working on a project and i want to display a hidden <div> below another <div> element using an event handler but when i click the icon that is meant to display the div, the whole page becomes blank
This is image I want:
This is what i get
I have tried to check through the internet for some places where i could get the solution. Well i found something similar to what i had done but the error still happens for me.
class PostItTeaser extends Component {
state = {
postIt: false,
moreIt: false,
}
togglePostIt = e => {
e ? e.preventDefault() : null
this.setState({ postIt: !this.state.postIt })
}
_toggle = e => {
e ? e.preventDefault() : null
this.setState({
moreIt: !this.state.moreIt,
})
}
Child = () => <div className="modal">Hello, World!</div>
render() {
let { postIt } = this.state
let { moreIt } = this.state
let {
type,
group,
disabled,
session: { id, username },
} = this.props
return (
<div>
<div
className="post_it inst"
style={{ marginBottom: type == 'group' && 10 }}
>
<img src={`/users/${id}/avatar.jpg`} alt="Your avatar" />
<div className="post_teaser">
<span
className="p_whats_new"
onClick={disabled ? null : this.togglePostIt}
>
What's new with you, #{username}? #cool
</span>
<span className="m_m_exp" data-tip="More" onClick={this._toggle}>
<MaterialIcon icon="expand_more" />
</span>
</div>
</div>
{moreIt && <Child />}
{postIt && (
<PostIt back={this.togglePostIt} type={type} group={group} />
)}
</div>
)
}
}
From skimming through the code I believe you need to bind the scope, since the function you're calling is using this.setState, it needs this to be the react component, not the event you're listening to:
onClick={this._toggle.bind(this)}
You can also bind the functions scope in the constructor. Or, a less memory performant & ugly way:
onClick={() => { this._toggle(); } }

Connect two dropdown filters to one search button

I have two dropdowns that are filtering, but they filter as you drop them down and make selections. I have a search button that I would like to hook them both to. So you just saw a change in results once, after you pressed the button. I think i have all the logic i need here But im not sure exactly how to hook up the button
note: i know i have alot of logic in the render, but im just trying to make it work first
So far this is what I have:
constructor(props) {
super(props);
this.state = {
developers: [],
filterCountry: "All locations",
filterSkills: "All skills"
};
}
componentDidMount() {
fetch('API')
.then(features => features.json())
.then(developers => {
this.setState({ developers })
})
}
filterCountry(e){
this.setState({filterCountry: e })
}
filterSkills(e){
this.setState({filterSkills: e })
}
render() {
let developers = this.state.developers.features
if (!developers ){
return null
}
if (this.state.filterCountry && this.state.filterSkills) {
developers = developers.filter( developer => {
return this.state.filterCountry === 'All locations' ||
developer.properties.continent.includes(this.state.filterCountry)
});
developers = developers.filter( developer => {
return this.state.filterSkills === 'All skills' ||
developer.properties.skills.includes(this.state.filterSkills)
});
}
return (
<div>
<div>
<ControlSelect
onChange={this.filterCountry.bind(this)}
value={this.state.filterCountry}
options={options_dd1}
/>
</div>
<div className="inline-block mr24">
<ControlSelect
onChange={this.filterSkills.bind(this)}
value={this.state.filterSkills}
options={options_dd2}
/>
</div>
<button>Search</button>
</div>
<div>
<div>
{developers.map(developer => {
return (
<div key={developer.id}">
{developer.properties.name}
{developer.properties.description}
{developer.properties.skills}
</div>
</div>
</div>
)}
)}
)
any help would be greatly appreciated
The main problem with what you have is that once the filtering is done, there is no way to get the original list of developers back. You can create an 'original list' or developers and a new filteredList, which could be actually used by the render method to show data.
Basically, in your initial render, the developers key in your state is the default loaded from fetch and will get rendered in its entirety. Once you click the button, the doSearch method will modify the state and remove developers. This will cause the render to be called and show the new filtered list.
Otherwise, there's a few minor things things taht I have commented below.
constructor(props) {
super(props);
this.state = {
developers: [],
filterCountry: "All locations",
filterSkills: "All skills"
};
}
componentDidMount() {
fetch('API')
.then(features => features.json())
.then(developers => {
this.setState({ developers })
})
}
filterCountry(e){
this.setState({filterCountry: e })
}
filterSkills(e){
this.setState({filterSkills: e })
}
doSearch() {
// Create copy of state (you had a `.filtered` in your code, which doesn't make sense as developers is an array so it will have no `filtered` property unless you modified the prototype
let developers = this.state.developers.slice()
// This if block is pointless, because you start with a default state in the constructor (so unless your ControlSelect have a falsy option, this will always evaluate to `true`)
if (this.state.filterCountry && this.state.filterSkills) {
// THis will match EITHER country OR skills. You can change to && if wanted.
developers = developers.filter( developer => {
return this.state.filterCountry === 'All locations' ||
developer.properties.continent.includes(this.state.filterCountry) || this.state.filterSkills === 'All skills'
|| developer.properties.skills.includes(this.state.filterSkills)
});
this.setState({ developers })
}
}
render() {
return (
<div>
<div>
<ControlSelect
onChange={this.filterCountry.bind(this)}
value={this.state.filterCountry}
options={options_dd1}
value={this.state.filterCountry}
/>
</div>
<div className="inline-block mr24">
<ControlSelect
onChange={this.filterSkills.bind(this)}
value={this.state.filterSkills}
options={options_dd2}
value={this.state.filterSkills}
/>
</div>
<button onClick={this.doSearch.bind(this)}>Search</button>
</div>
<div>
<div>
{/* Now the developers contains stuff that was filtered in search */}
{this.state.developers.map(developer => {
return (
<div key={developer.id}>
{developer.properties.name}
{developer.properties.description}
{developer.properties.skills}
</div>
</div>
</div>
)}
)}
)

Categories

Resources