How to import component as a button in Nextjs? - javascript

I have this component with an internal link which is used on multiple pages
const index = ({
as: Component,
children,
fullWidth,
secondary,
widget,
...props
}) => {
return (
<Component
{...props}
className={classNames(`${styles.BetTrackerCTA} btn btn-primary`, {
[styles["BetTrackerCTA--full-width"]]: fullWidth,
[styles["BetTrackerCTA--secondary"]]: secondary,
[styles["BetTrackerCTA--widget"]]: widget,
})}
>
{children}
</Component>
);
};
export default index;
when I try to import it as a button in the Nextjs application I get this result, it renders both the styled anchor link and the button in the background
<div className={`${styles.StrategyForm__submit} mt-5`}>
<BetTrackerCTA
as="button"
type="submit"
fullWidth={isMobile}
disabled={!isFormDirty}
>
{t("update_strategy")}
</BetTrackerCTA>
</div>
With the same code in the React application I get the desired result
It looks like the prop "as" for some reason is rendering the component as a button and also the anchor inside the component, which is not happening in the regular React version

I suspect you have different styling in "the regular React version".
Your component will always render <a> because that's hardcoded. If you want to use <a> as a default component to render, you can define a default value for as property and you don't need nested <a>.
For example:
const Comp=({as: Component = 'a', ...rest}) => {
return <Component {...rest}>Foobar</Component>
}
then following usage will render <a> and <button>:
<Comp />
<Comp as="button" />

Related

In React, can I define a functional component within the body of another functional component?

I've started seeing some of my team writing the following code which made me question if we're doing things the right way as I hadn't seen it written like this before.
import * as React from "react";
import "./styles.css";
function UsualExample() {
return <h1>This is the standard example…</h1>
}
export default function App() {
const CustomComponent = (): JSX.Element => <h1>But can I do this?</h1>
return (
<div className="App">
<UsualExample />
<CustomComponent />
</div>
);
}
It seems to render fine and I can't see any immediate adverse effects but is there some fundamental reason why we shouldn't be defining CustomComponent functional component from within another component?
CodeSandbox Example:
https://codesandbox.io/s/dreamy-mestorf-6lvtd?file=/src/App.tsx:0-342
This is not a good idea. Every time App rerenders it will make a brand new definition for CustomComponent. It has the same functionality, but since it's a different reference, react will need to unmount the old one and remount the new one. So you will be forcing react to do extra work on every render, and you'll also be resetting any state inside CustomComponent.
Instead, components should be declared on their own, not inside of rendering, so that they are created just once and then reused. If necessary, you can have the component accept props to customize its behavior:
const CustomComponent = (): JSX.Element => <h1>But can I do this?</h1>
export default function App() {
return (
<div className="App">
<UsualExample />
<CustomComponent />
</div>
);
}
On occasion, you may be doing something repetitive inside a single component and want to simplify the code by having a helper function. That's ok, but then you'll need to call it as a function, not render it as a component.
export default function App() {
const customCode = (): JSX.Element => <h1>But can I do this?</h1>
return (
<div className="App">
{customCode()}
<UsualExample />
{customCode()}
</div>
);
}
With this approach, react will be comparing an <h1> with an <h1>, and so it does not need to remount it.
It's not only a bad idea, it's a horrible idea. You haven't yet discovered composition (it's ok, it takes a while, you'll get there).
The only reason why you want to declare a component inside of another one is to close over a prop (maybe some state too, perhaps) that you want to capture in a child component - here's the trick - pass it as a prop to a new component and you can then declare the new component OUTSIDE.
Turn this:
function UsualExample() {
return <h1>This is the standard example…</h1>
}
export default function App({someProp}) {
// the only reason you're declaring it in here is because this component needs "something" that's available in <App/> - in this case, it's someProp
const CustomComponent = (): JSX.Element => <h1>I'm rendering {someProp}</h1>
return (
<div className="App">
<UsualExample />
<CustomComponent />
</div>
);
}
Into this:
function UsualExample() {
return <h1>This is the standard example…</h1>
}
const CustomComponent = ({someProp}) => <h1>I'm rendering {someProp}></h1>
export default function App({someProp}) {
return (
<div className="App">
<UsualExample />
{ /* but all you have to do is pass it as a prop and now you can declare your custom component outside */ }
<CustomComponent someProp={someProp} />
</div>
);
}

