Passing props via spread operator in React - javascript

Update: The plot has greatly thickened on this component as #wawka dove deep into the documentation and found that this should work but there are some wonky setups within the react-router-dom v^5.0.1. I'm still working through it but, this looks like it might require a rewrite of the myLink2 component.
Using React I have a component that I need to pass an 'id' prop to render an id on the html anchor. Starting at the lowest return level for this anchor point and working up we have:
// links.jsx
export const MyLink = ({children, location, ...props}) => {
const href = "mydomain.com" + location;
return (
<a href={href} {...props}>{children}</a>
)
}
export const MyLink2 = ({children, location, ...props}) => {
return (
<RouterLink to={location} {...props}>{children}</RouterLink>
)
}
//components.jsx
export const Block = ({linkLocation, htmlId, children, externalLink: isExternalLink}) => {
const EditLink = isExternalLink ? MyLink : MyLink2;
return <div className="outer-div">
<div className="inner-div">
{children}
</div>
<EditLink location={editLocation} id={htmlId}>{translate('edit')}</EditLink>
</div>
}
export const Summary = ({info1, info2, info3}) => {
return <Block editLocation={'/edit/location/' + info2} htmlId={'i-am-id-' + info2}>
<div>{info1}</div>
<div>{info2}</div>
<div>{info3}</div>
</Block>
}
That htmlId is what I'm seeking to pass up to myLink to assign the anchor's id attribute yet on page render it doesn't appear. Is it because id's are protected/special? Do I need to assign the spread operator on props to the EditLink component? Am I missing a passing point somewhere? I'm especially confused because similar questions show the spread operator as being just the right thing to do what I'm seeking.
Guidance would be much appreciated!

By all the research, this should have worked. As it would not in my application the workaround was to use a third MyLink3. I set it to a barebones link render and pass the component to MyLink2.
Like so:
// links.jsx
export const MyLink = ({children, location, ...props}) => {
const href = "mydomain.com" + location;
return (
<a href={href} {...props}>{children}</a>
)
}
export const MyLink2 = ({children, location, ...props}) => {
return (
<RouterLink to={location} component={MyLink3} {...props}>{children}</RouterLink>
)
}
export const MyLink3 = ({children, location, ...props}) => {
return (
<a href={href} {...props}>{children}</a>
)
}

Related

Capture this.name of a button in React onClick [duplicate]

This question already has an answer here:
ReactJS, event.currentTarget doesn't have the same behavior as Vanilla Javascript
(1 answer)
Closed last month.
I want to capture the name attribute of a button on click in React.
I tried the following code block:
export function TestButton(props){
function logName() {
console.log(this.name)
}
return(
<button name={props.name} onClick={event => logName(event.currentTarget.getAttribute("name"))} type='button'>{props.text}</button>
)
}
My expectation was that this code would allow me to create a button that displays the name in the console log:
<TestButton name='helloWorld' text='Click Me'/>
Instead I get an alert that this is undefined. This is in spite of my ability to see the name when I inspect the element.
I have also tried target instead of currentTarget with no luck. I also tried event.currentTarget.name without the results I desire.
What did i miss?
In react, I believe this is reserved for classes, whereas you are defining a functional component. In a functional component, the comparable state value would be stored with useState(). That being said, I'm not sure I see the need for that here, since this button is getting its props from somewhere and the value of name and text are not changing in this component. I would code it this way:
export const TestButton = ({props}) => {
return(
<button name={props.name} onClick={() => console.log(props.name)}>
{props.text}
</button>
)
}
Now to go a bit further, maybe you want to use state wherever this button is being rendered. That could look like this:
import {TestButton} from "./someFile";
const [name, setName] = useState("some-button");
const [text, setText] = useState("click me!");
// now there could be some code here that decides what the name or text would be
// and updates the values of each with setName("name") and setText("text")
const Page = () => (
<>
<TestButton props={{name: name, text: text}} />
</>
)
This is all building off your current code, but now I will combine everything in a way that makes sense to me:
import {useState} from "react";
const [name, setName] = useState("some-button");
const [text, setText] = useState("click me!");
// some code to determine/change the value of the state vars if necessary
const TestButton = ({name, text}) => {
return(
<button name={name} onClick={() => console.log(name)}>
{text}
</button>
)
}
export const Page = () => (
<>
<TestButton name={name} text={text} />
</>
)
Pleas try as follows:
export function TestButton(props){
function logName() {
console.log(props.name)
}
return(
<button name={props.name} onClick={() => logName()} type='button'>{props.text}</button>
)
}
Try this
export function TestButton(props){
const logName = (e, name) => {
console.log("name attribute ->", name)
}
return(
<button name={props.name} onClick={ (e) => logName(e, props.name)} type='button'>{props.text}</button>
)
}

