I want to test that on initial render of the parent component, there are no child components rendered in the document.
On every press of the button, the parent component generates a child component within it. On init, the child component array is empty. I therefore expect my child componet test-id to be null on initial render, when I render my parent component.
Parent component:
const ParentComponent = () => {
const [childComponentsArray, setChildComponentsArray] = useState([]);
const createChildComponent = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid()
};
setChildComponentsArray([...childComponentsArray, objToAdd]);
};
return (
<div>
{childComponentsArray.length > 0 && <div>
{childComponentsArray.map(() => {
return <div className={'child-item'}>
<ChildComponent />
</div>;
})}
</div>}
<ButtonContainer variant={'secondary'} label={'+ ' + component.addLabel}
onClick={() => createChildComponent()}
/>
</div>
);
};
Child component:
const ChildComponent = () => {
return (
<div data-testid={'childComponentTestId'}>
<p> I'm in child component! </p>
</div
)
}
Unit test:
test('on render, no child items are visible', () => {
render(
<RecoilRoot>
<ParentComponent />
</RecoilRoot>
)
expect(screen.getByTestId('childComponentTestId')).toBeNull();
});
When executing my test I get the following error in my unit test:
TestingLibraryElementError: Unable to find an element by: [data-testid="childComponentTestId"]
I find this error a bit of a paradox, since that is exactly what I want it to be.
note
Passing the data-testid as a prop does not help.
Using .not.toBeInDocument() does not work.
Using .toBeUndefined() does not work either.
You should use queryByTestIt as it returns null if object is not found.
See more on the documentation site.
I have a navbar component that I created for react.
When I call this component on different pages, I need to change the classNames.
For example : I want my "" component to have a different class on one page and a different class on another page.
const ServicesCiso = () => {
return (
<div className="hero">
<NavBar/>
<div className...
How can i add className in this code ?
You can pass the className as props to this component and pass the preferred className on the page you are rendering it
const ServicesCiso = ({ className }) => {
return (
<div className={className}>
<NavBar/>
<div className...
<ServicesCiso className="my-class-name" />
You should pass the className from out side to your component via props however you also need a default class in init case
I have suggestion this library
https://www.npmjs.com/package/classnames ( combine classes )
example
View
- ServicesCiso.js
- ServicesCiso.css
import ./ServicesCiso.css;
import cn from('classnames');
const ServicesCiso = (props) => {
const {className} = props;
return (
<div className={cn('hello', className}}>
</div>
)
}
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
I'm exporting hooks with nested components so that the parent can toggle state of a child. How can I make this toggle work with hooks instead of classic classes or old school functions?
Child Component
export let visible;
export let setVisible = () => {};
export const ToggleSwitch = () => {
const [visible, setVisibile] = useState(false);
return visible && (
<MyComponent />
)
}
Parent
import * as ToggleSwitch from "ToggleSwitch";
export const Parent: React.FC<props> = (props) => {
return (
<div>
<button onClick={() => ToggleSwitch.setVisible(true)} />
</div>
)
}
Error: Linter says [setVisible] is unused variable in the child... (but required in the parent)
You can move visible state to parent like this:
const Child = ({ visible }) => {
return visible && <h2>Child</h2>;
};
const Parent = () => {
const [visible, setVisible] = React.useState(false);
return (
<div>
<h1>Parent</h1>
<Child visible={visible} />
<button onClick={() => setVisible(visible => !visible)}>
Toggle
</button>
</div>
);
};
If you have many child-components you should make more complex logic in setVisible. Put object to useState where properties of that object will be all names(Ids) of child-components
as you know React is one-way data binding so if you wanna pass any props or state you have only one way to do that by passing it from parent to child component and if the logic becomes bigger you have to make it as a global state by using state management library or context API with react hooks use reducer and use effect.
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/