preact createPortal renders multiple times

I am using preact (a small version of react) in my project.
before updating to preactX version I was using Modal component like this and there was no problem with it, this is how my Modal component looked like:
import { Component } from 'preact';
import Portal from 'preact-portal';
export default class Modal extends Component {
componentWillReceiveProps({ isOpen }) {
if (this.state.isOpen !== isOpen) {
this.setState({ isOpen });
}
}
handleClose = () => {
const { onClose } = this.props;
this.setState({ isOpen: false });
onClose && onClose();
};
render({ children, closeIcon, isOpen }) {
return isOpen && (
<Portal into="body">
<Wrapper className="notranslate">
<Overlay />
<Holder>
<Close onClick={this.handleClose}>
<img src={closeIcon || DefaultCloseIcon} alt="Close" />
</Close>
{children}
</Holder>
</Wrapper>
</Portal>
);
}
}
after upgrading to preactX they dropped out Portal component support and change it to createPortal method like the one in react and here where the problem happenes, it renders whenever the props isOpen changes and because of that modals are opening multiple times.
here is my implementation for modal component with createPortal using hooks:
import { createPortal, useState, useEffect, memo } from 'preact/compat';
function Modal({ children, onCloseClick, closeIcon, isOpen }) {
const [isStateOpen, setIsStateOpen] = useState(isOpen);
useEffect(() => {
if (isStateOpen != isOpen) {
setIsStateOpen(isOpen);
}
return () => {
setIsStateOpen(false);
};
}, [isOpen]);
return (
isStateOpen &&
createPortal(
<Wrapper>
<Overlay />
<Holder>
<Close onClick={onCloseClick}>
<img src={closeIcon || DefaultCloseIcon} alt="Close" />
</Close>
{children}
</Holder>
</Wrapper>,
document.body
)
);
}
export default memo(Modal);
and I am using Modal component like that:
<App>
<SomeOtherComponents />
<Modal
isOpen={hasModalOpen}
closeIcon={CloseIcon}
onCloseClick={cancelModal}
>
<div>
some other content here
</div>
</Modal>
</App>
the place where I used my Modal components may render multiple times and that makes Modal component renders too, that was fine before when I was using Portal but when I used createPortal it seems createPortal is not recognizing whether if the Modal component is already in the dom or not.
I suppose that the same would happen in react also.
this isn't a createPortal issue, it's your use of Hooks.
I created a demo based on the code you posted, and it was continuously re-rendering because of the "cleanup" callback returned from your useEffect() hook. That callback was unnecessary, and removing it fixes the whole demo:
https://codesandbox.io/s/preact-createportal-renders-multiple-times-32ehe
I solved the problem by adding shouldComponentUpdate method to the Modal component.
it seems that the component was rendering whenever any prop changes in the parent component even if isOpen prop hasn't changed.
shouldComponentUpdate(nextProps) {
return this.props.isOpen !== nextProps.isOpen;
}
it seems also that createPortal doesn't apply the shallow rendering as the old Portal applies it in versions before preactX

ReactJS - Disabling a component

