React - toggle in stateless component - javascript

I am trying to toggle visiblity of a div in a stateless component like this:
const playerInfo = (props) => {
let isPanelOpen = false;
return (
<div onClick={() => isPanelOpen = !isPanelOpen }>Toggle</div>
{isPanelOpen && <div className="info-panel">
{props.children}
</div>}
);
};
I see that the value of isPanelOpen changes to true, but the panel is not being shown. I assume that is because this is the stateless function that doesn't get called again, so once we return the jsx it will have the value of false, and won't update it later.
Is there a way of fixing this, and avoiding of pasing this single variable as props through 4 more parent stateless components?

You can't tell React to re-render the UI by assigning new value directly to the variable (in your case you did isPanelOpen = !isPanelOpen).
The correct method is to use setState.
But you cannot do it in a stateless component, you must do it in a stateful component, so your code should looks like this
import React, {Component} from 'react';
class playerInfo extends Component {
constructor(props){
super(props);
this.state = {
isPanelOpen: false
}
}
render() {
return (
<div onClick={() => this.setState({isPanelOpen: !this.state.isPanelOpen})}>Toggle</div>
{this.state.isPanelOpen && <div className="info-panel">
{this.props.children}
</div>}
);
}
}
Explanation
Remember two things:
1) Your UI should only bind to this.state.XXXX (for stateful component) or props.XXX (for stateless component).
2) The only way to update UI is by calling setState() method, no other way will trigger React to re-render the UI.
But... how do I update stateless component since it doesn't have the setState method?
ONE ANSWER:The stateless component should be contained in another stateful component.
Example
Let's say your stateless component is called Kid, and you have another stateful component called Mum.
import React, {Component} from 'react';
class Mum extends Component {
constructor(props){
super(props);
this.state = {
isHappy: false
}
}
render() {
return (
<div>
<button onClick={() => this.setState({isHappy: true})}>Eat</button>
<Kid isHappy={this.state.isHappy}/>
</div>
);
}
}
const Kid = (props) => (props.isHappy ? <span>I'm happy</span> : <span>I'm sad</span>);

You can do this by using useState hooks like this:
import { useState } from "react";
function playerInfo () {
const [panel, setPanel] = useState(false);
function toggleButton () {
if(!panel) setPanel(true);
else setPanel(false);
}
return (
<div>
<button onClick={toggleButton}>Toggle</div>
panel ? {this.props.children} : null;
</div>}
);
};
export default playerInfo;

I assume that is because this is the stateless function that doesn't get called again
Basically, the only way to re-render component is to change state or props. :)
So when you change a local variable, React doesn't get notified about it and doesn't start reconcilation.

You can do this with native Javascipt otherwise in React you can not do this with stateless Component :)
const playerInfo = (props) => {
let isPanelOpen = false;
return ( <
div onClick = {
() => {
if (document.getElementsByClassName("info-panel")[0].style.display == 'none') {
isPanelOpen = true;
document.getElementsByClassName("info-panel")[0].style.display = '';
} else {
isPanelOpen = false;
document.getElementsByClassName("info-panel")[0].style.display = 'none';
}
}
} > Toggle < /div> <
div className = "info-panel" > {
this.props.children
} <
/div>
);
};

As of version 16.8 of React you can handle this for stateless components using a hook - in this case useState. In it's simplest form it can be implemented like this:
import React, { useState } from 'react';
const PlayerInfo = (props) => {
const [showPanel, togglePanel] = useState(false);
return (
<div onClick={() => togglePanel(!showPanel) }>Toggle</div>
{showPanel && (
<div className="info-panel">
{props.children}
</div>
)}
</div>
);
};
export default PlayerInfo;