React - toggle text and class in an HTML element?

I am trying to create a system where I can easily click a given sentence on the page and have it toggle to a different sentence with a different color upon click. I am new to react native and trying to figure out the best way to handle it. So far I have been able to get a toggle working but having trouble figuring out how to change the class as everything is getting handled within a single div.
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<div className="textline" onClick={() => setStatus(!status)}>
{`${status ? 'state 1' : 'state 2'}`}
</div>
);
};
How can I make state 1 and state 2 into separate return statements that return separate texts + classes but toggle back and forth?
you can just create a component for it, create a state to track of toggle state and receive style of text as prop
in React code sandbox : https://codesandbox.io/s/confident-rain-e4zyd?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function ToggleText({ text1, text2, className1, className2 }) {
const [state, toggle] = useState(true);
const className = `initial-style ${state ? className1 : className2}`;
return (
<div className={className} onClick={() => toggle(!state)}>
{state ? text1 : text2}
</div>
);
}
in React-Native codesandbox : https://codesandbox.io/s/eloquent-cerf-k3eb0?file=/src/ToggleText.js:0-465
import React, { useState } from "react";
import { Text, View } from "react-native";
import styles from "./style";
export default function ToggleText({ text1, text2, style1, style2 }) {
const [state, toggle] = useState(true);
return (
<View style={styles.container}>
<Text
style={[styles.initialTextStyle, state ? style1 : style2]}
onPress={() => toggle(!state)}
>
{state ? text1 : text2}
</Text>
</View>
);
}
This should be something you're looking for:
import React from "react"
const Sentence = ({ className, displayValue, setStatus }) => {
return (
<div
className={className}
onClick={() => setStatus((prevState) => !prevState)}
>
{displayValue}
</div>
);
};
const ButtonExample = () => {
const [status, setStatus] = React.useState(false);
return status ? (
<Sentence
className="textLine"
displayValue="state 1"
setStatus={setStatus}
/>
) : (
<Sentence
className="textLineTwo"
displayValue="state 2"
setStatus={setStatus}
/>
);
};
You have a Sentence component that takes in three props. One for a different className, one for a different value to be displayed and each will need access to the function that will be changing the status state. Each setter from a hook also has access to a function call, where you can get the previous (current) state value, so you don't need to pass in the current state value.
Sandbox

Can't pass useState() 'set' function to grand child