I need to disable PostList component in its initial state.
import React from 'react';
import PostList from './PostList';
const App = () => {
return (
<div className="ui container">
<PostList />
</div>
);
};
export default App;
Whats the best way to disable (and grey out) a component? Possible solutions are to pass a value as props and then apply it to a ui element, However please keep in mind that PostList may have inner nested components as well. Please share an example.
Since you mentioned in a comment that instead of hiding it, you want to grey it instead. I would use the disabled state and style the component. Since PostList could be nested, we don't know what the props are since you did not specify them.
Also, I assuming that you are not using styled-components.
import React, { useState } from "react";
import PostList from "./PostList";
const App = () => {
const [disabled, setDisabled] = useState(true);
return (
<div className="ui container">
<PostList
style={{
opacity: disabled ? 0.25 : 1,
pointerEvents: disabled ? "none" : "initial"
}}
/>
</div>
);
};
export default App;
There are 2 different ways I like to do something like this.
One way you can do it is by using state
this.state = {
showList: false
}
and than something like
return (
{this.state.showList && <PostList />}
)
Another option is to pass the showList in state as a prop, so something like
return(
<PostList show = {this.state.showList} />
)
and than in PostList something like
return props.show && (your component here)
You can also use conditional classNames, so if you want that component shown, you can throw a className and style it how you normally would, but if not, just throw a display: none. I usually save doing that for replacing a navbar with a dropdown button on smaller screens, but it is another option

How do I access react-navigation from inside a component that isn't the HomeScreen?

I'm building a React Native app. I have imported createStackNavigator from react-navigation. I'm able to get it working on my Home screen - I click a button, it brings me to a new component. This is the code that I'm using to bring it into my Home.js
// src/components/Home/Home
export class Home extends Component {
render() {
return (
<React.Fragment>
<Button
title="Test button"
onPress={() => this.props.navigation.navigate('Roads')}
/>
<StatusBar />
<Header />
<Menu />
</React.Fragment>
);
}
}
const RootStack = createStackNavigator(
{
Home: Home,
Roads: Roads,
},
{
initialRouteName: 'Home',
}
);
export default class App extends React.Component {
render() {
return <RootStack />;
}
}
My Home page takes in a Menu which has a list of MenuItems. I am trying to get the MenuItems to jump to the appropriate pages. When I try to bring in the navigation inside MenuItem.js's render method, like so:
// src/components/Roads/Roads
render() {
const { navigate } = this.props.navigation;
console.log(this.props, "props is here");
I get the following error message:
TypeError: undefined is not an object (evaluating 'this.props.navigation.navigate').
Do I need to pass the navigator down in props to Menu.js and then to MenuItem.js? The docs give examples but it seems to be examples that assume you jam all your code into one file rather than across several components.
Have I set this up correctly?
When using a Navigator from react-navigation only the components you declare as Screens inherit the navigation prop (in your case Home and Roads)
This means that you will need to pass it as a prop to its children as you said:
<Menu navigation={this.props.navigation} />
<MenuItem navigation={this.props.navigation} />
In case anyone is wondering how to navigate from a component that isn't inside a Navigator then I suggest reading this part of the react-navigation documentation
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html

React functional props

Let me show you an example first:
<List hidden={this.state.hideList} />
For this case I would like hidden prop to function in the following way: if value is true – hide component or show otherwise. And List shouldn't be aware about this prop.
I don't know much about React internals, but it seems to be possible. We just need to extended React.Component class. Maybe extending globally with all functional props isn't good idea, but having something like this at the top would be nice:
import { ReactComponent } from 'react'
import extend, { hidden } from 'react-functional-props'
const Component = extend(ReactComponent, hidden)
It reminds me directives in Angular.js, but here we are able to combine them.
You can just return null or undefined in the render method to prevent it from rendering, or use a conditional class to hide it, like this:
return !this.state.hideList && <List />;
And about the
react-functional-props
In React, you can use higher order component (HOC) to achieve what you need, for example:
const Hideable = Component => (props) => {
const { isHidden, ...otherProps } = props;
return !isHidden && (<Component {...otherProps} />);
};
const HideableList = Hideable(List);
...
return (
<div>
<HideableList isHidden={this.state.hideList} /> // The isHidden props is handled by the HOC
</div>
);
But for a simple use case like this, I think just handle the hide and show in the parent component is much more clearer.

Categories

Resources