A functional component will not re-render unless a previous parent's state changes and passes down updated properties that propagate down to your functional component. You can pass an onClick handler in from a stateful parent and call this from your stateless component when its onClick is triggered. The parent will control the toggling of the display and pass it in as a prop to the child (see snippet below).
To architect this, you should determine if your HOC (higher order component) should be in charge of UI state. If so, then it can make the determination if its child component should be in an open state or not and then pass that as a property to the child state. If this is a component that should open and close independent of the world around it, then it should probably have its own state. For example, if you are making a tabbed widget, it should probably be controlling its own open and closed states.
class App extends React.Component {
state= {
isOpen: false
}
handleClick = () => {
this.setState({
isOpen: !this.state.isOpen
})
}
render() {
return (
<div>
<YourComponent isOpen={this.state.isOpen} handleClick={this.handleClick} />
</div>
)
}
}
const YourComponent = ({isOpen, handleClick} = props) => {
const onClick = () => {
if (handleClick) {
handleClick();
}
}
return (
<div onClick={onClick}>
{isOpen ?
<h2>it is open</h2>
:
<h2>it is closed</h2>
}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
If your major concern is about passing properties/methods down to too many components, you could create a pure component which will give you access to state but not all the overhead of a React Component subclass. You could also look into using a state management tool like Redux.

Related

How can i use {useState} inside ComponentDidMount in ReactJS

const [bgColor, setBgColor] = useState('black');
class Accelerons extends Component {
constructor(props) {
super(props);
}
componentDidMount = () => {
$(window).on('scroll touchmove', function () {
if ($(document).scrollTop() >= $('.about-us').position().top) {
setBgColor('red');
}
});
};
render() {
return (
<article
style={{
backgroundColor: bgColor,
}}
>
<Overlay />
<Landing landing={DataAccelerons.landing} />
<AboutUs itemColor={itemColor} accelerons={DataAccelerons} />
<Participation itemColor={itemColor} accelerons={DataAccelerons} />
<TeamMembers itemColor={itemColor} accelerons={DataAccelerons} />
<Results itemColor={itemColor} accelerons={DataAccelerons} />
<Footer
accentColor={DataAccelerons.accentColor}
footerColors={DataAccelerons.footerColors}
/>
</article>
);
}
}
export default Accelerons;
This gives me an error that we can't use react hooks at the starting ,but if I declare them in render it won't be recognized by ComponentDidMount function ReactJS
You can use hooks such as useState only inside functional component whereas setState in the lifecycles in Class components. You don't wanna mix two. A typical usage would go something like this:
import React, { useState } from 'react';
function Example() {
const [myVal, setMyVal] = useState('Hello World');
return (
<div>
<p>{myVal}</p>
</div>
);
}
Read More
If you want to go with lifecycle methods (such as componentDidMount), you may consider using seState. Also, you can't use setState inside render method as it would trigger an infinite call.
React hook can be used only functional component whereas componentDidMount is the life cycle method for class based component.

Functional component definition inside class component's render() method, state resets when created through JSX, but not when called directly

If I define a functional component inside of a class component's render() method, then the component's state is getting reset every time the class component's render() method is called. If I call the functional component directly though, the state does not reset.
Look at the following example:
import React from 'react';
import Counter from './Counter'
const MilliCounter = ({name}) => {
return <Counter name={name} initial={1e6} />
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
flag: false
}
}
onButtonClick = (event) => {
this.setState({flag: !this.state.flag});
};
render() {
const HundoCounter = ({name}) => {
return <Counter name={name} initial={100} />
};
return (<div>
<button onClick={this.onButtonClick}>Change State</button>
<div>{`Flag: ${this.state.flag}`}</div>
<HundoCounter name="Component Def Inside render() - Hundo JSX"/>
{HundoCounter({name: 'Component Def Inside render() - Hundo Function Call'})}
<MilliCounter name="Component Def Outside render() - Milli JSX"/>
{MilliCounter({name: 'Component Def Outside render() - Milli Function Call'})}
</div>)
}
}
export default App;
import * as React from 'react'
export default class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: props.initial
}
}
onButtonClick = (event) => {
this.setState({
count: this.state.count + 1
})
};
render() {
return (
<div style={{border: '1px solid black', margin: '1rem', padding: '0.67rem'}}>
<h6>{this.props.name}</h6>
<p>Count: {this.state.count}</p>
<button onClick={this.onButtonClick}>Click Me</button>
</div>
)
}
}
Here's a video showing the demo app in action.
https://i.imgur.com/WfS8DXJ.mp4
As you can see, when the button is clicked it changes the flag to true which forces a re-render. During this the state of the functional component HundoCounter defined with JSX is reset, but not the one that is called directly.
It makes sense to me that the state would reset, because it's creating a new definition of HundoCounter every time render() is called. How come the state for the HundoCounter that's called directly as a function does not get reset?
I believe the reason is because you're re-rendering the parent component, which then resets the initial={100} to set it back to 100, when the child component is re-rendered due to the parent re-render.
Which is the intended behaviour
As for why the second one isn't resetting i don't know, but it seems odd that it is not, since it's value should also be reset
Okay it seems odd. I think it is related with React's reconciliation and diff algorithm. When I add the key property to Counter component it behaves what we expect.
const HundoCounter = ({ name }) => {
console.log("hundo function")
return <Counter key={Math.random()} name={name} initial={100} />
};
I think render() method is called and the diff algorithm recurses on the previous result and the new result and somehow function surround the component and behaves like it is the same component. Btw I like this experiment :)

