React: Passing attribute from functional component to class component via onClick - javascript

Both of these first two snippets appear in a class component.
Here's the onClick handler:
selectChoice = (id) => {
console.log(id)
}
Here's where I call the functional component that generates both the id I need, and the onClick method.
<ReturnChoices choices={this.state.choices} selectChoice={() => this.selectChoice(id)}/>
Here's the functional component.
const ReturnChoices = ({choices, selectChoice}) => {
return choices.map(( choice , index) => (
<li key={index} id={index} onClick={() => { selectChoice(this.id) }}>
{choice}
</li>
))
}
For some reason, id is coming though as 'undefined'

pass the function itself, no need to wrap in additional function:
<ReturnChoices choices={this.state.choices} selectChoice={this.selectChoice}/>

Pass id given as argument from ReturnChoices to its caller function
<ReturnChoices choices={this.state.choices} selectChoice={(id) => this.selectChoice(id)}/>

Related

What is the React way of inserting an icon into another component?

I'm trying to create an WithIcon wrapper component which would insert a child (icon) into a wrapped component.
Let's say I have a button:
<Button>Add item</Button>
I want to create a component WithIcon which will be used like this:
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
Ultimately what I want to achieve is this:
<Button className="with-icon"><i className="me-2 bi bi-{icon}"></i>Add item</Button>
Notice the added className and the tag within the Button's body.
I'm trying to figure out how the WithIcon component's code should look like. What is the React way of achieving this result?
The hardest part was the rules of using the WithIcon Will we only have one ?
Will we have only it at the leftmost ? Something like that.
But if we follow your example. We can relatively write something like this for the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
return (
<>
<i className={`me-2 bi bi-${i}`}></i>
{React.cloneElement(child, { className: "with-icon" })}
</>
);
});
};
Then we can just use it the way you want it
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
What we do is just looping through the children which in react is any nested jsx you throw in it (Button in our case)
You can find my fiddle here : https://codesandbox.io/s/react-font-awesome-forked-321tz?file=/src/index.js
UPDATE
So my previous answer does not fully meet the end result we want. The will need to be the main parent
The idea is still quite the same as before but here we are infering the type of the component we passed inside the WithIcon This also adds a safeguard when we passed a nested component inside the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
const MyType = child.type; // So we can get the Button
return (
<MyType className="with-icon">
<i className={`me-2 bi bi-${i}`}></i>
{(React.cloneElement(child, {}), [child.props.children])}
</MyType>
);
});
};
I think I'll go to sleep I'll update the rest of the explanation at later date.
See the fiddle here :
https://codesandbox.io/s/react-font-awesome-forked-y43fx?file=/src/components/WithIcon.js
Note that this code does not preserved the other props of the passed component, but you can relatively add that by adding {...child.props} at the MyComponent which is just (reflection like?) of infering the component.
Of course also have another option like HOC Enhancers to do this but that adds a bit of complexity to your how to declare your component api. So Pick whats best for ya buddy
Maybe try using a higher order component?
const withIcon = (icon, Component) => ({children, ...props}) => {
return (
<Component className="with-icon" {...props}>
<i className=`me-2 bi bi-${icon}` />
{children}
</Component>
);
}
Then the usage is
const ButtonWithIcon = withIcon("your-icon", Button);
<ButtonWithIcon>Add Item</ButtonWithIcon>
From my experience with react it usually comes down to either using a property inside the component like here (https://material-ui.com/api/button/) or higher order component like what I described.
There are two common patterns used in React for achieving this kind of composition:
Higher-Order Components
Start by defining a component for your button:
const Button = ({ className, children }) => (
<button className={className}>{children}</button>
);
Then the higher-order component can be implemented like this:
const withIcon = (Component) => ({ i, className = '', children, ...props }) => (
<Component {...props} className={`${className} with-icon`}>
<i className={`me-2 bi bi-${i}`} />
{children}
</Component>
);
Usage:
const ButtonWithIcon = withIcon(Button);
<ButtonWithIcon i="plus">Add Item</ButtonWithIcon>
Context
Start by defining the context provider for the icon:
import { createContext } from 'react';
const Icon = createContext('');
const IconProvider = ({ i, children }) => (
<Icon.Provider value={i}>{children}</Icon.Provider>
);
and then your component:
import { useContext } from 'react';
const Button = ({ className = '', children }) => {
const i = useContext(Icon);
if (i) {
className += ' with-icon';
children = (
<>
<i className={`me-2 bi bi-${i}`} />
{children}
</>
);
}
return (
<button className={className}>{children}</button>
);
};
Usage:
<IconProvider i="plus"><Button>Add Item</Button></IconProvider>

How can I pass a parent's state to be used within a child component?

I have a parent component where I am looping through an array and setting the state for clicked to that of the id of the clicked item in the array.
I am using this component elsewhere where I need to access this state.
How can I go about passing this state down?
Here is my loop:
{
filteredArray.map(link => {
return (
<div
key={link.id}
role="button"
style={{paddingBottom: 20}}
onClick={this.changeView(link.id)}
onKeyPress={() => {}}
tabIndex={0}
>
<Paragraph size="large">
<a className='heading__dropdown__link'>
{link.label}
</a>
</Paragraph>
</div>
)
})
}
Here is my function where I am setting the state for clicked
changeView(id) {
return (
() => this.setState({clicked: id})
)
}
And here is where I am using the above component:
How can I use the above state here?
<HeadingDropdown
expandedTitle="Change view"
links={links}
heading={currentLocation}
/>
Working off your last comment. If you passed down a function to be used as a prop in HeadingDropdown, you can use it to pass back up it's state-value to the Parent.
Function defined in Parent
class Parent extends React.Component{
state = {
headingDropdownvalues: {}
}
getHeadingDropdownState = (valueFromChild) => {
this.setState({
headingDropdownvalues: valueFromChild
})
}
render(){
<HeadingDropdown passupstate={this.getHeadingDropdownState}/>
}
}
So now your parent component is set-up to consume the state-value from HeadingDropdown.
Now we need to configure HeadingDropdown to actually pass up that value after clicking.
In HeadingComponent, we just need to update your changeView method to call the prop we passed down, after the state has been set. We do this by utilizing the 2nd argument of this.setState() which is a call-back.
changeView(id) {
return (
() => this.setState({
clicked: id
}, () => this.props.passupstate(this.state)) //right here you can pass in whatever you want
)
}
Also here is a sandbox for you to see how it works: https://codesandbox.io/s/jovial-thompson-ldg3n

How pass to react list item to function as parameter at onClick

i wonder how i can pass as parameter to onClick function react list element
here is my list.
const items = this.state.tableData.items.map(
(item, index) => <td key={index} onClick={this.getItem}>{item.name}</td>
);
here is i define my function
constructor(props) {
super(props);
this.state = {
tableData: this.props.items
};
this.getItem = this.getItem.bind(this);
}
here is my function.
getItem(item) {
console.log(item)
}
I wan't to get item details onClick to item where i render my list.
i tried to make onClick={this.getItem(item)} i get with this way item details but not at onClick. Just all items details console logged when i open my page.
what can i do ?
There are two common ways: Using an arrow function, or using bind:
Arrow function:
const items = this.state.tableData.items.map(
(item, index) => <td key={index} onClick={e => this.getItem(item, e)}>{item.name}</td>
);
bind:
const items = this.state.tableData.items.map(
(item, index) => <td key={index} onClick={this.getItem.bind(this, item)}>{item.name}</td>
);
In both cases, with the above, getItem would get the item as its first argument, the event object as its second.
This is just an alternative answer. Separating the item into its own component would be an alternative. In this way, you can pass the onClick function without binding it then use it in the Item component like this.
const items = [{ id: 1, name: "foo" }, { id: 2, name: "bar" }];
const Item = ({ item, getItem}) => {
const handleClick = () => getItem(item);
return (
<div onClick={handleClick}>{item.name}</div>
)
}
const App = () => {
const getItem = item => console.log(item.id);
return (
<div className="App">
{items.map(item => (
<Item key={item.id} item={item} getItem={getItem} />
))}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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="root" />
If there are so many items you can avoid rerendering all the items if none of their props changed since you don't use bind or any inline arrow function. But, as you can see it needs a separate component and some more coding.
Functional Components are going to win over classes.
So better to be used with this:
onClick={ ( item.id) => handleclick( item.id, [...])
Edit 1
Handleclick here intended as a Hook ( e. G. SetValue on useState).
Nb:
On purpose code dirty to fight against the awful copypasta SO practice. Thanks redditors. Read the code, then read the memes.

React/material ui raisedbutton executing onTouchTap on init

I am using react/redux/material ui and normally through out my website the components work fine. One 1 page there is something very very wierd going on.
I create a component like this:
class MyOwnComponent extends Component {
doSomething = (id) => {
alert('doSomething: id = ' + id )
}
render() {
return (
<RaisedButton secondary={true} label={'My label'} onTouchTap={this.doSomething(id)}/>
)
}
}
I have a raisedbutton from material ui and put it in the render method.
The thing is that when the page loads with the component in it the doSomething method is called. Even though it is only called in the onTouchTap in raisedbutton. Almost as if a bug in the raisedbutton is calling the onTouchTap method immediately instead when the button is clicked.
Does any body have a explanation for this really strange behaviour?
Thanks
You are giving to onTouchTap void, because that's what this.doSomething(id) returns .
this.doSomething(id)
is executed the firs time MyOwnComponent is rendered.
Instead you should do this :
class MyOwnComponent extends Component {
doSomething = () => {
const {id} = this.props.object;
alert('doSomething: id = ' + id )
}
render() {
return (
<RaisedButton secondary={true} label={'My label'} onTouchTap={this.doSomething}/>
)
}
}
The problem was that the return value of doSomething() function is returned and assigned to onTouchTap. Instead you should just pass the function name, without paranthesis.
One solution can be this.
class MyOwnComponent extends Component {
doSomething = () => {
alert('doSomething: id = ' + this.props.object.id );
}
render() {
return (
<RaisedButton secondary={true} label={'My label'} onTouchTap=
{this.doSomething}/>
);
}
}
Alternatively you can use also use
<RaisedButton secondary={true} label={'My label'} onTouchTap={()=>
this.doSomeThing(id) /> // eslint-disable-line
I tried your sollution. However when I use onTouchTap={() => this.doSomething(id)} the page wont load because the browser says JSX props should not use arrow functions.
And the id is from the props. At the top of the render method I say
const { id } = this.props.object

React: how to pass arguments to the callback

I have a list of elements inside my react component, and I want them to be clickable. On click I call some external function passing item ID in arguments:
render () {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id} onClick={() => {doSomething(item.id)}></li>
))}
</ul>
)
}
This code works, but it has a big performance drawback: a lot of new anonymous functions are being created on each call to render.
How can I pass that doSomething function as a reference here while still being able to provide a item.id to it?
You could use data-attributes, to set the correct id on each item while using the same function:
function doSomethingFromEvent(event){
return doSomething(event.target.dataset.id);
}
render () {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id} data-id={item.id} onClick={doSomethingFromEvent}></li>
))}
</ul>
)
}
When setting data-* attributes in your element, you can get it back with dataset, in the form of a hash. For example, in doSomethingFromEvent I have event.target.dataset = {id: *id*}. See more on MDN
This is even cleaner when updating a hash (the state for example), with <li key={item.id} data-myattriute={myvalue} onClick={this.handleClick}></li>, I can simply define handleClick such as:
handleClick(event){
// Here event.target.dataset = {myattribute: myvalue}
Object.assign(myObject, event.target.dataset);
// or
this.setState(event.target.dataset);
}
Coming back to your problem, the great thing with this approach is that if you ensure your container element (ul) cannot be clicked outside its children with data-attributes (li), which is your case, you can declare the function on it:
render () {
return (
<ul onClick={doSomethingFromEvent}>
{this.props.items.map(item => (
<li key={item.id} data-id={item.id}></li>
))}
</ul>
)
}
Now your function is created a single time, and is not even repeated in each item.
What you can do is create a partially applied or higher order function to enclose the item.id and pass it along. So let's look at a toy example of this:
class App {
partiallyApplied = id => e => {
console.log(id,'this is passed in first')
console.log(e,'this is passed in second')
}
render(){
return (
<button onClick={this.partiallyApplied(1234)}>Click Me</button>
)
}
}
Now you have access to 1234 along with your event object
This is use transform-class-properties babel plugin. If do not or cannot use that, you can probably do something like this:
partiallyApplied(id){
return function(e){
console.log(id,'this is id')
console.log(e,'this is event')
}
}
but then you will have to bind this during your call and I just don't like that everywhere.
You could create a new component for every item in the array and use the props, like this:
class Li extends React.Component {
render() {
return <li onClick={this.onClick}> {this.props.children} </li>;
}
onClick = () => {
console.log(this.props.item);
};
}
class App extends React.Component {
state = {
items: [
{id: 1, name: 'one'},
{id: 2, name: 'two'},
{id: 3, name: 'three'},
]
};
render() {
return <ul>
{this.state.items.map(i =>
<Li key={i.id} item={i}>{i.name}</Li>
)}
</ul>;
}
}

Categories

Resources