I'm having issues trying to get my useState variable to work. I create the state in my grandparent then pass it into my parent. Here's a simplified version of my code:
export function Grandparent(){
return(
<div>
const [selectedID, setSelectedID] = useState("0")
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
Parent:
const Parent = ({setSelectedID2 ...}) => {
return(
<div>
{setSelectedID2("5")} //works
<Child setSelectedID3={setSelectedID2} />
</div>
)
}
From the parent I can use 'setSelectedID2' like a function and can change the state. However, when I try to use it in the child component below I get an error stating 'setSelectedID3' is not a function. I'm pretty new to react so I'm not sure if I'm completely missing something. Why can I use the 'set' function in parent but not child when they're getting passed the same way?
Child:
const Child = ({setSelectedID3 ...}) => {
return(
<div >
{setSelectedID3("10")} //results in error
</div>
);
};
In React you make your calculations within the components/functions (it's the js part) and then what you return from them is JSX (it's the html part).
export function Grandparent(){
const [selectedID, setSelectedID] = useState("0");
return(
<div>
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
You can also use (but not define!) some js variables in JSX, as long as they are "renderable" by JSX (they are not Objects - look for React console warnings).
That's your React.101 :)
Here's a working example with everything you have listed here. Props are passed and the function is called in each.
You don't need to name your props 1,2,3.., they are scoped to the function so it's fine if they are the same.
I moved useState and function calls above the return statement, because that's where that logic should go in a component. The jsx is only used for logic dealing with your display/output.
https://codesandbox.io/s/stupefied-tree-uiqw5?file=/src/App.js
Also, I created a working example with a onClick since that's what you will be doing.
https://codesandbox.io/s/compassionate-violet-dt897?file=/src/App.js
import React, { useState } from "react";
export default function App() {
return <Grandparent />;
}
const Grandparent = () => {
const [selectedID, setSelectedID] = useState("0");
return (
<div>
{selectedID}
<Parent setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Parent = ({ selectedID, setSelectedID }) => {
setSelectedID("5");
return (
<div>
{selectedID}
<Child setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Child = ({ selectedID, setSelectedID }) => {
setSelectedID("10");
return <div>{selectedID}</div>;
};
output
10
10
10
const [selectedID, setSelectedID] = useState("0")
should be outside return

ReactJS: programmatically change component tag with JSX

I am making a conditional component that returns <a> if href is defined and returns <div> if not. I am doing this way:
const Label = ({children, ...other}) => {
let component;
if (other.href)
component =
<a {...other}>
{ children }
</a>
else
component =
<div {...other}>
{ children }
</div>
return component;
}
export default Label
There is any way to make this component by only changing the tag name? I know that if I use the manual way of creating compontents (React.createElement) I can do this by changing the first argument passed, for it's a string. But with JSX it's a little different. I though something like
let component =
<{tag} {...other}>
{ children }
</{tag}>
Is it possible?
something like this?
const Label = ({children, ...other}) => {
let component;
if (other.href) {
component = <a {...other}>{ children }</a>;
} else if (other.tag) {
const Tag = other.tag;
component = <Tag {...other}>{ children }</Tag>;
} else {
component = <div {...other}>{ children }</div>;
}
return component;
}
ReactDOM.render(
<Label href="#">Hello World</Label>,
document.getElementById('root')
);
ReactDOM.render(
<Label>Not a Link</Label>,
document.getElementById('rootTwo')
);
ReactDOM.render(
<Label tag="p">Paragraph</Label>,
document.getElementById('rootThree')
);
Working demo:, https://jsfiddle.net/tgm4htac/
If you pass to <> values like component.tag and JSX transpiler can create dynamic tags.
const Label = ({children, tag, ...other}) => {
const component = { tag: tag };
return (<component.tag {...other}>
{ children }
</component.tag>);
};
Example
or
const Label = ({children, tag, ...other}) => {
const Component = tag;
return (<Component {...other}>
{ children }
</Component>);
};
Example

How do I conditionally wrap a React component?

I have a component that will sometimes need to be rendered as an <anchor> and other times as a <div>. The prop I read to determine this, is this.props.url.
If it exists, I need to render the component wrapped in an <a href={this.props.url}>. Otherwise it just gets rendered as a <div/>.
Possible?
This is what I'm doing right now, but feel it could be simplified:
if (this.props.link) {
return (
<a href={this.props.link}>
<i>
{this.props.count}
</i>
</a>
);
}
return (
<i className={styles.Icon}>
{this.props.count}
</i>
);
UPDATE:
Here is the final lockup. Thanks for the tip, #Sulthan!
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';
export default class CommentCount extends Component {
static propTypes = {
count: PropTypes.number.isRequired,
link: PropTypes.string,
className: PropTypes.string
}
render() {
const styles = require('./CommentCount.css');
const {link, className, count} = this.props;
const iconClasses = classNames({
[styles.Icon]: true,
[className]: !link && className
});
const Icon = (
<i className={iconClasses}>
{count}
</i>
);
if (link) {
const baseClasses = classNames({
[styles.Base]: true,
[className]: className
});
return (
<a href={link} className={baseClasses}>
{Icon}
</a>
);
}
return Icon;
}
}
Just use a variable.
var component = (
<i className={styles.Icon}>
{this.props.count}
</i>
);
if (this.props.link) {
return (
<a href={this.props.link} className={baseClasses}>
{component}
</a>
);
}
return component;
or, you can use a helper function to render the contents. JSX is code like any other. If you want to reduce duplications, use functions and variables.
Create a HOC (higher-order component) for wrapping your element:
const WithLink = ({ link, className, children }) => (link ?
<a href={link} className={className}>
{children}
</a>
: children
);
return (
<WithLink link={this.props.link} className={baseClasses}>
<i className={styles.Icon}>
{this.props.count}
</i>
</WithLink>
);
Here's an example of a helpful component I've seen used before (not sure who to accredit it to). It's arguably more declarative:
const ConditionalWrap = ({ condition, wrap, children }) => (
condition ? wrap(children) : children
);
Use case:
// MaybeModal will render its children within a modal (or not)
// depending on whether "isModal" is truthy
const MaybeModal = ({ children, isModal }) => {
return (
<ConditionalWrap
condition={isModal}
wrap={(wrappedChildren) => <Modal>{wrappedChildren}</Modal>}
>
{children}
</ConditionalWrap>
);
}
There's another way
you could use a reference variable
let Wrapper = React.Fragment //fallback in case you dont want to wrap your components
if(someCondition) {
Wrapper = ParentComponent
}
return (
<Wrapper parentProps={parentProps}>
<Child></Child>
</Wrapper>
)
const ConditionalWrapper = ({ condition, wrapper, children }) =>
condition ? wrapper(children) : children;
The component you wanna wrap as
<ConditionalWrapper
condition={link}
wrapper={children => <a href={link}>{children}</a>}>
<h2>{brand}</h2>
</ConditionalWrapper>
Maybe this article can help you more
https://blog.hackages.io/conditionally-wrap-an-element-in-react-a8b9a47fab2
You could also use a util function like this:
const wrapIf = (conditions, content, wrapper) => conditions
? React.cloneElement(wrapper, {}, content)
: content;
You should use a JSX if-else as described here. Something like this should work.
App = React.creatClass({
render() {
var myComponent;
if(typeof(this.props.url) != 'undefined') {
myComponent = <myLink url=this.props.url>;
}
else {
myComponent = <myDiv>;
}
return (
<div>
{myComponent}
</div>
)
}
});
Using react and Typescript
let Wrapper = ({ children }: { children: ReactNode }) => <>{children} </>
if (this.props.link) {
Wrapper = ({ children }: { children: ReactNode }) => <Link to={this.props.link}>{children} </Link>
}
return (
<Wrapper>
<i>
{this.props.count}
</i>
</Wrapper>
)
A functional component which renders 2 components, one is wrapped and the other isn't.
Method 1:
// The interesting part:
const WrapIf = ({ condition, With, children, ...rest }) =>
condition
? <With {...rest}>{children}</With>
: children
const Wrapper = ({children, ...rest}) => <h1 {...rest}>{children}</h1>
// demo app: with & without a wrapper
const App = () => [
<WrapIf condition={true} With={Wrapper} style={{color:"red"}}>
foo
</WrapIf>
,
<WrapIf condition={false} With={Wrapper}>
bar
</WrapIf>
]
ReactDOM.render(<App/>, document.body)
<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>
This can also be used like this:
<WrapIf condition={true} With={"h1"}>
Method 2:
// The interesting part:
const Wrapper = ({ condition, children, ...props }) => condition
? <h1 {...props}>{children}</h1>
: <React.Fragment>{children}</React.Fragment>;
// stackoverflow prevents using <></>
// demo app: with & without a wrapper
const App = () => [
<Wrapper condition={true} style={{color:"red"}}>
foo
</Wrapper>
,
<Wrapper condition={false}>
bar
</Wrapper>
]
ReactDOM.render(<App/>, document.body)
<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>
With provided solutions there is a problem with performance:
https://medium.com/#cowi4030/optimizing-conditional-rendering-in-react-3fee6b197a20
React will unmount <Icon> component on the next render.
Icon exist twice in different order in JSX and React will unmount it if you change props.link on next render. In this case <Icon> its not a heavy component and its acceptable but if you are looking for an other solutions:
https://codesandbox.io/s/82jo98o708?file=/src/index.js
https://thoughtspile.github.io/2018/12/02/react-keep-mounted/

Categories