Recompose: How to change state from outside component

Created on 28 Apr 2017  路  7Comments  路  Source: acdlite/recompose

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

Most helpful comment

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 */ ))

All 7 comments

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 */ ))

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.

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

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

@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.

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

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

adrianmcli picture adrianmcli  路  3Comments

nemocurcic picture nemocurcic  路  3Comments

rndmerle picture rndmerle  路  3Comments

istarkov picture istarkov  路  3Comments

finom picture finom  路  3Comments