So I am trying to use onMouseOver on my React component like this
<CookieDetails
key={cookie.id}
name={cookie.name}
cost={cookie.cost}
value={cookie.cost}
numOwned={purchasedItems[cookie.id]}
onMouseOver={event => {
console.log('cookies');
if (numCookies < cookie.cost) {
alert('Not Enough Cookies');
return;
}
setPurchasedItems({
...purchasedItems,
[cookie.id]: purchasedItems[cookie.id] + 1,
});
setNumCookies(numCookies - cookie.cost);
console.log('purchased', purchasedItems[cookie.id], numCookies);
}}
/>;
and here is whats inside my component for now
import React from 'react';
const CookieDetails = ({ name, cost, value, numOwned }) => {
return (
<React.Fragment>
<div className="cookie-details-wrapper">
<h3>{name}</h3>
<p>Cost:{cost}</p>
<p>Value:{value}</p>
<p>Owned:{numOwned}</p>
</div>
</React.Fragment>
);
};
export default CookieDetails;
However when I mouse over my component, nothing happens. Doesn't event console log cookies. Any help would be appreciated
CookieDetails isn't a DOM element (so it doesn't have native handling for events) it is a component you wrote. You don't do anything with the onMouseOver prop.
You need to read that prop and attach it to a DOM element (like the div).
const CookieDetails = ({ name, cost, value, numOwned, onMouseOver }) => {
return (
<React.Fragment>
<div onMouseOver={onMouseOver} className="cookie-details-wrapper">
So here onMouseOver is a prop that you are passing to children. You need to trigger onMouseOver on children
<CookieDetails
key={cookie.id}
name={cookie.name}
cost={cookie.cost}
value={cookie.cost}
numOwned={purchasedItems[cookie.id]}
onMouseOver={event => {
console.log('cookies');
if (numCookies < cookie.cost) {
alert('Not Enough Cookies');
return;
}
setPurchasedItems({
...purchasedItems,
[cookie.id]: purchasedItems[cookie.id] + 1,
});
setNumCookies(numCookies - cookie.cost);
console.log('purchased', purchasedItems[cookie.id], numCookies);
}}
/>;
import React from 'react';
const CookieDetails = ({ name, cost, value, numOwned, onMouseOver }) => {
return (
<React.Fragment>
<div className="cookie-details-wrapper" onMouseOver={onMouseOver}>
<h3>{name}</h3>
<p>Cost:{cost}</p>
<p>Value:{value}</p>
<p>Owned:{numOwned}</p>
</div>
</React.Fragment>
);
};
export default CookieDetails;
Related
When trying to pass a component as a prop of another component, everything works fine.
But if i want instead pass a Component and handle its css classes inside the children, I'm currently lost.
In my mind im trying to achieve something similar to this:
import Navbar from 'what/ever/path/Navbar/is/in/Navbar.js';
export default function ParentComponent {
return(
<Navbar NavIcon={<MyIcon/>} />
)
}
.... Imports etc...
export default function Navbar(props) {
const {NavIcon} = props;
return(
<Navigation>
// Now use the Prop as a Component and pass default classNames to it.
// So that we don't need to wrap everything inside a span / div etc.
<NavIcon className="AddCustomStylesAlwaysHere" />
</Navigation>
)
}
Two approaches come to my mind:
Passing a component
Just pass the component and let the parent take care of its instantiation. This way, the only changes you need is making sure <MyIcon /> accepts a className prop:
const MyIcon = ({ className }) => {
return <div className={className} />
};
const Navbar = ({ NavIcon }) => {
return (
<Navigation>
<NavIcon className="AddCustomStylesAlwaysHere" />
</Navigation>
);
};
<Navbar NavIcon={MyIcon} />
Passing an element instance
This way, you take care of instantiating the component and the parent just renders it. In this case, you have to use React utilities to modify existing elements (https://reactjs.org/docs/react-api.html#cloneelement):
const MyIcon = ({ className }) => {
return <div className={className} />
};
const Navbar = ({ NavIcon }) => {
return (
<Navigation>
{React.cloneElement(NavIcon, { className: 'AddCustomStylesAlwaysHere' })}
</Navigation>
);
};
<Navbar NavIcon={<MyIcon />} />
You can use React.Children.map in combination with React.cloneElement:
{
React.Children.map(children, ( child, idx ) => {
return React.cloneElement(child, { className: 'additional-classnames' })
})
}
I have a Grid with 3*3 squares.
When a click on a square , we change the background color to green.
So, I tried to put the all the states in the parent GridContainer.
state = {
gridCells: []
};
This will hold the indices that are clicked.
GridContainer nests Grid and Grid nests Square.
render() {
return (
<div>
<Grid action={this.handler} />
<button>Reset Clicks</button>
</div>
);
}
Here is my current implementation.
Now how do I clear the background cells when I reset clicks and make the background back to white again?
function Square(props) {
const liClickHandler = event => {
event.target.classList.add("highlight");
props.clickAction();
};
return <li onClick={e => liClickHandler(e)} />;
}
function Grid(props) {
const gridHandler = index => {
props.action(index);
};
return (
<ul>
{Array.from(new Array(9)).map((item, index) => (
<Square key={index} clickAction={() => gridHandler(index)} />
))}
</ul>
);
}
class GridContainer extends React.Component {
state = {
gridCells: []
};
handler = index => {
let temp = [...this.state.gridCells];
temp.push(index + 1);
this.setState({
gridCells: temp
});
};
render() {
return (
<div>
<Grid action={this.handler} />
<button>Reset Clicks</button>
</div>
);
}
}
So when I click a Sqaure , it calls a method clickAction that calls handler
that updates the state and we have an array which indices were clicked in order.
How do I implement Reset clicks that updates the background of those Sqaures back to white ? How do I let know my child know.
Am I maintaining the state wrong?
Sandbox link : https://codesandbox.io/s/3-x-3-grids-s0b43?file=/src/index.js:640-1563
I'd advise to rethink the way how your components are structured.
Each component should be independent unit with it's own logic and state (if needed of course). I'm saying if needed for state, cause ideally components should be stateless.
There are several problems with Square class:
It adds class via event.target, which is not react way to go. React works with virtual DOM and has it's own set of methods to interact with html. Working with DOM directly - will bite your later, starting from writing tests for your code in the future.
It does not contain incoming information whether it should be highlighted or not
Both these problems result in fact that you cannot reset presentation of your squares easily.
I've updated your sample: https://codesandbox.io/s/3-x-3-grids-uflhr?file=/src/index.js
It's still not ideal, but you can notice that gridCells is passed from top via props. And then each square gets own props param. This allows state to come through the flow and let squares rerender with updated class.
In react you should think the "react" way:
pass the necessary state down through the props
pass down the callbacks so that children can update the parent state
Here is corrected version of the demo:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Square(props) {
return (
<li onClick={props.onClick} className={props.active ? "highlight" : ""} />
);
}
function Grid(props) {
let squares = [];
for (let i = 0; i < 9; i++) {
squares.push(
<Square
key={i}
onClick={() => props.onCellClick(i)}
active={props.cells[i]}
/>
);
}
return <ul>{squares}</ul>;
}
class GridContainer extends React.Component {
state = {
gridCells: []
};
onCellClick = index => {
this.setState(prevState => {
const newCells = [...prevState.gridCells];
newCells[index] = true;
return {
gridCells: newCells
};
});
};
render() {
return (
<div>
<Grid cells={this.state.gridCells} onCellClick={this.onCellClick} />
<button
onClick={() => {
let that = this; //we could bind the callback aswell
that.setState(() => ({ gridCells: [] }));
}}
>
Reset Clicks
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<GridContainer />, rootElement);
Probably a simple question and have so many people answered here but in this scenario, I cannot figure out why it doesn't work ...
In the parent I have
updateAccountNumber = value => {
console.log(value);
};
<Child updateAccountNumber={this.updateAccountNumber} />
In the child I have
<ListItem
button
key={relatedSub.office + relatedSub.account}
onClick={() =>
this.props.updateAccountNumber(
relatedSub.office + relatedSub.account
)
}
Even if I do parent like this, still no help..
<Child updateAccountNumber={() => this.updateAccountNumber()} />
if I have the below child item, then when I click on the menu that runs the child items, the component calls itself as many items as there are...
onClick={this.props.updateAccountNumber(
relatedSub.office + relatedSub.account
)}
It won't even run the below code, simple code, I can't see why it wouldn't kick of the handleClick event...
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const handleClick = () => {
debugger;
alert("sda");
console.log("bbb");
};
export default class RelatedSubAccounts extends Component {
Links = () => {
if (this.props.RelatedSubAccounts) {
let RelatedSubArray = this.props.RelatedSubAccounts;
let source = RelatedSubArray.map(relatedSub => (
<ListItem
button
onClick={handleClick}
key={relatedSub.office + relatedSub.account}
className={
relatedSub.office + relatedSub.account !== this.props.OfficeAccount
? ""
: "CurrentRelatedSub"
}
>
<ListItemText primary={relatedSub.office + relatedSub.account} />
</ListItem>
));
return (
<div id="RelatedSubLinks">
<List>{source}</List>
</div>
);
} else {
return "";
}
};
render() {
return this.Links();
}
}
Please ask if any other related code is missing, and I can share.
I was able to get an example that works with the code you shared by using RelatedSubAccounts like this:
<RelatedSubAccounts RelatedSubAccounts={[{ office: 1, account: 2 }]} />
Code Sandbox Example
I see a few things that stand out as potentially confusing. I will point them out in code below with comments:
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const handleClick = () => {
debugger;
alert("RelatedSubAccounts clicked");
console.log("bbb");
};
export default class RelatedSubAccounts extends Component {
// Capitalization like this in react normally indicates a component
Links = () => {
/*
Having a prop that is the same name as the component and capitalized is confusing
In general, props aren't capitalized like the component unless you are passing a component as a prop
*/
if (this.props.RelatedSubAccounts) {
// Again, capitalization on RelatedSubArray hints that this is a component when it really isn't
let RelatedSubArray = this.props.RelatedSubAccounts;
let source = RelatedSubArray.map(relatedSub => (
<ListItem
button
onClick={handleClick}
key={relatedSub.office + relatedSub.account}
className={
relatedSub.office + relatedSub.account !== this.props.OfficeAccount
? ""
: "CurrentRelatedSub"
}
>
<ListItemText primary={relatedSub.office + relatedSub.account} />
</ListItem>
));
return (
<div id="RelatedSubLinks">
<List>{source}</List>
</div>
);
} else {
return "";
}
};
render() {
return this.Links();
}
}
This is going to be strangest solution but might give you a lesson or two.. I found the cause of the issue here.
So I have a menu like this
When you click on that arrow to open up the menu, it becomes active and when you click away onBlur kicks in and that menu goes away.. (menu created used react-select Creatable)
DropdownIndicator = props => {
return (
<div
onBlur={() => {
this.setState({ Focused: false, RelatedSub: false });
}}
so I had to update it to below:
onBlur={() => {
this.setState({ Focused: false });
setTimeout(() => {
this.setState({ RelatedSub: false });
}, 100);
}}
In my JSX, I'm, mapping through an array of objects (imported from a local JS file) to display a set of icons with a key, id and alt tag.
I use hooks to set a state to an empty string. I want to use an onClick event (passed to the HeroIcons component) to replace this state with the id of the clicked icon (that id is a string). Here's the code:
import React, { useState } from "react";
import HeroImages from "../images/HeroImages";
import HeroIcons from "../components/HeroIcons";
import HeroShowcase from "../components/HeroShowcase";
const Heroes = () => {
const [hero, currentHero] = useState("");
const setCurrentHero = e => {
currentHero(e.target.id);
console.log(hero);
};
return (
<div className="row">
<div className="col-heroes">
<ul className="hero-list">
{/* map function below */}
{HeroImages.map(({ id, src, altTag }) => (
<HeroIcons
key={id}
id={id}
src={src}
altTag={altTag}
setCurrentHero={setCurrentHero}
/>
))}
</ul>
</div>
<div className="col-showcase">
<HeroShowcase />
</div>
</div>
);
};
export default Heroes;
Inside the heroIcons component:
import React from "react";
const HeroIcons = props => {
return (
<li key={props.id} id={props.id} onClick={props.setCurrentHero}>
<img src={props.src} alt={props.altTag} />
</li>
);
};
export default HeroIcons;
When clicking on an icon (created by the map function), the id isn't logged to the console. However, when I furiously click it many times, sometimes an id DOES get logged. This gives me a hint that this click event could be causing the map function to re-run and prevent the normal console log
How could I fix this this issue?
First you have to use e.currentTarget.id instead of e.target.id so you get the id of current image.
const setCurrentHero = e => {
currentHero(e.currentTarget.id);
console.log(hero);
};
Second useState Hook needs you to handle the callback to use log the value of the current state, while it doesn't accept the callback like setState.
You can use useEffect but It would better if you use the value of e.currentTarget.id;
This is because you hero is not updated at the time of console so you need to use useEffect hook when that value is updated
const setCurrentHero = e => {
currentHero(e.target.id);
console.log(hero);
};
useEffect(() => {
console.log('Hero', hero);
}, [hero]);
why not just set the value in the render:
<HeroIcons
key={id}
id={id}
src={src}
altTag={altTag}
setCurrentHero={setCurrentHero(id)}
/>
So I've set up a file to setState() for onBlur and onFocus in the SocialPost.js file. But when I onClick a <div> in the SocialPostList.js (the parent) where it activates the parameterClicked() function in the SocialPost.js file, the <input> in SocialPost.js becomes blurred.
How do I make it so that the <button> onClick in SocialPostList.js does not take the focus() from the <input> in SocialPost.js?
I've tried e.preventDefault() and e.stopPropagation() without success. The files are below, any help would be appreciated!!!
SocialPostList.js
import React, { Component } from 'react'
import { graphql, gql } from 'react-apollo'
import SocialPost from './SocialPost'
class SocialPostList extends Component {
render() {
const PostListArray = () => {
return(
<div onClick={(e) => {e.preventDefault(); e.stopPropagation()}}>
{this.props.allParametersQuery.allParameters.map((parameter, index) => (
<div
key={index}
onClick={(e) => {e.preventDefault();e.stopPropagation();this.child.parameterClicked(parameter.param, parameter.id)}}
>{'{{' + parameter.param + '}}'}</div>
))}
</div>)
}
return (
<div>
...
<PostListArray />
{this.props.allSocialPostsQuery.allSocialPosts.map((socialPost, index) => (
<SocialPost
ref={instance => {this.child = instance}}
key={socialPost.id}
socialPost={socialPost}
index={index}
deleteSocialPost={this._handleDeleteSocialPost}
updateSocialPost={this._handleUpdateSocialPost}
allParametersQuery={this.props.allParametersQuery}/>
))}
...
</div>
)
}
}
const ALL_SOCIAL_POSTS_QUERY = gql`
query AllSocialPostsQuery {
allSocialPosts {
id
default
message
}}`
export default graphql(ALL_SOCIAL_POSTS_QUERY, {name: 'allSocialPostsQuery'})(SocialPostList)
SocialPost.js
import React, { Component } from 'react'
class SocialPost extends Component {
constructor(props) {
super(props)
this.state = {
message: this.props.socialPost.message,
focus: false
}
this._onBlur = this._onBlur.bind(this)
this._onFocus = this._onFocus.bind(this)
}
_onBlur() {
setTimeout(() => {
if (this.state.focus) {
this.setState({ focus: false });
}}, 0);
}
_onFocus() {
if (!this.state.focus) {
this.setState({ focus: true });
}
}
render() {
return (
<div className='socialpostbox mb1'>
<div className='flex'>
<input
onFocus={this._onFocus}
onBlur={this._onBlur}
type='text'
value={this.state.message}
onChange={(e) => { this.setState({ message: e.target.value})}}/>
</div>
</div>
)
}
parameterClicked = (parameterParam) =>{
if (!this.state.focus) return
let message = this.state.message
let newMessage = message.concat(' ' + parameterParam)
this.setState({ message: newMessage })
}
export default SocialPost
Well, I don't think that's a React thing. It appears the blur event fires before the onClick, so the latter cannot prevent the former, and I'd expect event.stopPropagation to stop events bubbling from child to parent, not the other way around. In other words, I don't know how to stop it.
In all fairness this behaviour is expected - clicking somewhere else makes you lose focus. That said, here and elsewhere a solution is presented where you set up a flag on mouse down. Then, when blur fires, if it encounters the 'click flag' it may abstain from producing effects and may even refocus back.
If you choose to refocus, it is trivial to save a reference to the button or input, or querySelecting it (it's not too late or anything like that). Just be cautious that it is all too easy to set focus traps or mess up navigation for screen readers when you mix focus and javascript.