How to share state between child component (siblings) in ReactJS?

I would like to pass state to a sibling or even a grandparent whatever.
I have 3 components. Inside Header, I have a button with an onClick function to toggle a Dropdown Menu inside Navigation. And by the way, I would like to pass the same state to AnotherComponent.
How to pass state (such as isDropdownOpened) from Header to Navigation and AnotherComponent?
<div>
<Header />
<Navigation />
<div>
<div>
<div>
<AnotherComponent />
</div>
</div>
</div>
</div>
You have different approaches to address this situation.
Keep the state in the top component and pass it to children through props
Use a state container to keep and share your application state among components (e.g. https://redux.js.org/)
Use the new React Context feature. Context provides a way to pass data through the component tree without having to pass props down manually at every level.
That's the exact reason why "React Hooks" have been developed (and hyped by the community 😉), but don't use them yet in production, they are still in early development (alpha) and their specification/implementation might be changed!
You problem can be solved using the awesome “React Context“ API which allows to pass data to components no matter how deep they are nested in the tree.
To get to know to context read the extensive documentation linked above. I'll only explain a small and quick example here:
Create a context component and export the consumer
App.jsx
import React from "react";
// The initial value can be anything, e.g. primitives, object, function,
// components, whatever...
// Note that this is not required, but prevebents errors and can be used as
// fallback value.
const MyContext = React.createContext("anything");
// This component is the so called "consumer" that'll provide the values passed
// to the context component. This is not necessary, but simplifies the usage and
// hides the underlying implementation.
const MyContextConsumer = MyContext.Consumer;
const someData = { title: "Hello World" };
const App = ({ children }) => (
<MyContext.Provider value={someData}>{children}</MyContext.Provider>
);
export { MyContextConsumer };
export default App;
Import the created consumer in any component and use the provided value
AnotherComponent.jsx
import React from "react";
import { MyContextConsumer } from "./App";
const AnotherComponent = () => (
<div>
<MyContextConsumer>{({ title }) => <h1>{title}</h1>}</MyContextConsumer>
</div>
);
export default AnotherComponent;
Render the app with both context components
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import AnotherComponent from "./AnotherComponent";
const Root = () => (
<App>
<AnotherComponent />
</App>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<Root />, rootElement);
The component will render a level 1 heading with the "Hello World" text.
How to pass state (such as isDropdownOpened) from Header to Navigation and AnotherComponent, please ?
You hold the state in an ancestor of Header and pass that state to Haeader, Navigation, and AnotherComponent as props. See State and Lifecycle and Lifting State Up in the documentation.
Example:
const Header = props => (
<div>
<span>Header: </span>
{props.isDropdownOpened ? "Open" : "Closed"}
</div>
);
const Navigation = props => (
<div>
<span>Navigation: </span>
{props.isDropdownOpened ? "Open" : "Closed"}
</div>
);
const AnotherComponent = props => (
<div>
<span>AnotherComponent: </span>
{props.isDropdownOpened ? "Open" : "Closed"}
</div>
);
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
isDropdownOpened: false
};
}
componentDidMount() {
setInterval(() => {
this.setState(({isDropdownOpened}) => {
isDropdownOpened = !isDropdownOpened;
return {isDropdownOpened};
});
}, 1200);
}
render() {
const {isDropdownOpened} = this.state;
return (
<div>
<Header isDropdownOpened={isDropdownOpened} />
<Navigation isDropdownOpened={isDropdownOpened} />
<div>
<div>
<div>
<AnotherComponent isDropdownOpened={isDropdownOpened} />
</div>
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<Wrapper />,
document.getElementById("root")
);
<div id="root"></div>
<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>
There are some other options, which Arnaud usefully provides in his answer.
Like how TJ Said, use the state of the parent component. That way one state is shared by all the sub components, which is what you wanted I presume.
class ExampleParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isDropdownOpened: false
}
}
toggleDropdown() {
this.setState({
isDropdownOpened: !isDropdownOpened
});
}
render() {
return (
<div>
<Header open={isDropdownOpened} toggleDropdown={ this.toggleDropdown }/>
<Navigation open={ isDropdownOpened}/>
<div>
<div>
<div>
<AnotherComponent open={ isDropdownOpened} />
</div>
</div>
</div>
</div>
);
}
class Header extends React.Component {
render() {
return (
<div>
<button onClick={ this.props.toggleDropdown }>TOGGLE ME</button>
{ isDropdownOpened && (
<h1> DROPPED </h1>
}
</div>
);
}
}
You can only use this.state.variableName to access
<ChildComponent data={this.state.name} />
And to pass functions
<ChildComponent data={this.HandleChange} />
First Send the data from the first child to the common parent using callback
function and then send that received data (stored in state in parent component)
to the second child as props.
you can also read this article - https://www.pluralsight.com/guides/react-communicating-between-components

React propTypes component class?

How can I validate that the supplied prop is a component class (not instance)?
e.g.
export default class TimelineWithPicker extends React.PureComponent {
static propTypes = {
component: PropTypes.any, // <-- how can I validate that this is a component class (or stateless functional component)?
};
render() {
return (
<this.props.component {...this.props} start={this.state.start}/>
);
}
}
For anyone using PropTypes >= 15.7.0 a new PropTypes.elementType was added in this pull request and was released on february 10, 2019.
This prop type supports all components (native components, stateless components, stateful components, forward refs React.forwardRef, context providers/consumers).
And it throws a warning when is not any of those elements, it also throws a warning when the prop passed is an element (PropTypes.element) and not a type.
Finally you can use it like any other prop type:
const propTypes = {
component: PropTypes.elementType,
requiredComponent: PropTypes.elementType.isRequired,
};
EDITED: Added React's FancyButton example to codesandbox as well as a custom prop checking function that works with the new React.forwardRef api in React 16.3. The React.forwardRef api returns an object with a render function. I'm using the following custom prop checker to verify this prop type. - Thanks for Ivan Samovar for noticing this need.
FancyButton: function (props, propName, componentName) {
if(!props[propName] || typeof(props[propName].render) != 'function') {
return new Error(`${propName}.render must be a function!`);
}
}
You'll want to use PropTypes.element. Actually... PropType.func works for both stateless functional components and class components.
I've made a sandbox to prove that this works... Figured this was needed considering I gave you erroneous information at first. Very sorry about that!
Working sandbox example!
Here is the code for the test in case link goes dead:
import React from 'react';
import { render } from 'react-dom';
import PropTypes from "prop-types";
class ClassComponent extends React.Component {
render() {
return <p>I'm a class component</p>
}
}
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
const FSComponent = () => (
<p>I'm a functional stateless component</p>
);
const Test = ({ ClassComponent, FSComponent, FancyButton }) => (
<div>
<ClassComponent />
<FSComponent />
<FancyButton />
</div>
);
Test.propTypes = {
ClassComponent: PropTypes.func.isRequired,
FSComponent: PropTypes.func.isRequired,
FancyButton: function (props, propName, componentName) {
if(!props[propName] || typeof(props[propName].render) != 'function') {
return new Error(`${propName}.render must be a function!`);
}
},
}
render(<Test
ClassComponent={ ClassComponent }
FSComponent={ FSComponent }
FancyButton={ FancyButton } />, document.getElementById('root'));

How to pass in a react component into another react component to transclude the first component's content?

Is there a way to pass one component into another react component? I want to create a model react component and pass in another react component in order to transclude that content.
Edit: Here is a reactJS codepen illustrating what I'm trying to do. http://codepen.io/aallbrig/pen/bEhjo
HTML
<div id="my-component">
<p>Hi!</p>
</div>
ReactJS
/**#jsx React.DOM*/
var BasicTransclusion = React.createClass({
render: function() {
// Below 'Added title' should be the child content of <p>Hi!</p>
return (
<div>
<p> Added title </p>
{this.props.children}
</div>
)
}
});
React.renderComponent(BasicTransclusion(), document.getElementById('my-component'));
You can use this.props.children to render whatever children the component contains:
const Wrap = ({ children }) => <div>{children}</div>
export default () => <Wrap><h1>Hello word</h1></Wrap>
Note I provided a more in-depth answer here
Runtime wrapper:
It's the most idiomatic way.
const Wrapper = ({children}) => (
<div>
<div>header</div>
<div>{children}</div>
<div>footer</div>
</div>
);
const App = () => <div>Hello</div>;
const WrappedApp = () => (
<Wrapper>
<App/>
</Wrapper>
);
Note that children is a "special prop" in React, and the example above is syntactic sugar and is (almost) equivalent to <Wrapper children={<App/>}/>
Initialization wrapper / HOC
You can use an Higher Order Component (HOC). They have been added to the official doc recently.
// Signature may look fancy but it's just
// a function that takes a component and returns a new component
const wrapHOC = (WrappedComponent) => (props) => (
<div>
<div>header</div>
<div><WrappedComponent {...props}/></div>
<div>footer</div>
</div>
)
const App = () => <div>Hello</div>;
const WrappedApp = wrapHOC(App);
This can lead to (little) better performances because the wrapper component can short-circuit the rendering one step ahead with shouldComponentUpdate, while in the case of a runtime wrapper, the children prop is likely to always be a different ReactElement and cause re-renders even if your components extend PureComponent.
Notice that connect of Redux used to be a runtime wrapper but was changed to an HOC because it permits to avoid useless re-renders if you use the pure option (which is true by default)
You should never call an HOC during the render phase because creating React components can be expensive. You should rather call these wrappers at initialization.
Note that when using functional components like above, the HOC version do not provide any useful optimisation because stateless functional components do not implement shouldComponentUpdate
More explanations here: https://stackoverflow.com/a/31564812/82609
const ParentComponent = (props) => {
return(
{props.childComponent}
//...additional JSX...
)
}
//import component
import MyComponent from //...where ever
//place in var
const myComponent = <MyComponent />
//pass as prop
<ParentComponent childComponent={myComponent} />
You can pass it as a normal prop: foo={<ComponentOne />}
For example:
const ComponentOne = () => <div>Hello world!</div>
const ComponentTwo = () => (
<div>
<div>Hola el mundo!</div>
<ComponentThree foo={<ComponentOne />} />
</div>
)
const ComponentThree = ({ foo }) => <div>{foo}</div>
Facebook recommends stateless component usage
Source: https://web.archive.org/web/20160608001717/http://facebook.github.io/react/docs/reusable-components.html
In an ideal world, most of your components would be stateless
functions because in the future we’ll also be able to make performance
optimizations specific to these components by avoiding unnecessary
checks and memory allocations. This is the recommended pattern, when
possible.
function Label(props){
return <span>{props.label}</span>;
}
function Hello(props){
return <div>{props.label}{props.name}</div>;
}
var hello = Hello({name:"Joe", label:Label({label:"I am "})});
ReactDOM.render(hello,mountNode);
i prefer using React built-in API:
import React, {cloneElement, Component} from "react";
import PropTypes from "prop-types";
export class Test extends Component {
render() {
const {children, wrapper} = this.props;
return (
cloneElement(wrapper, {
...wrapper.props,
children
})
);
}
}
Test.propTypes = {
wrapper: PropTypes.element,
// ... other props
};
Test.defaultProps = {
wrapper: <div/>,
// ... other props
};
then you can replace the wrapper div with what ever you want:
<Test wrapper={<span className="LOL"/>}>
<div>child1</div>
<div>child2</div>
</Test>
You can pass in a component via. the props and render it with interpolation.
var DivWrapper = React.createClass({
render: function() {
return <div>{ this.props.child }</div>;
}
});
You would then pass in a prop called child, which would be a React component.
Late to the game, but here's a powerful HOC pattern for overriding a component by providing it as a prop. It's simple and elegant.
Suppose MyComponent renders a fictional A component but you want to allow for a custom override of A, in this example B, which wraps A in a <div>...</div> and also appends "!" to the text prop:
import A from 'fictional-tooltip';
const MyComponent = props => (
<props.A text="World">Hello</props.A>
);
MyComponent.defaultProps = { A };
const B = props => (
<div><A {...props} text={props.text + '!'}></div>
);
ReactDOM.render(<MyComponent A={B}/>);
Actually, your question is how to write a Higher Order Component (HOC). The main goal of using HOC is preventing copy-pasting. You can write your HOC as a purely functional component or as a class here is an example:
class Child extends Component {
render() {
return (
<div>
Child
</div>
);
}
}
If you want to write your parent component as a class-based component:
class Parent extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
If you want to write your parent as a functional component:
const Parent = props => {
return (
<div>
{props.children}
</div>
);
}
Here is an example of a parent List react component and whos props contain a react element. In this case, just a single Link react component is passed in (as seen in the dom render).
class Link extends React.Component {
constructor(props){
super(props);
}
render(){
return (
<div>
<p>{this.props.name}</p>
</div>
);
}
}
class List extends React.Component {
render(){
return(
<div>
{this.props.element}
{this.props.element}
</div>
);
}
}
ReactDOM.render(
<List element = {<Link name = "working"/>}/>,
document.getElementById('root')
);
Let's create a Wrapper Component:
export const Wrapper = (props) => {
return(<>
<Menu />
{props.children}
<Footer />
</>
)
}
You can now enclose your new into an existing structure.
You will enclose the Component in a Route for example:
<Route path="/" element={<Wrapper><ExampleComponent /></Wrapper>} />
You can pass your component as a prop and use the same way you would use a component.
function General(props) {
...
return (<props.substitute a={A} b={B} />);
}
function SpecificA(props) { ... }
function SpecificB(props) { ... }
<General substitute=SpecificA />
<General substitute=SpecificB />
you can pass your react component into another component and emit the function from child
import CustomerFilters;
parent:
const handleFilterChange = (value) => {
console.log(value)
}
<DataGrid
contentName="customer"
fetchFilterComponents = {<CustomerFilters onSelectFilter={handleFilterChange} />}
</DataGrid>
child:
CustomerFilters
return (
<select className="filters-dropdown" onChange={onSelectFilter}>
<option>Select Filter</option>
{customerFilterOptions?.map((filter: any) => {
return <option value={filter.value}>{filter.name}</option>;
})}
</select>
)

Categories

Resources