React: How to access component refs from Redux/Flux actions?
Asked Answered
B

2

5

When implementing a state container like Redux or MobX, your state and events are usually moved to a separate class or object that no longer can read the refs.

For example, in a normal component:

import Alert from Alert.js;

class Dummy extends React.Component {
  constructor(props) {
    super(props);

    this.state = { clicked: false }
  }

  handleClick() {
    fetch('api').then(function(){
      this.setState({ clicked: true });
      this._alert.show('Cool response!');
    });
  }

  render() {
    return (
      <div>
        <Alert ref={a => this._alert = a} />
        <Button onClick={this.handleClick}/>
      </div>
    )
  }
}

If I click the button, once the server request is completed, the state is updated and the alert is triggered. Using refs like this is quite common in some modal and alert libraries.

Now, in Redux (or any Flux implementation), that fetch() will live in an action, which lives in a separate file, which doesn't have access to this._alert.

What would be the best way to maintain the functionality without rewriting the external "Alert" library?

Blackout answered 14/3, 2017 at 21:7 Comment(2)
In your action you can return a promise I.E. the api call and you could do something like: this.props.fetchSomething.then(() => this._alert.show('Cool response!') you just have to make sure that you are returning the promise in the action.Grolier
returning a Promise is a good idea, but you need to use some middleware to allow your actions to return promises like redux-promise-middleware or something similarAbsorb
G
11

As a note, I came from your post: https://dannyherran.com/2017/03/react-redux-mobx-takeaways/

This is wrong from the start. Refs should not be shared between components because that couples them. Components should be designed completely decoupled from one another and react based on state given to them.

The problem is that the ref tells you nothing about the state of the component itself, you don't know if it mounted, you don't know if it even exists, so you're playing with something volatile.

So let's decouple everything and leverage the power of react/redux properly, all code should be organized in this manner:

1. Reducer:

Reducer will maintain the display state of the alert. Any component in your entire application now has access to it, independent of refs, so it won't matter if the component actually exists or not.

const DummyPageReducer = function(state, action) 
{
    if (state === undefined) 
    {
        state = 
        {
            alertShow: false
        };
    }

    switch(action.type) 
    {
        case 'ALERT_DISPLAY':
            return Object.assign({}, state, { alertShow: action.display });
    }
    return state;
}

2. Action and Async Action:

Action to adjust the display setting of the alert, and async action to perform the fetch and produce the correct result.

export const ALERT_DISPLAY = 'ALERT_DISPLAY '
export function alertDisplay(display) 
{
    return {
        type: ALERT_DISPLAY,
        display
    }
}

export function showAlert() 
{   
    return function (dispatch) 
    {  
        fetch('api').then(function()
        {
            dispatch(alertDisplay(true))
        });

    }

}

3. Connected Component:

The connected component. No need to share refs, the ref will be used, but the component will react to its given props and set the Alert accordingly.

import Alert from Alert.js;

class Dummy extends React.Component 
{
    constructor(props) 
    {
        super(props);

        this.setAlert = this.setAlert.bind(this);
    }

    setAlert()
    {
        if(this.props.alertShow)
            this._alert.show('Cool response!');
        else
            this._alert.hide();

    }

    componenDidMount()
    {
        setAlert();
    }

    componentDidUpdate(prevProps, prevState)
    {
        if(prevProps.alertShow !== this.props.alertShow)
            setAlert();
    }

    render() 
    {
        return 
        (
            <div>
                <Alert ref={a => this._alert = a} />
                <Button onClick={this.props.showAlert}/>
            </div>
        )
    }
}
Dummy = connect(

    state => 
    ({
        alertShow: state.Dummy.alertShow
    }),

    dispatch =>
    ({
        showAlert: () => dispatch(showAlert(true))
    })

)(Dummy);
Getaway answered 18/8, 2017 at 0:55 Comment(1)
I think you're forgetting to import your showAlert action on the connected Dummy component.Distribution
R
2

First, did you try to use arrow function in handleClick?

handleClick() {
    fetch('api').then(() => {
        this.setState({ clicked: true });
        this._alert.show('Cool response!');
    });
}

That may affect the context of this

On the other hand, you should create two actions if you use Redux. Such as, fetchAPI() and APIfetched().

Rosio answered 18/8, 2017 at 1:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.