Suppose we have the following pure function:
const HeaderComponent = () => (
<h1> Title <h1>
)
export default HeaderComponent
Now I need to receive the new title from props, so we often destructure this.props to avoid accessing title with this.props.title and in the render method we will have something like:
render() {
const {Title} = this.props;
return(
<h1> {Title} </h1>
)
}
The thing is we have to extend React.Component class to access render method.
Is it possible to use the destructure in pure functions?
const HeaderComponent = props => (
// const {Title} = this.props;
)
You can do it like this. I also find it a nice way of making the function self documenting.
const HeaderComponent = ({ title }) => (
<h1>{ title }<h1>
)
Also can set default values
const HeaderComponent = ({ title: 'Default Title' }) => (
<h1>{ title }<h1>
)
UPDATE:
As T.J. Crowder points out, Title is capitalized in your examples above. In the text portion it is lowercase; as that is the norm, I have used the lowercase version
For your specific situation, see ken4z's answer, as parameter destructuring is the most concise way to do that.
But in the general case: If you have logic you need to put in the arrow function prior to the return, just use the verbose form of arrow function:
const HeaderComponent = props => {
const {Title} = props;
// ....more logic can go here...
return <h1>{Title}<h1>;
};
But again, you don't need that just to grab Title from props.
(Side note: It's unusual to capitalize the T in title when it's a property name or variable name...)
Related
Im having issues when reading defaultprops of where I use a translation function to read a value mapped to a labelkey. In this case it's a variable called 'greeting_component_title'.
const GreetingComponent = ({ title, id, className }) => {
console.log('id');
return (
<section id={id} className={className}>
<p> {title} </p>
</section>
)
};
GreetingComponent.propTypes = {
title: PropTypes.string,
};
GreetingComponent.defaultProps = {
title: translate('greeting_component_title'),
};
export default GreetingComponent;
Title in this case is an object of arrays when the translate function returns a string, however if i instead write default props like this
GreetingComponent.defaultProps = {
title: 'greeting_component_title',
};
and then use it in my p as such
{translate(title)}
it works perfectly. This was working a few months ago and I've just returned to it. I also tried using an arrow function in the defaultProps and it didn't work.
I cant find anything that tells me if something has changed regarding defaultprops not allowing for functions to run, am I missing something here?
I found this:
https://github.com/jsx-eslint/eslint-plugin-react/issues/2396#issuecomment-539184761
Which confirmed my thoughts regarding defaultProps and it seems the syntax has changed. So instead of the code you see in my question, this was the answer
const GreetingComponent = ({
title = translate('greeting_component_title'),
id,
className
}) => {
console.log('id');
return (
<section id={id} className={className}>
<p> {title} </p>
</section>
)
};
GreetingComponent.propTypes = {
title: PropTypes.string,
};
export default GreetingComponent;
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>
I am writing a ControlledInput component, and in order to have access to the state of the component using ControlledInput, I have a binder prop in ControlledInput.
I'm having a slight issue when using the component:
render() {
const CI = props => <ControlledInput binder={this} {...props} />;
return (
<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput binder={this} label={"Title"} />
</div>
);
}
The implementation above works completely fine. However, note the const CI I've defined. I tried to use this so I could just write <CI label={"Title"}/> without the binder since the binder will be the same on all the ControlledInput components I use in a given render method.
The problem with using <CI label={"Title"}/> is that when I type into the input, the input "blurs" and I have to reselect it. This appears to be because the render method creates the CI on every render.
I hope I've explained that clearly, because my head hurts.
Anyway, it makes sense to me why this happens. And I know that one solution is to put const CI = props => <ControlledInput binder={this} {...props} />; outside of the render function. But then I'd have to call it as <this.CI> and that starts to defeat the purpose.
And I can't put CI in global scope because then I don't have access to this.
Is there a way to solve this?
Update
Here is the current (very much in progress) code for ControlledInput:
// #flow
import React, { Component } from "react";
type Props = {
containerStyle?: Object,
label: string,
propName?: string,
binder: Component<Object, Object>,
onChange?: Object => void
};
class ControlledInput extends Component<Props> {
render() {
const props = this.props;
const propName = props.propName || props.label.toLowerCase();
return (
<div style={props.containerStyle}>
<p>{props.label}</p>
<input
type="text"
label={props.label}
onChange={
this.props.onChange ||
(e => {
props.binder.setState({ [propName]: e.target.value });
})
}
value={props.binder.state[propName]}
></input>
</div>
);
}
}
The point of this whole endeavor is to simplify creating a form with controlled components, avoiding having to add value={this.state.whatever} and onChange={e=>this.setState({whatever: e})} to each one, which is not DRY in my opinion.
And then I want get a little more DRY by not passing binder={this} to every component and that's why I'm doing const CI = props => <ControlledInput binder={this} {...props} />;, which, again, has to be inside the class to access this and inside the render function to be called as CI rather than this.CI.
So that first explanation why you need to pass this, although I suppose I could also have props like setState={this.setState} parentState={this.state}, and in that case it does indeed start to make sense to combine those into something like {...propsToSend} as #John Ruddell suggested.
Note that I've provided a possibility to override onChange, and plan on doing so for most or all of the other props (e.g, value={this.props.value || binder.state[propName]}. If one were to override a lot of these (especially value and onChange) it would indeed make the component much less reusable, but the main use case is for quickly creating multiple inputs that don't have special input handling.
So, again, my ideal would be to call <ControlledInput label="Title"/> and have the component code take care of binding state and setState correctly. If this is possible. And then the second option would be to have a place to define the necessary context props in a place that makes it simple when it's time to actually use the component multiple times, like so:
<ControlledInput label={"title"} {...contextProps}/>
<ControlledInput label={"author"} {...contextProps}/>
<ControlledInput label={"email"} {...contextProps}/>
<ControlledInput label={"content"} textArea={true} {...contextProps}/> // textarea prop not implemented yet, fyi
etc
I hear that accessing the parent state/context may be an anti-pattern, but there must be some way to do what I'm trying to do without using an anti-pattern, isn't there?
If you want the state of the parent, handle the state there and pass down the value to your input - ControlledInput won't have to know anything except how to handle data in and out. Something like this, and note that I jacked up the names a little so you can see which component is handling what:
import React, { useState } from "react"
const Parent = () => {
const [title, setTitle] = useState("")
const handleChangeInParent = (newTitle) => {
setTitle((oldValue) => newTitle)
}
return(<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput handleChange={handleChangeInParent} label={title} />
</div>)
}
const ControlledInput = ({handleChange, label}) => {
return (
<input onChange={handleChange} type="text" value={label} />
)
}
If ControlledComponent needs to handle its own state, then pass it a default value and then have the Parent read the value when saving (or whatever):
import React, { useState } from "react"
const Parent = () => {
const handleSaveInParent = (newTitle) => {
console.log("got the new title!")
}
return (
<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput handleSave={handleSaveInParent} initialLabel="Title" />
</div>
)
}
const ControlledInput = ({ handleSave, initialLabel }) => {
const [title, setTitle] = useState(initialLabel)
const handleChange = (ev) => {
const value = ev.target.value
setTitle((oldValue) => value)
}
const handleSubmit = (ev) => {
ev.preventDefault()
handleSave(title)
}
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} type="text" value={title} />
</form>
)
}
You shouldn't be sending this through - just send values and/or functions to handle values.
With Updated Implementation
(okay, John you win!)
Not positive if this is technically an "answer", but I've rewritten the component to take a state and (updated) a setterFn prop:
component
// #flow
import React, { Component } from "react";
type Props = {
containerStyle?: Object,
labelStyle?: Object,
label: string,
propName?: string,
state: Object,
onChange?: Object => void,
textArea?: boolean,
setterFn: (key: string, value: mixed) => void
};
class ControlledInput extends Component<Props> {
render() {
const props = this.props;
const propertyName = props.propName || props.label.toLowerCase();
const TagType = props.textArea ? "textarea" : "input";
// only pass valid props to DOM element (remove any problematic custom props)
const { setterFn, propName, textArea, ...domProps } = props;
return (
<div style={props.containerStyle}>
<p style={props.labelStyle}>{props.label}</p>
<TagType
{...domProps}
label={props.label} // actually could get passed automatically, but it's important so I'm leaving it in the code
onChange={
this.props.onChange ||
(setterFn ? e => setterFn(propertyName, e.target.value) : null)
}
value={props.state[propertyName] || ""}
></TagType>
</div>
);
}
}
export default ControlledInput;
in use (somehow less code than before!)
class Wrapper extends Component<Object, Object> {
state = {};
render() {
const setterFn = (k, v) => this.setState({ [k]: v });
const p = { state: this.state, setterFn: setterFn.bind(this) };
return <ControlledInput {...p} {...this.props.inputProps} />
}
}
I guess this is more appropriate. It still takes up a lot more space than binder={this}.
It doesn't actually the questions of:
How to access the parent's state from the component. Though from comments it seems like this is an anti-pattern, which I do understand from the theory of React.
How to set these repeating props elsewhere so that I can just call `. I guess the only solution is to do something like this:
render() {
const props = {state: this.state, setState: this.setState}
<ControlledInput {...props} label="Title"/>
}
Which certainly isn't such a bad solution. Especially if I shorten that name to, say, a single character.
Much thanks to #John Ruddell for setting me on the right path.
In the simple example below, I'm looking for a way to get for example CardBody child.
const Card = ({children}) => {
const cardHeadChild = ...;
const cardBodyChild = ...;
return (
<div>
{cardHeadChild}
{cardBodyChild}
</div>
)
}
const CardHead = () => <div>Head</div>
const CardBody = () => <div>Body</div>
// Usage:
<Card>
<CardHead>
<CardBody>
</Card>
I cannot get by index (eg: React.Children.toArray(children)[1]) because children are optionals.
I tried something like this:
React.Children.forEach(children, child => {
if(child.type.name === 'CardBody') cardBodyChild = child
// or
if(child.type.displayName === 'CardBody') cardBodyChild = child
..
})
but it doesn't work when component are wrapped in HOC.
Any solution ?
Function name shouldn't be used in production client-side code because function names are mangled when the application is minified. The same applies to displayName - unless it was set explicitly. Also notice that primary use of displayName and name is debugging.
Children can be identified by React element type. If the purpose is to output optional children in specified order, this can be done similarly to this answer:
Optional head:
{props.children.find(({ type }) => type === CardHead)}
Optional body:
{props.children.find(({ type }) => type === CardBody)}
It's expected that children should be exactly CardHead and CardBody stateless components. If there's a need to enhance their functionality with other components, CardHead and CardBody should wrap these components:
const CardHead = props => <div>
Head
{props.children}
</div>
...
<Card>
<CardHead><SomeComponent/></CardHead>
</Card>
const HiContainer => (props) {
render{
return(
<h1>Hi {this.props.greet}</h1>
)
}
}
ReactDOM.render(
<HiContainer greet="hi"/>
document.getElementById('root')
);
What's wrong with this code? It's hard to debug I can't see which lines has problem in the console.
Also when do I need to use constructor?
You have some syntax errors, should be
const HiContainer = (props) => {
return(
<h1>Hi {props.greet}</h1>
)
}
and can be simplify to:
const HiContainer = props => <h1>Hi {props.greet}</h1>
You may need to learn from the basics, this is arrow function:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
Problem is, you are using the arrow function in a wrong way.
It should be like this const HiContainer = () => {}.
Try this it will work:
const HiContainer = (props) => {
return(
<h1>Welcome {props.greet}</h1>
)
}
ReactDOM.render(
<HiContainer greet="hi"/>,
document.getElementById('app')
);
constructor is required when you use stateful components, and store the information in state variable, since you are using stateless components, constructor is not required.
Check jsfiddle for working example: https://jsfiddle.net/ej2szg3a/
Check this for Stateless Function Components: https://www.reactenlightenment.com/react-state/8.4.html
Seems like you're using functional component, which handles render method automatically.
The code should be:
const HiContainer = (props) => (
<h1>Hi {props.greet}</h1>
)
If you want to add lifecycle methods to the component, you need to convert it to class component.