Just wondering if there is anyway to set heading levels by passing props down to the base component.
Example:
Base Component
class Heading extends Component {
render() {
return (
<h{this.props.headinglevel} className={this.props.titleStyle}>
{this.props.title}
</h{this.props.headinglevel}>
);
}
}
export default Heading;
Parent Component (Passing Props)
class HomeHeader extends Component {
render() {
return (
<Heading headinglevel="1" titleStyle="big-header" title="Hello World" />
)
}
}
export default HomeHeader;
When I try this I get a syntax error.
Yup! The way to make your tag a variable is as such:
render() {
const Tag = 'h1';
return <Tag>{/* some code here */}</Tag>;
}
Notice that Tag is capitalized. It is required you capitalize a tag variable so React understands it's not just a normal HTML element.
So in your case, you could do something like:
render() {
const Tag = 'h' + this.props.headinglevel; // make sure this has a default value of "1" or w/e
return (
<Tag className={this.props.titleStyle}>
{this.props.title}
<Tag>
);
}
(If you're being safe, you may want to add some check so this.props.headinglevel can only be 1-6.)
Related
So I have two components: Tag and TagGroup. When Tag component is used alone, I want that component to use <div> tag and when Tag is wrapped inside of TagGroup, which means more than 1 Tag is being used, then I want it to use <li> tag. With my current design, I used context to pass prop groupUpTags to Tag component. When groupUpTags is true then Tag component will use <li> tag, if false, <div> will be used. Currently, it doesn't even work properly as whether I wrap Tag component under TagGroup or not <li> tag is always being used. My question is, is there a way to get Tag component to render <li> tag when wrapped inside TagGroup without using any props and render <div> tag when used alone? My current design look little too messy.
https://codesandbox.io/s/tag-group-s07oyh?file=/src/App.tsx
App.tsx
import "./styles.css";
import React from "react";
import { TagGroupContextInterface, TagGroupContext } from "./TagGroupContext";
export interface TagGroupInterface extends TagGroupContextInterface {
children: React.ReactNode;
}
const Tag = () => {
const context = React.useContext(TagGroupContext);
return context.groupUpTags ? (
<li className="tag">Tag</li>
) : (
<div className="tag">Tag</div>
);
};
const TagGroup = ({ groupUpTags = true, children }: TagGroupInterface) => {
const context = React.useMemo(
() => ({
groupUpTags
}),
[groupUpTags]
);
return (
<TagGroupContext.Provider value={context}>
<div>
<ul>{React.Children.map(children, (child) => child)}</ul>
</div>
</TagGroupContext.Provider>
);
};
export default function App() {
return (
<div className="App">
<TagGroup>
<Tag />
<Tag />
<Tag />
</TagGroup>
</div>
);
}
TagGroupContext.tsx
import React from "react";
export interface TagGroupContextInterface {
groupUpTags?: boolean;
}
const defaultTagGroupContext: TagGroupContextInterface = {
groupUpTags: true
};
export const TagGroupContext = React.createContext<TagGroupContextInterface>(
defaultTagGroupContext
);
Keep your components more deterministic
A Tag component should probably always return a simple tag in a div or a span whatever you need for the stand alone tag to work.
I would then add the li inside the list component since thats the moment you need the li. when you code a Tag it shouldn't know about the parents.
Probably something like this in your Tag Group
{Children.map(children, (child, i) => {
<li key={i}>{child}</li>;
})}
note: don't use "i" as a key its not reliable. Use a uid from the tag itself.
I am currently experiencing an issue when it comes to wrapping the child elements of a parent element in a higher order component and then rendering them.
Consider the following structure:
return (
<div className="App">
<FocusProvider>
<TestComponent testProp={'Foo'}/>
<TestComponent testProp={'Foo'}/>
<TestComponent testProp={'Foo'}/>
</FocusProvider>
</div>
);
Where FocusProvider is the parent element and TestComponent is the child element that needs to be wrapped in a higher order component that provides lifecycle methods to it as well as inject props.
And then the higher order component called hoc which overrides the prop for TestComponent and provides a lifecycle method to it as well looks like:
const hoc = (WrappedComponent, prop) => {
return class extends React.Component {
shouldComponentUpdate = (prevProps, prop) => {
return !prevProps === prop
}
render(){
return <WrappedComponent testProp={prop}/>
}
}
}
The render method of FocusProvider looks like :
render(){
return(
this.props.children.map(child => {
let Elem = hoc(child, 'bar')
return <Elem/>
})
)
}
When I try and render that I get Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
When I try and change it to :
render(){
return(
this.props.children.map(child => {
let elem = hoc(child, 'bar')
return elem
})
)
}
Nothing is returned from render. I am confused because I can render the chil components directly, but not the child components wrapped in the HOC:
render(){
return(
this.props.children.map(child => {
return child //This works
})
)
}
I want to avoid using React.cloneElement as I don't want to trigger re-renders by cloning the child elements every time the parent updates.
Any help would be appreciated
hoc is a function which returns a Component not jsx. You cannot wrap the children in a HOC like that.
But you can wrap just FocusProvider and pass the prop down to it's children using cloneElement. There is no problem in use cloneElement like this. Is a common pattern actually.
function App() {
return (
<div className="App">
<FocusProvider bar="baz">
<Child />
<Child />
<Child />
</FocusProvider>
</div>
);
}
const withHOC = Component => props => {
return <Component foo="bar" {...props} />;
};
const FocusProvider = withHOC(({ children, foo, bar }) => {
return React.Children.map(children, child => {
return React.cloneElement(child, { foo, bar });
});
});
const Child = ({ foo, bar }) => (
<>
{foo}
{bar}
</>
);
I want to convert an array prop into a string:
export default class MyComp extends React.Component {
render() {
let type = this.props.type; // [".abc",".ded",".ted"];
newType = type.join();
//o/p: newType= ".abc,.ded,.ted"
console.log(newType) // ".abc,.ded,.ted"
return (
<div>
<input type="file" accept={newType}/> //here throws error
</div>
)
}
}
export default class SubComp extends React.Component{
render() {
<Mycomp type={[".abc",".ded",".ted"]}/>
}
}
when I try to access newType as a values to the accept html, it throws:
TypeError: Cannot read property 'join' of undefined.
One thing I checked here if I try to pass the hard code values to newType it gets working fine. Only when Im trying to convert the array to string using .join() or .toString(), it fails.
render() {
let type = this.props.type; // [".abc",".ded",".ted"];
newType = ".abc,.ded,.ted";
return (
<div>
<input type="file" accept={newType}/> //Works Fine!!!!
</div>
)
}
Any idea what may be causing the issue?
There are actually two problems with your code in the example:
You have to use curly brackets around prop values that aren't strings, so it should look like this: <Mycomp type={[".abc",".ded",".ted"]} />
You assign the joined array to newType, but then use newtype in your input tag (note the capitalization difference)
The corrected code would look like this:
export default class MyComp extends React.Component {
render() {
let type = this.props.type;
let newType = type.join();
return (
<div>
<input type="file" accept={newType}/>
</div>
)
}
}
export default class SubComp extends React.Component {
render() {
<Mycomp type={[".abc",".ded",".ted"]} />
}
}
You need to set your type prop using curly brackets {}, like so:
<Mycomp type={[".abc",".ded",".ted"]}/>
Try to pass the array to prop like this
<Mycomp type={[".abc",".ded",".ted"]} />
You should pass data to props like this
export default class MyComp extends React.Component {
render() {
let type = this.props.type; // [".abc",".ded",".ted"];
let newType = type.join()//converts to string: ".abc,.ded,.ted"
return (
<div>
<input type="file" accept={newType}/> //here throws error
</div>
)
}
}
export default class SubComp extends React.Component{
render() {
<Mycomp type={[".abc",".ded",".ted"]}/>
}
}
Im trying to use a dynamic style property. The approach below throws me an "The style prop expects a mapping from style properties to values, not a string" error.
class someClass extends React.Component {
someFunction = () => {
return {marginLeft : 20 };
}
render() {
return( <div style={this.someFunction}/>
);
}
}
Howerver this one works:
class someClass extends React.Component {
render() {
return( <div style={{marginLeft : 20}}/>
);
}
}
Why is that so and how can i return style objects from functions?
Thanks for any answers in advance!
You didn't call the function inside the style props JSX. Call it like this.someFunction(), then it will return the object of style you kept inside the someFunction.
return <div style={this.someFunction()} />
In the past I was able to set a default prop that used this like so...
let MyButton = React.createClass({
getDefaultProps() {
return {
buttonRef: (ref) => this.button = ref
};
},
But now that I'm using JS classes MyButton looks like this...
class MyButton extends React.Component {
static defaultProps = {
buttonRef: (ref) => this.button = ref
};
And I get an error saying that this is undefined.
What do I need to do to be able to set default props that use this?
EDIT: Add some context
Setting a default buttonRef prop allowed me to use the ref in the MyButton component but also always be able to pass in a custom ref if a parent component needs to access the MyButton DOM node.
class MyButton extends React.Component {
static defaultProps = {
buttonRef: (ref) => this.button = ref
};
componentDidMount() {
Ladda.bind(this.button);
}
render() {
return (
<button
ref={(ref) => this.button = this.props.buttonRef(ref)}
onClick={this.props.handleClick}
>
{this.props.buttonText}
</button>
);
}
}
So then my button can always get hooked in to Ladda: Ladda.bind(this.button)
And if I need to access that button's DOM node in a parent component I can do so by passing in buttonRef as a prop like...
class MouseOverButton extends React.Component {
componentDidMount() {
this.mouseEnterButton.addEventListener("mouseover", doSomething(event));
}
render() {
return (
<div>
<MyButton
buttonRef={(ref) => this.mouseEnterButton = ref}
/>
</div>
);
}
}
EDIT: Apparently my simplified example doesn't illustrate the point well enough so I can come up with a more practical example or y'all can just answer the original question: What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
Where this convention of having a defaultProp for a specific element's ref has been useful is when using a HOC that hooks a nested component into some 3rd party API. I have an AddressSearch HOC that takes a node via a function passed to the wrapped component. Then it uses that node to hook it up with Google's Places API.
So I've got my addAddressSearch(component) function from my HOC. It adds the functions needed to hook up the Google places API. But for Google's API to work I need to know what DOM node I'm working with. So I pass my Input component an inputRef that gives my AddressSearchInput access to the appropriate node.
class AddressSearchInput extends React.Component {
static defaultProps = {
inputRef: (ref) => this.addressSearchInput = ref
};
componentDidMount() {
let node = this.addressSearchInput;
this.props.mountAddressSearch(node);
}
render() {
return (
<div>
<Input
inputAttributes={inputAttributes}
inputRef={(ref) => this.addressSearchInput = this.props.inputRef(ref)}
labelText={<span>Event address or venue name</span>}
labelClassName={labelClassName}
/>
</div>
);
}
}
AddressSearchInput = addAddressSearch(AddressSearchInput);
module.exports = AddressSearchInput;
// Here's the Input component if that helps complete the picture here
class Input extends React.Component {
render() {
return (
<div>
<Label>{this.props.labelText}</Label>
<HelperText text={this.props.inputHelperText} />
<input
{...this.props.inputAttributes}
ref={this.props.inputRef}
></input>
</div>
);
}
}
So now when I want to use my AddressSearchInput in a parent component that needs to add an eventListener to the relevant node I can just pass AddressSearchInput an inputRef prop.
class VenueStreetAddress extends React.Component {
componentDidMount() {
let node = this.venueStreetAddressInput;
this.props.mountValidateOnBlur(node, venueValidationsArray);
},
render() {
return (
<div>
<AddressSearchInput
inputRef={(ref) => this.venueStreetAddressInput = ref}
hasError={this.props.hasError}
/>
{this.props.errorMessageComponent}
</div>
);
}
}
And I can use AddressSearchInput all over the place and it doesn't break anything.
class UserStreetAddress extends React.Component {
componentDidMount() {
let node = this.userStreetAddressInput;
this.props.mountValidateOnBlur(node, userValidationsArray);
},
render() {
return (
<div>
<AddressSearchInput
inputRef={(ref) => this.userStreetAddressInput = ref}
hasError={this.props.hasError}
/>
{this.props.errorMessageComponent}
</div>
);
}
}
Maybe this way is convoluted and wrong but I don't have the time to figure out another way to do it on my own. So either point me to a tutorial(s) on the best way to hook into 3rd party APIs and add dynamic form validation without using refs or answer my original question which is...
What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
EDIT: In attempting to explain my use case I had the idea to make my defaultProps look like this...
static defaultProps = {
inputRef: (ref) => ref
};
which seems to be working without error.
In any case, the original question still stands. What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
This really should be a method, not a property.
class MyButton extends React.Component {
setButtonRef (ref) {
this.button = ref;
}
componentDidMount() {
Ladda.bind(this.button);
}
render() {
return (
<button
ref={ ref => this.setButtonRef(ref) }
onClick={this.props.handleClick}
>
{this.props.buttonText}
</button>
);
}
}
If you want a ref to the button, bind a variable at the class level and assign it to that class variable. Example:
export default class MyComponent extends Component {
componentDidMount() {
// button will be available as `this.button`
}
button = null; // initialize to null
render() {
return (
<div>
<Button ref={e => { this.button = e; }} />
</div>
);
}
}
Since a static property has no knowledge of class instances, if it's strictly necessary to make a static method aware of a given instance, the only way would be to pass to the static method the instance as an argument:
<button
ref={(ref) => this.button = this.props.buttonRef(this, ref)}
onClick={this.props.handleClick}
>
And in your defaultProp, use the instance:
static defaultProps = {
buttonRef: (instance, ref) => instance.button = ref
};