Skip to content
This repository has been archived by the owner on Sep 10, 2022. It is now read-only.

How to change state from outside component #368

Closed
dingchaoyan1983 opened this issue Apr 28, 2017 · 7 comments
Closed

How to change state from outside component #368

dingchaoyan1983 opened this issue Apr 28, 2017 · 7 comments

Comments

@dingchaoyan1983
Copy link

dingchaoyan1983 commented Apr 28, 2017

now I am using recompose to create HOC for my stateless component, and I use withState to hold the inner state in the HOC, and this state is initial from the pass in props. this is work fine in the mount period, but I don't know how to handle the state change from props at the update period, i just want to use lifecycle componentWillReceiveProps to handle that, but nothing happened. so anyone can help me on this?

the code just like below:

compose(
    withState('counter', 'updateCounter', ({ outerCounter }) =>outerCounter ),
    lifecycle({
       componentWillReceiveProps(nextProps) {
           if(this.props.outerCounter !== nextProps.outerCounter) {
                 this.props.updateCounter(nextProps.outerCounter);
           }
        }
     })
)({ counter, updateCounter } => (
<div>
    <div>{counter}</div>
    <button onClick={
        () => updateCounter(counter++)
    }>Add counter</button>
</div>
));

we can see my purpose is to pass a counter the component, and click the button we can change the counter, but we also can change the counter from outside of component, it can also effect the inner value. I don't know how to implement it by using recompose. I know we can update state at lifecycle method componentWillReceiveProps, but seems it hard to write it in recompose way

@istarkov
Copy link
Contributor

Hi,
At first I want to say that state dependent of props is not a good idea and always can be fixed by moving it upper in a tree. Sometimes can be fixed by using props and state to calculate anything you need on the fly without updating state itself.
Why it's not good idea:

  • Having such dependent state means that your app state can't be mapped directly to a view, as some of your component state depends not on data but on data change. (If it does not depend on data change you always can calculate it on the fly so you don't need to update it)
  • Easy to make a mistake as you need to track such state in a few places in a same manner - constructor, receiveProps etc

But, sometimes it's hard to move state upper in a tree, sometimes we need to encapsulate such logic inside component without affecting other components, and sometimes we just have no time, and even I wrote that it's not good idea, I can't say that it's very bad idea.

In recompose the only good way to work with such state is to use mapPropsStream enhancer, as all other ways have drawbacks, so in simplest form it will be something like this

mapPropsStream(props$ => props$.scan( /* here logic to update state */ ))

@dingchaoyan1983
Copy link
Author

yes I agree with you, but sometime we need encapsulate the state with component that will make the component expose less api for other users( developers ), so this is why I need put the state into the component scope, right now i have no good idea to handle it.

@evenchange4
Copy link
Contributor

evenchange4 commented Apr 28, 2017

I want to propose two ways for this use case:

1. [Simple] Just pass outerCounter prop into the component

compose(
  withState('counter', 'updateCounter', 0),
)({ outerCounter = 0, counter, updateCounter } => (
<div>
  <div>{outerCounter + counter}</div>
  <button onClick={
    () => updateCounter(counter++)
  }>Add counter</button>
</div>
));

2. [Prefer] The reactive way

It is very similar to counter example from docs, but we need to get latest props value:

const count$ = props$
  .pluck(['counter'])
  .combineLatest(delta$, (counter, delta) => counter + delta)

DEMO

@dingchaoyan1983
Copy link
Author

@evenchange4 how should I implement it without Rxjs? do you have some idea

@wuct
Copy link
Contributor

wuct commented May 2, 2017

@dingchaoyan1983 Have you tried to use ++counter instead of counter++? It seems like fix your problem.

let counter = 1
console.log(counter++) // log 1
console.log(++counter) // log 3

IMO always writing counter + 1 is a good practice.

@wuct
Copy link
Contributor

wuct commented May 2, 2017

I am going to close this issue now. Feel free to reopen it if you have further questions.

@gabrieldavid98
Copy link

Greetings
i have doubts about how to use mapPropsStream or componentFromStream to update my state on props change. For example i have this:

const Item = withState('checked', 'setChecked', false)(
	({ checked, setChecked }) => (
		<Checkbox checked={checked} onChange={({ target }) => setChecked(target.checked)} />
	)
)

const App = withState('checkedAll', 'setCheckedAll', false)(
	({ checkedAll, setCheckedAll }) => (
		<div>
			<Checkbox checked={checkedAll} onChange={({ target }) => setCheckedAll(target.checked)} />
			<List>
				<Item checked={checkedAll} />
				<Item checked={checkedAll} />
				<Item checked={checkedAll} />
				<Item checked={checkedAll} />
				<Item checked={checkedAll} />
				<Item checked={checkedAll} />
				<Item checked={checkedAll} />
			</List>
		</div>
	)
)

the problem here is when i try to check all my items it does not work, i have tried with mapPropsStream and componentFromStream according to comments above but it doesn't work.
Can you give me more explicit example about how to use mapPropsStream or componentFromStream in this case.

Thanks.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants