6
\$\begingroup\$

I made a simple state wrapper for a react project I am working on, so I can keep my state at the top component but reason about it at a lower class level.

The example below is very simple, but it works and provides enough of an example of what StateWrapper does, obviously i am using it in reality with a much more complex object at the top state level.

This works even when nesting StateWrapped components, the state always lives at the top level.

I'm not 100% sure that this is the right way to go to be honest, but it seems to work quite nicely so that I can keep a complex component's logic local to its class while still keeping the state at the top level and allowing components to interact with eachother at the top stateful level.

Welcome any feedback, i'm aware it might be a bit of an anti-pattern but i can follow the state down through my classes from the top level in my code easily, and the state all lives at the top component so it debugs nicely in chrome and performs well on my more complex app.

Simple working example is as follows:

import * as React from 'react';

export default class Test extends React.Component {

    constructor(...props) {
        super(...props)

        this.component1 = new WrappedComponent (
            props => this.setState({component1: props}) 
        )

        this.component2 = new WrappedComponent (
            props => this.setState({component2: props})
        )

        //this one passes some initial props
        this.component3 = new WrappedComponent (
            props => this.setState({component3: props}),
            {...this.props}
        )

        this.state = {
            component1: this.component1.state,
            component2: this.component2.state,
            component3: this.component3.state,
        }

    }

    render() {

        return(
            <div>
                <this.component1.component 
                    onClick={() => this.component2.setText("component1")} />

                <this.component2.component 
                    onClick={() => this.component3.setText("component2")} />

                <this.component3.component 
                    onClick={() => this.component1.setText("component3")} />

            </div>
        );
    }

}

class StateWrapper {

    constructor(setState, initialProps = []) {
        this.setState = props => {
            this.state = {...this.state, ...props}
            setState(this.state)
        }
        this.props = initialProps
    }

    render() {
        return(<div>render() not defined</div>)
    }

    component = props => {
        this.props = {...this.props, ...props}
        return this.render()
    }
}

class WrappedComponent extends StateWrapper {

    constructor(...props) {
        super(...props)
        this.state = {
            text: "no text has been set"                
        }
    }

    render() {
        let text = `Received text: ${this.state.text}`
        return(
            <div onClick={this.onClick}>{text}</div>
        )
    }

    onClick = () => {
        this.setState({text: "i got clicked!"})
        this.props.onClick()
    } 

    setText = name => this.setState({text: `${name} set my text`})
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I'm just strating with react and I found this as an answer to my question how to not pollute the main app with all the components states. I really appreciate your post. Great way of creating more structure... and components are even more pluggable \$\endgroup\$ – Jurudocs Oct 11 '17 at 13:42
1
\$\begingroup\$

In general, React disapproves playing with tis internal lifecycle function such has render or setState has it may cause unexpected behavior later on in your app.

Everything that you put in a Component's state is only readable by this component, and nothing else. As the docs will also tell you, the state may only be modified by the setState function, as it will avoid unexpected behaviors in the re-rendering process.

If some components need to modify what each others are going to render, then the values determining the ouput of their render function should be stored in their first common parent component.

A working live implementation :

class Test extends React.Component {
	constructor(props) {
		super(props)
		this.state = {}
	}

	setText = (source, dest) => event => {
		this.setState({ 
			[dest]: `Received text: Component n°${source + 1} set my text`,
			[source]: 'I got clicked!'
	 	})
	}

	render() {
		return (
			<div>
				{[1, 2, 0].map((dest, index) => <WrappedComponent onClick={this.setText(index, dest)} text={this.state[index]}/>)}
			</div>
		);
	}

}

class WrappedComponent extends React.Component { //I left this component stateful for future modifications

	render() {
		const { text, onClick } = this.props
		return (
			<div onClick={onClick}>{text || 'No one defined my text (yet)'}</div>
		)
	}
}

ReactDOM.render(<Test/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.0/umd/react-dom.production.min.js"></script>
<div id='root'/>

Here every component's text is stored in their parent component dynamically.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.