Update same variable from two different components - reactjs

I want to use the same state variable say count and update and retrieve the updated one.
I wrote the following code as a higher order component consisting of one button and one label . Both updates the count but they have separate instances. So how can I re-align my code to keep the same copy of the variable count.
const HOC = (InnerComponent) => class extends React.Component{
constructor(){
super();
this.state = {
count: 0
}
}
update(){
this.setState({count: this.state.count + 1})
}
render(){
return(
<InnerComponent
{...this.props}
{...this.state}
update = {this.update.bind(this)}
/>
)
}
};
class App extends React.Component {
render() {
return (
<div>
<Button>Button</Button>
<hr />
<LabelHOC>Label</LabelHOC>
</div>
);
}
}
const Button = HOC((props) => <button onClick={props.update}>{props.children} - {props.count}</button>)
class Label extends React.Component{
render(){
return(
<label onMouseMove={this.props.update}>{this.props.children} - {this.props.count}</label>
)
}
}
const LabelHOC = HOC(Label)
export default App;

You need to do some "thinking-in-react".
React is just a rendering library, it renders the state, so you need to do some thinking about where that state should live. It's common for your scenario to start look at some sort of Flux library that can handle this "one source of truth" (keep you state in one place only), like Redux for example. If you're using Redux then the Redux store would hold the "count" state for both components and they could both update and read it, so that would be my suggestion in the long run. But to solve your immediate question, you must let a higher component hold the state and then of course also modify that state, you do that by passing down the state and a update function as props to the children.
This is snippet of how it could look, just send the state (count) and the update function down to the child components. I excluded HOC component because i think it just adds to your confusion here. But i'm sure you can imagine how it would work. :)
class App extends React.Component {
constructor(){
super();
this.state = {
count: 0
}
this.update = this.update.bind(this); //Bind it once
}
update(){
this.setState({count: this.state.count + 1})
}
render() {
return (
<div>
<Button count={this.state.count} update={this.update}>Button</Button>
<hr />
<LabelHOC count={this.state.count} update={this.update}>Label</LabelHOC>
</div>
);
}
}
Good reads from the docs:
Components and props
Data flows down

Related

How do I implement an onClick method in one child component that updates the text in a sibling component, based on the state in App.js?

Every row in my SideMenuContainer corresponds to an object from schema.json, showing only the name property. The behavior I want is that when a row is clicked, the PaneViewContainer toggles to display the name and other properties of that respective object from the json.
In App.js, the data is passed to SideMenuContainer like so:
render() {
return (
<MainContainer>
<SideMenuContainer genInfoList={this.state.genInfoList}/>
<PaneViewContainer genInfoList={this.state.genInfoList}/>
</MainContainer>
);
}
In SideMenuContainer, every row is populated like this:
render() {
return (
<SideMenuPane>
<SideMenu>
<div>
<h2>GenInfo</h2>
{this.props.genInfoList.map(genInfoElement => {
return (
<SideMenuRow>
{genInfoElement.name}
</SideMenuRow>
);
})}
</div>
</SideMenu>
</SideMenuPane>
);
}
What I want to do is change the genInfoList information being displayed in the PaneViewContainer based on which row is clicked in its sibling, SideMenuContainer.
The entire genInfoList data is being passed to both sibling components from their parent App.js, so I want to change which portion of that data is loaded in the Pane based on the row clicked in the SideMenu.
I thought about using the Context API, but I'm having trouble figuring out how to implement it for this purpose. Any ideas?
If I understand correctly you have your information stored in the parent element of both components then you can just pass a function down as a prop and have all of your logic stored in the parent element.
changeInfoList = id => {
//change your info list based on id or index or whatever
this.setState({
//your new list
})
}
render() {
return (
<MainContainer>
<SideMenuContainer changeInfoList={this.changeInfoList} genInfoList={this.state.genInfoList}/>
<PaneViewContainer genInfoList={this.state.genInfoList}/>
</MainContainer>
);
}
and then call changeInfoList from your component with props
render() {
return (
<SideMenuPane>
<SideMenu>
<div>
<h2>GenInfo</h2>
{this.props.genInfoList.map(genInfoElement => {
return (
<SideMenuRow>
{genInfoElement.name}
<button onClick={this.props.changeInfoList(genInfoElement.id)>CLick Me</button>
</SideMenuRow>
);
})}
</div>
</SideMenu>
</SideMenuPane>
);
}
this is commonplace in react as you should have smart components and dumb components. When you have components not in the same tree or spread far away then the context api is very useful. In your case I don't think its necessary.
Without external state management, you would have to pass down a callback (as props), so the children can update the parent's state.
As the components get far away from each other, this pattern can get annoying (passing down callbacks each time). That's where external state management can help.
Here's a simple (and untested) example using a callback:
class Counter extends React.Component {
constructor() {
super();
this.increment = this.increment.bind(this);
this.state = {count: 0};
}
increment() {
let count = thist.state.count;
this.setState({count: count + 1});
}
render() {
return <div>
<CounterButton increment={this.increment}/>
<CounterDisplay count={this.state.count}/>
</div>;
}
}
class CounterButton extends React.Component {
render() {
let increment = this.props.increment;
return <button onClick={increment}>Plus One</button>;
}
}
class CounterDisplay extends React.Component {
render() {
let count = this.props.count;
return <span>{count}</span>;
}
}

Proper use of React getDerivedStateFromProps

In this example
https://codepen.io/ismail-codar/pen/QrXJgE?editors=1011
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("nextProps", nextProps, "\nprevState", prevState)
if(nextProps.count !== prevState.count)
return {count: nextProps.count};
else
return null;
}
handleIncrease(e) {
this.setState({count: this.state.count + 1})
}
handleDecrease(e) {
this.setState({count: this.state.count - 1})
}
render() {
return <div>
<button onClick={this.handleIncrease.bind(this)}>+</button>
{this.state.count}
<button onClick={this.handleDecrease.bind(this)}>-</button>
</div>;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = { initialCount: 1 };
}
handleChange(e) {
this.setState({initialCount: e.target.value})
}
render() {
return <div>
<Counter count={this.state.initialCount} />
<hr/>
Change initial:<input type="number" onChange={this.handleChange.bind(this)} value={this.state.initialCount} />
</div>
}
}
ReactDOM.render(
<Main/>,
document.getElementById("root")
);
Expected:
Clicking + / - buttons and textbox change must be update count
Currently:
Main component stores initialCount in own state and passes initial count to child Counter Component.
If handleChange triggered from textbox and initialCount is updated also child Counter component is updated correctly because getDerivedStateFromProps static method provides this.
But changing count value in Counter component with updating local state via handleIncrease and handleDecrease methods it prolematic.
Problem is getDerivedStateFromProps re-trigger this time and resets count value. But I did not expect this because Counter component local state updating parent Main component is not updating. UNSAFE_componentWillReceiveProps is working this way.
Summary my getDerivedStateFromProps usage is incorrect or there is another solution for my scenario.
This version https://codepen.io/ismail-codar/pen/gzVZqm?editors=1011 is good with componentWillReceiveProps
Trying to "sync" state to props like you do is extremely error-prone and leads to buggy applications.
In fact even your example with componentWillReceiveProps has a bug in it.
If you re-render the parent component more often, you will lose user input.
Here is a demo of the bug.
Increment counter, then click “demonstrate bug” and it will blow away the counter. But that button’s setState should have been completely unrelated.
This shows why trying to sync state to props is a bad idea and should be avoided.
Instead, try one of the following approaches:
You can make your component fully "controlled" by parent props and remove the local state.
Or, you can make your component fully “uncontrolled”, and reset the child state from the parent when necessary by giving the child a different key.
Both of these approaches are described in this article on the React blog about avoiding deriving state. The blog post includes detailed examples with demos so I strongly recommend to check it out.
I'm not sure if I understood correctly but if you want to use the prop as a "seed" for the initial value to do it in the constructor and you don't even need getDerivedStateFromProps. You actually don't need to duplicate state:
class Counter extends React.Component {
render() {
return <div>
<button onClick={this.props.handleIncrease}>+</button>
{this.props.count}
<button onClick={this.props.handleDecrease}>-</button>
</div>;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = { count: 1 };
}
handleIncrease() {
this.setState(prevState => ({count: prevState.count + 1}))
}
handleDecrease() {
this.setState(prevState => ({count: prevState.count - 1}))
}
render() {
return (
<div>
<Counter count={this.state.count} />
<hr/>
Change initial:
<input
type="number"
handleIncrease={this.handleIncrease.bind(this)}
handleDecrease={this.handleDecrease.bind(this)}
count={this.state.count}
/>
</div>
)
}
}

React dumb component with UI state

