Calling onClick outside of a component acting globally - reactjs

so i have this definition here:
const generateList = props => {
...
item = <ListItemEl
icon='directions'
text={'Directions'}
onClick={props.openGoogleMaps(coords)}
/>
...
}
const SideList = props => {
const listItems = generateList(props)
return (
<List>
{listItems}
</List>
)
}
however the onClick action is not bound to that specific ListItemEl.
The onClick is called way up the component tree that this ListItemEl belongs to.
openGoogleMaps is successfully called but upon clicking anything not just that ListItemEl.
here is the parent component when openGoogleMaps is passed.
class index extends Component {
openGoogleMaps(coords) {
const center = PolygonCenter(coords)
window.open(
"https://www.google.com/maps/dir/?api=1&destination=" +
center.coordinates[0] +
"," +
center.coordinates[1]
)
}
...
{context.state.drawerOpen ? <SideList data={this.props.data} openGoogleMaps={this.openGoogleMaps}/> : null}
}
i'm guessing this has something to do with binding openGoogleMaps to that specific ListItemEl but when i try to do that with 'this.props.openGoogleMaps(coords).bind(this)'
or something similar the onClick no longer works anywhere.
also im guessing this has to do with a gap in my knowledge concerning defining 'const' vs a component. previously when i've used bind i've used 'this' in front of the function im binding but since the ListItemEl is defined in a const i can't use
'this'. please and thank you
also here is the branch, repo and file where the problem is occuring https://github.com/CodeForCary/cary-connects-web/blob/googlemaps/src/components/Drawer/SideList.js

Related

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

Deleting Note From Array

