I want add some behavior on a given lifecycle method of a React application without having to define it in every one of them?
I came from Java world and have been trying to use HOC for printing/console at every react component life cycle methods similar to AOP concept in Spring/Javaor you can say universal cross cutting on life cycle methods like componentWillUnmount, componentDidMount, componentWillMount... I want to console component name and lifecyle methods name.
Example
Component B componentWillMount called
Component A componentWillMount called
Component B componentDidMountcalled ...
I have tried to use HOC correct me if am wrong but it seems I will be forced to pass all components through this function.
Extend React lifecycle hook (e.g add a print statement on every ComponentDidMount) similar question was asked before but the solution only prints the parent components life cycle but not the child?
Thank you for your help and I really appreciate if you include a code snippet.
The only solution in React that I can think of would be to create a new base class that extends the base React Component class. Add the lifecycle method into the base class and then every component you create extends this new class if you want it to use the lifecycle method.
class NewBaseClass extends React.Component {
componentDidMount() {
console.log('do something on mount')
}
}
class CustomComponent extends NewBaseClass {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
I havent tested this exact use case let me know if it helps :)
You can implement an HOC similar to the following:
function withLifeCycleLogs(WrappedComponent) {
const Enhanced = class extends React.Component {
componentDidMount() {
console.log(`Component ${WrappedComponent.name} did mount`);
}
componentDidUpdate(prevProps) {
console.log(`Component ${WrappedComponent.name} did update`, {
// Uncomment below lines to inspect props change
// prevProps,
// nextProps: this.props
});
}
componentWillUnmount() {
console.log(`Component ${WrappedComponent.name} will unmount`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
// Wrap the display name for easy debugging
// https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging
Enhanced.displayName = `WithLifeCylceLogs${getDisplayName(WrappedComponent)}`;
// Static Methods Must Be Copied Over
// https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
//
// hoistNonReactStatic(Enhanced, WrappedComponent);
return Enhanced;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || "Component";
}
class Counter extends React.Component {
render() {
return (
<div>
<button onClick={this.props.increment}>Increment</button>
<p>{this.props.counter}</p>
<button onClick={this.props.unmount}>Unmount Counter</button>
</div>
);
}
}
const CounterContainer = withLifeCycleLogs(Counter);
class App extends React.Component {
constructor() {
super();
this.state = {
counter: 0,
counterVisible: true
};
}
increment = () => {
this.setState((state) => ({ ...state, counter: state.counter + 1 }));
};
unmountCounter = () => {
this.setState((state) => ({ ...state, counterVisible: false }));
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{this.state.counterVisible && (
<CounterContainer
counter={this.state.counter}
increment={this.increment}
unmount={this.unmountCounter}
/>
)}
</div>
);
}
}
const AppContainer = withLifeCycleLogs(App);
const rootElement = document.getElementById("root");
ReactDOM.render(
<AppContainer />,
rootElement
);
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<div id="root">
</div>
Yes, you need to pass every component to withLifeCycleLogs function. But this is really simple and does not use much space, check this out:
class AwesomeComponent extends React.Component {
//
}
export default withLifeCylceLogs(AwesomeComponent);
It's like using annotation in Spring (correct me if I'm wrong)
CodeSandbox
Related
i am making a website under react with reactstrap, i have a section that contains charts and a button whose function is to replace said charts with another chart containing more details. however i am struggling to make a concrete code.
i have tried placing the charts in a separate component and have it's content switch through the use of a handleclick function on the button that changes the state of the section (using 'onclick')
i am really not confident in my code's clarity, so i tried reproducing what i did in a simpler matter within fiddle
class hello extends React.Component {
render() {
return (
<h2>hello</h2>
);
}
}
class bye extends React.Component {
render() {
return (
<h2>goodbye</h2>
);
}
}
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<div>
<div>
{this.state.components[hello]}
</div>
<button onClick={this.handleClick}>
switch
{this.setState({components:[<bye />]})}
</button>
</div>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
the div in the "toggle" component is supposed to switch between the components "hello" and "bye"
in effect the current section that is supposed to be displayed ("hello") will be replaced by the other section ("bye") uppon clicking the button under them.
thanks in advance.
If you simply want to toggle between the two components with the button click, you can use conditional rendering.
Change your render method to this:
render(){
return (
<div>
{this.state.isToggleOn?<Hello />:<Bye />}
<button onClick={this.handleClick}>Switch</button>
</div>
}
Also keep your Component's name first character capitalized or react might complain. And using Class based Components is outdated. Hooks are the hot thing right now. So try to use more Functional Components.
Note: My answer assumes you are using babel presets for transpiling jsx and es6 syntax. If not, check out #Colin's answer. It also uses hooks.
why not import all partial views and conditionally render them based on the condition
{condition & <View1/>
There's a few mistakes in your code. Here's an example which does what you want using conditional rendering:
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Hello = () => {
return <h2>hello</h2>;
};
const Bye = () => {
return <h2>bye</h2>;
};
const App = () => {
const [toggled, setToggled] = useState(true);
const handleClick = () => {
setToggled(!toggled);
};
const render = () => {
if (toggled) {
return <Hello />;
}
return <Bye />;
};
return (
<div>
<button onClick={handleClick}>toggle</button>
{render()}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
There are many ways to do it:
Using conditional operator:
{ this.state.isToggleOn?<Hello/>:<Bye/> }
Using if condition:
render() {
let chart;
if(this.state.isToggleOn) {
chart = <Hello/>;
} else {
chart = <Bye/>;
}
return ( <div> { chart } </div>);
}
3 You can use switch case also for conditional rendering. Here it is not well suited as condition is true or false.
I've got a may confusing question because it does not fit standard-behaviour how react and the virtual dom works but i would like to know the answer anyway.
Imagine i've got a simple react-component which is called "Container".
The Container-component has a "div" inside of the render-method which contains another component called "ChildContainer". The "div" which surrounds the "ChildContainer" has the id "wrappingDiv".
Example:
render() {
<Container>
<div id="wrappingDiv">
<ChildContainer/>
</div>
</Container
}
How can i destroy the "ChildContainer"-component-instance and create a completly new one. Which mean the "ComponentWillUnmount" of the old instance is called and the "ComponentDidMount" of the new component is called.
I don't want the old component to update by changing the state or props.
I need this behaviour, because an external library from our partner-company got a libary which change the dom-items and in React i'll get a "Node not found" exception when i Update the component.
If you give the component a key, and change that key when re-rendering, the old component instance will unmount and the new one will mount:
render() {
++this.childKey;
return <Container>
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
The child will have a new key each time, so React will assume it's part of a list and throw away the old one, creating the new one. Any state change in your component that causes it to re-render will force that unmount-and-recreated behavior on the child.
Live Example:
class Container extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
class ChildContainer extends React.Component {
render() {
return <div>The child container</div>;
}
componentDidMount() {
console.log("componentDidMount");
}
componentWillUnmount() {
console.log("componentWillUnmount");
}
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.childKey = 0;
this.state = {
something: true
};
}
componentDidMount() {
let timer = setInterval(() => {
this.setState(({something}) => ({something: !something}));
}, 1000);
setTimeout(() => {
clearInterval(timer);
timer = 0;
}, 10000);
}
render() {
++this.childKey;
return <Container>
{this.state.something}
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Having said that, there may well be a better answer to your underlying issue with the plugin. But the above addresses the question actually asked... :-)
Using hooks, first create a state variable to hold the key:
const [childKey, setChildKey] = useState(1);
Then use the useEffect hook to update the key on render:
useEffect(() => {
setChildKey(prev => prev + 1);
});
Note: you probably want something in the array parameter in useEffect to only update the key if a certain state changes
I have multiple component with similar piece code in lifecycle methods and some similarity in state variables. Is there a way to unify them, by inheriting from one parent or something like that?
constructor(props) {
super(props);
this.state = {
//state properties similar in all components, getting from redux
//state properties specific for this component
}
// same code in many components
}
componentWillMount() {
// same code in many components
// code specific for this component
}
Can I use children methods and props in parent "wrapper" ? Can I change component state from parent ?
You can create Higher Order Component (HOC) for that, basically, you just write component with your same lifecycle method which is repeating, and then in render() function, call this.props.children function with any HOC internal state arguments you want, you can pass the whole state and a setState function as well, so you can change the HOC's state inside the underlying component.
For example:
class HOC extends React.Component {
constructor(props) {
super(props);
state = {
someState: 'foo',
};
}
componentWillMount() {
console.log('i mounted!')
}
render() {
return (
<div>
{this.props.children({ state: this.state, setState: this.setState })}
</div>
)
}
}
const SomeComponent = () =>
<HOC>
{({ state, setState }) => (
<div>
<span>someState value: </span>
<input
value={state.someState}
onChange={e => setState({ someState: e.target.value})}
/>
</div>
)}
</HOC>
You can also do really cool and interesting things with it, like connecting a slice of your redux state whenever you need it:
import { connect } from 'react-redux';
const ProfileState = connect(
state => ({ profile: state.profile }),
null,
)(({
profile,
children
}) => (
<div>
{children({ profile })}
</div>
));
const ProfilePage = () => (
<div>
Your name is:
<ProfileState>
{({ profile }) => (
<span>{profile.name}</span>
)}
</ProfileState>
</div>
);
Here is the full documentation on this technique.
You could create HOCs (Higher Order Components) in that case. It can look like this:
/*
A Higher Order Component is a function,
that takes a Component as Input and returns another Component.
Every Component that gets wrapped by this HOC
will receive `exampleProp`,`handleEvent`,
plus all other props that get passed in.
*/
function WithCommonLogic(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
example: ''
}
}
componentWillMount() {
...
// Same code in many components.
}
callback = () => {
/* Enhanced components can access this callback
via a prop called `handleEvent`
and thereby alter the state of their wrapper. */
this.setState({example: 'some val'})
}
render() {
return <WrappedComponent
exampleProp={this.state.example}
handleEvent={this.callback}
{...this.props}
/>
}
}
// You use it like this:
const EnhancedComponent1 = WithCommonLogic(SomeComponent);
const EnhancedComponent2 = WithCommonLogic(SomeOtherComponent);
Now all the shared logic goes into that HOC, which then wrap all your different components you want to share it with.
See the React Docs for further reading.
If I have a state in the App class, and I want to transfer those values into SecondApp, how do you go about that? I've tried using props but when I console log it, I get undefined.
Excuse the nooby question, I'm fairly new and trying to get my hands dirty, haha.
class App extends Component {
constructor(props) {
super(props)
this.state = {
todo: ['hello', 'hey']
}
}
}
class SecondApp extends Component {
render() {
return (
<p>?</p>
)
}
}
If you are passing the props correctly, they shouldn't turn up undefined. Props would be the correct way to go about this though!
class App extends Component {
constructor(props) {
super(props)
this.state = {
todo: ['hello', 'hey']
}
}
render() {
return <SecondApp testProp={this.state.todo}/>;
}
}
class SecondApp extends Component {
render() {
return <div>this is the prop: {this.props.testProp}</div>;
}
}
If you pass it through like that, you'll see the prop show up as "hellohey", check out the JSFiddle. Next off you'll likely want to render these items in a list, and will need to handle that accordingly. This article will point you in the right direction!
First you need to call SecondApp in App and then pass props.
class SecondApp extends React.Component {
render() {
return ( <
p > {this.props.todo} < /p>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todo: ['hello', 'hey']
}
}
render() {
return ( <
SecondApp todo = {
this.state.todo
} />
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<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="app"></div>
If you mean to pass data from one app to another (that means, you render an app on an id and another app on another id) you can use events.
Whenever the state of the first app updates, you can dispatch an event and put an event listener on the other app, that updates it's state.
This is a common way to share data between independent modules/apps.
You can read more about this when you google "observer subscriber pattern".
Otherwise, if you mean to pass data to a child component, you really should read the react documentation.
From what I understand, HOCs in ReactJS add props to your decorated component, but I want to add methods that can also act on the state.
As an example, I generally never call this.setState() without checking this.isMounted() first. In essence, I want:
export default ComposedComponent => class BaseComponent extends React.Component {
static displayName = "BaseComponent";
constructor(props) {
super(props);
}
//------> I want this method to be available to any ComposedComponent
//------> And it has to act upon the state of ComposedComponent
updateState(obj) {
if (this.isMounted() && obj) {
this.setState(obj);
}
}
render() {
return (
<ComposedComponent {...this.props} {...this.state} />
)
}
}
Say I want to decorate my component Home. So I'd just return it as export default BaseComponent(Home).
But this.updateState() is not available inside Home class. How do I solve this?
Okay, I figured it out. I had spent too much time on this, so I hope this answer could help somebody out as well. Short answer: add the method in your decorator to props, then bind it in your decorated class' constructor.
Here is the code:
export default ComposedComponent => class BaseComponent extends React.Component {
static displayName = "BaseComponent";
constructor(props) {
super(props);
// Note how I am adding this to state
// This will be passed as a prop to your composed component
this.state = {
updateState: this.updateState
}
}
updateState(obj) {
this.setState(obj);
}
render() {
return (
<ComposedComponent {...this.props} {...this.state} />
)
}
}
And here is an example of a class that would use it (I'm using ES7 for simplicity):
#BaseComponent
class Home extends React.Component {
static displayeName = 'Home';
constructor(props) {
super(props);
// And here I am binding to it
this.updateState = this.props.updateState.bind(this);
}
render() {
return (
<div>Hi</div>
)
}
}