I want to build a select input component with React.
The select should be dumb component as it's only a UI Component,
but it also have it's own state (Whether to show the options list, or not)
How should I manage this state?
return (
const Select = (props) => {
<div>
<label>{placeholder}</label>
{/*some toggle state*/ && <div>props.children</div>}
</div>
}
)
thanks!
You should not get too confused by the fact that "it's only a UI component". Anything that has an internal state should be a class.
Your code, a dropdown, is my go-to example of when you should use internal state.
Manage your state with setState().
Now your component is stateless, but you need a stateful.
For example:
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {value: '', toggle: false};
}
render() {
return (
<div>
<label>{placeholder}</label>
{this.state.toggle && <div>this.props.children</div>}
</div>
);
}
}
And you should change state with setState function.
For more information, check this article.
According to your code, what you are rendering is a stateless component, so it will not have any state.
What you can do is pass the state from the parent to this component like so:
constructor(props) {
this.state = { showDumbComponent:true }
}
render() {
<DumbComponent show={this.state.showDumbComponent} />
}

React Redux - pass data down to components via props or connect

I'm working on React Redux app and I have quite fundamental question about some kind of best practises.
I have MainComponent (kind of container) where I'm fetching data on componentDidMount:
class MainComponent extends React.Component {
componentDidMount(){
this.fetchData()
}
fetchData() {
this.props.fetchDataAction()
}
render() {
return (
<div>
<ChildComponent1 />
<ChildComponent2 />
</div>
)
}
}
export default connect(mapStateToProps,{fetchDataAction})(MainComponent)
How to pass fetched data to ChildComponents? What is the best practise? Two possible solutions are (IMHO - maybe more?)
First solution:
class MainComponent extends React.Component {
...
render() {
return (
<div>
<ChildComponent1 dataOne={this.props.data.one} />
<ChildComponent2 dataTwo={this.props.data.two} />
</div>
)
}
...
Second solution - connect ChildComponents to store which is updated by fetchDataAction() in MainComponent:
class ChildComponent1 extends React.Component {
render() {
return (
<div>
{this.props.one}
</div>
)
}
}
function mapStateToProps(state){
return (
one: state.one
)
}
export default connect(mapStateToProps,null)(ChildComponent1)
Now I use first solution when ChildComponents do not fire actions which update store and second solution when they do. But I'm not sure if it is proper approach.
If you have multiple child components and you have to pass a part of fetched data to different child components ; I would suggest keep the parent component as single point of source.
You can try something like:-
class MainComponent extends React.Component {
constructor(){
super()
this.state = {
data : {}
}
}
componentDidMount(){
this.fetchData()
}
fetchData() {
this.props.fetchDataAction()
}
componentWillReceiveProps(nextProps){
//once your data is fetched your nextProps would be updated
if(nextProps.data != this.props.data && nextProps.data.length>0){
//sets your state with you data--> render is called again
this.setState({data:nextProps.data})
}
render() {
//return null if not data
if(this.state.data.length === 0){
return null
}
return (
// it should have keys as one and two in api response
<div>
<ChildComponent1 data={data.one}/>
<ChildComponent2 data={data.two}/>
</div>
)
}
}
function mapStateToProps(state){
return (
data: state
)
}
export default connect(mapStateToProps,{fetchDataAction})(MainComponent)
I feel all logic stays at one place this way. Say if you plan to add to add few more child components in future,you only need to add a line of code above and few changes in API. However if you read in each component you have connect that component to store again which makes it more complex.
So if you dont have any other logic in your child components apart from getting data I would keeping this logic in the parent component.

React.js - setState after calculation based on props

I have a component that receives images as props, performs some calculation on them, and as a result I need to update its class. But if I use setState after the calculation, I get the warning that I shouldn't update state yet... How should I restructure this?
class MyImageGallery extends React.Component {
//[Other React Code]
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
//NEED TO UPDATE STATE HERE?
}
//componentWillUpdate()? componentDidUpdate()? componentWillMount()? componentDidMount()? {
//I CAN ADD CLASS HERE USING REF, BUT THEN THE COMPONENT'S
// FIRST RENDERED WITHOUT THE CLASS AND IT'S ONLY ADDED LATER
}
render() {
return (
<div ref="galleryWrapper" className={GET FROM STATE????}
<ImageGallery
items={this.getImages()}
/>
</div>
);
} }
You should put your logic into componentWillReceiveProps (https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops) so as to do a prop transition before render occurs.
In the end what we did was run the logic in the constructor and then put the class into the initial state:
class MyImageGallery extends React.Component {
constructor(props) {
super(props);
this.getImages = this.getImages.bind(this);
this.images = this.getImages();
this.state = {smallImgsWidthClass: this.smallImgsWidthClass};
}
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
this.smallImgsWidthClass = '[calculation result]';
return this.props.images;
}
render() {
return (
<div className={this.state.smallImgsWidthClass }
<ImageGallery
items={this.images}
/>
</div>
);
}
}

Resources