Current I am working on a note app using React.Js and I had the functionality for saving notes but then I decided to add the functionality to remove the note when it is clicked. I wrote some code however, now it won't work. I can't add notes display them. Usually when you click on the button is was suppose to add the note which it was doing before I tried adding the deleting the note functionality. There is also no error in anywhere. I have been trying to solve this the past few days. Here is my code:
The problem is that the id is undefined in your NoteItem component. You are only passing the title to the component. You should pass the note object and then you can have access to the id and title. Suggested changes below:
NotesList
const NotesList = (props) => {
return props.data.map((note) => {
return (
<NoteItem
note={note}
key={note.id}
onDelete={props.onDeleteItem}
>
</NoteItem>
);
})
}
and change NoteItem component to access note as an object. (You could also pass title/id individually as props as well.
NoteItem:
const NoteItem = props => {
const deleteHandler = () => {
props.onDelete(props.note.id);
}
return (
<h3 onClick={deleteHandler}>{props.note.title}: {props.note.id}</h3>
)
}

Add class on parent div - React TypeScript

I'm really struggling to see how to do this. I want to add a class on click somewhere in one of the parent element which is not available in my code.
I think it would be something like this with jQuery:
$('.child').parent('parent').addClass('the-class')
I want to know how to use the above code in React.
You can pass a callback down from the parent that will trigger the parent to change a state value which will then control a class. I will show an example below,
const Parent = () => {
const [extraClass, setExtraClass] = useState(false);
const invertClass = () => setExtraClass(!extraClass);
return (
<div className={extraClass ? 'the-class' : ''}>
<Child changeClass={invertClass} />
</div>
)
}
const Child = (props) => {
return (
<div onClick={() => invertClass}>
Click me to change the parents class.
</div>
)
}
Obviously they should be put into separate files, the right imports should be added, and all of your other code should be implemented. This is just the idea of what you are trying to do, and you will be able to fix your code with the above implementation.
Just comment if you need any more help and I would be happy to help you out.

passing a function as a prop from a functional component to a class component

I am learning react by coding a gamertag generator. It generates random tags and keeps a list, and each can be rated. My main app is a functional component utilizing the useState hook to set state: an array of objects with details about the tags, namely a star rating system.
I generate each gamertag using a react component Tag, within, it uses a functional component, RenderStars to draw the stars. Each tag stars off with 0 stars, so 5 empty stars, and I want the user to change rating by clicking on however many stars, 1-5. RenderStars will then draw however many empty and filled stars as needed.
I have a function in App, changeStars, that I can't seem to get any of the child components to call successfully. I am passing the function to the child components through props.
I've tried writing changeStars in arrow notation and as a plain function. I've tried it without requiring any parameters. I've tried calling it within Tag just using a button. There's other ways I've messed with it that I can't quite recall, just messing with the syntax and trying other things from stackexchange and articles.
I don't bind the function because it's created in a functional component.
This seems like a super basic task and I can't figure it out. Yes i've read the Docs from react.js
Here is some of the code, I'll try to take out as much as possible:
function App() {
const [tagInventory, setTagInventory] = useState([]);
const latestTag = tagInventory[tagInventory.length - 1] ? tagInventory[tagInventory.length -1] : null;
const handleAdd = (tag) => {
uses hook to add tag to state
}
const makeTag = () => {
creates random tag
}
function changeStars(stars,key) {
console.log(stars, key);
//this will change the star rating of an individual tag
}
return (
<main>
A bunch of amazing interface html
<section className="tagInventory">
{tagInventory.map( (item) =>
<Tag
key={item.timeStamp}
tagItem={item}
/>
) }
</section>
</main>
);
};
class Tag extends React.Component {
render() {
const item = this.props.tagItem;
const stars = this.props.tagItem.stars;
const key = this.props.tagItem.timeStamp;
const tagClass = this.props.newTag ? "tag-item new-item" : "tag-item";
return (
<div className={tagClass}>
code to generate cool tag info
</div>
<RenderStars
stars={stars}
changeStars={changeStars}
newTag={false}
key={key}
/>
</div>
);
}
}
const RenderStars = (props) => {
// ref for using symbol tag https://css-tricks.com/svg-symbol-good-choice-icons/
return (
i load svg of stars then can display as many as i need later...
now i draw 4 stars, for the example i'll stop at the first, here's the call..
{props.stars === 0 &&
<div>
<svg className="empty-star" onClick={() => props.changeStars(4,props.key)}>
<use xlinkHref="#empty-star" />
now the other stars and whatnot
}
Thanks!
So basically you want to pass the function changeStars from App to Tag and then to RenderStars, is that correct?
If so, you're forgetting to pass it from App to Tag
App:
<section className="tagInventory">
{tagInventory.map( (item) =>
<Tag
key={item.timeStamp}
tagItem={item}
/>
) }
</section>
Should be passing the function:
<section className="tagInventory">
{tagInventory.map( (item) =>
<Tag
key={item.timeStamp}
tagItem={item}
changeStars={changeStars}
/>
) }
</section>
And then on Tag:
const changeStars = this.props.changeStars;

functions vs class components to handle events and attach arguments

I am trying to choose which is the most optimized way to write a React component that has to handle an event and send some data.
Hi I am tinkering and trying to get comfortable writing react components.
This piece of code fetches some array and makes buttons out of it.
https://codepen.io/daifuco/pen/OdxWYZ
App is just the main component, nothing special about it.
My question: As you can see in the working code, Header + GenreButton:
class Header extends Component {
render() {
return (
<div clasName="footer">
{this.props.data.map((genre)=>
<GenreButton clicky={this.props.clickytoTop}genre={genre}/>
)}
</div>
)
}
}
class GenreButton extends Component {
handleClick = () => {
this.props.clicky(this.props.genre)
}
render() {
return (
<div className="genreButton" onClick={this.handleClick}>{this.props.genre}</div>
)
}
}
has the same result as Header2, which is just a functional component, but as far as I know , Header2 creates a callback every time it renders each div.
function Header2(props) {
return (
<div clasName="footer">
{props.data.map((genre)=>
<div className="genreButton" onClick={()=>props.clickytoTop(genre)}>
{genre}
</div>)}</div>
)
}
So, I understand that Header2 is not the optimal way to design it?
are Header + GenreButton more optimized? Is there any way to improve it?
If you ever have the choice, it is usually preferable to use a stateless component, as they do not have to manage a state which would slow down your application.
To choose between one or the other, if your component needs to re-render itself, use a class, if not, use a function.
Your GenreButton does not need to be a class component either in this case :
const Header = ({ data, clickytoTop }) => ( //Deconstructs your props
<div clasName="footer">
{data.map((genre) =>
<GenreButton clicky={clickytoTop} genre={genre} />
)}
</div>
)
To avoid creating a new function everytime in your render, you could use a decorated arrow function, that you will bind this way :
const GenreButton = ({ clicky, genre }) => <div className="genreButton" onClick={clicky(genre)}>{genre}</div>
The function passed in the header props will now have the following declaration, to handle both sets of parameters :
clickFunction = genre => ev => {
}
When calling clicky(genre) it will return another function capable of accepting your click event.
<Header clickytoTop={this.clickFunction}/>
Second version (same result) :
const Header2 = ({ data, clickytoTop }) => ( //Deconstructs your props
<div clasName="footer">
{data.map(genre =>
<div className="genreButton" onClick={clickytoTop(genre)}>{genre}</div>
)}
</div>
)

Resources