2

I have a custom component that uses Apollo and React-Select and has two mutations (see below). The react-select is multivalue and needs to be custom because I need an "isSelected" checkbox on it. Not shown in this code, but the initial options list is passed in from the parent container.

The parent Select and its mutation work as expected. However, I'm running into a couple of odd problems with the custom MultiValueContainer. The first is that the first time I select any of the check-boxes, I get an error saying that "Can't call setState (or forceUpdate) on an unmounted component." Note below that I have an empty componentWillUnmount function just to see if it gets called, which it doesn't. But (I assume) as a result of that, when the "toggleThing" mutation is called, the state doesn't have the vars necessary to complete the request. The second time I click it works as expected, with the exception of the second issue.

The second issue is that the onCompleted function on the MultiValueContainer mutation never fires, so even though the server is returning the expected data, it never seems to get back to the mutation and therefore never to the component. The onCompleted function on the parent Select works as expected.

Thanks in advance for any insights anyone might have. Perhaps needless to say, I am relatively new to react/apollo/react-select and apologize in advance for any newbie mistakes. Also, I've tried to scrub and simplify the code so apologies also for any renaming mistakes.

const UPDATE_THINGS = gql`
    mutation UpdateThings(
        $id: ID!
        $newThings: [ThingInput]
    ) {
        updateThings(
            id: $id
            newThings: $newThings
        ) {
            id
        }
    }
`;

const TOGGLE_THING = gql`
    mutation ToggleThing($id: ID!, $isChecked: Boolean) {
        toggleThing(
            id: $id
            isChecked: $isChecked
        ) {
            id
        }
    }
`;

class ThingList extends Component {
    stylesObj = {
        multiValue: base => {
            return {
                ...base,
                display: 'flex',
                alignItems: 'center',
                paddingLeft: '10px',
                background: 'none',
                border: 'none'
            };
        }
    };

    constructor(props) {
        super(props);

        this.state = {
            selectedThings: [],
            selectedThingId: '',
            selectedThingIsChecked: false
        };
    }

    onUpdateComplete = ({ updateThings }) => {
        console.log('onUpdateComplete');
        console.log('...data', updateThings );
        this.setState({ selectedThings: updateThings });
    };

    onToggleThing = (thingId, isChecked, toggleThing) => {
        console.log('onToggleThing, thingId, isChecked');

        this.setState(
                {
                    selectedThingId: thingId,
                    selectedThingIsChecked: isHighPisCheckedoficiency
                },
                () => toggleThing()
            );
    };

    onToggleThingComplete = ({ onToggleThing }) => {
        console.log('onToggleThingComplete ');
        console.log('...data', onToggleThing );
        this.setState({ selectedThings: onToggleThing });
    };

    handleChange = (newValue, actionMeta, updateThings) => {
        this.setState(
            {
                selectedThings: newValue
            },
            () => updateThings()
        );
    };

    isThingSelected = thing=> {
        return thing.isSelected;
    };

    getSelectedThings = selectedThings => {
        console.log('getSelectedSkills');
        return selectedThings ? selectedThings.filter(obj => obj.isSelected) : [];
    };

    componentWillUnmount() {
        console.log('componentWillUnmount');
    }

    render() {
        const self = this;
        const MultiValueContainer = props => {
            // console.log('...props', props.data);
            return (
                <Mutation
                    mutation={ TOGGLE_THING }
                    onCompleted={self.onToggleThingComplete}
                    variables={{
                        id: self.state.selectedThingId,
                        isChecked: self.state.selectedThingIsChecked
                    }}>
                    {(toggleThing, { data, loading, error }) => {
                        if (loading) {
                            return 'Loading...';
                        }

                        if (error) {
                            return `Error!: ${error}`;
                        }

                        return (
                            <div className={'option d-flex align-items-center'}>
                                <input
                                    type={'checkbox'}
                                    checked={props.data.isChecked}
                                    onChange={evt => {
                                        self.onToggleThing(
                                            props.data.id,
                                            evt.target.checked,
                                            toggleIsHighProficiency
                                        );
                                    }}
                                />
                                <components.MultiValueContainer {...props} />
                            </div>
                        );
                    }}
                </Mutation>
            );
        };

        return (
            <Mutation
                mutation={UPDATE_THINGS}
                onCompleted={this.onUpdateComplete}
                variables={{ id: this.id, newThings: this.state.selectedThings}}>
                {(updateThings, { data, loading, error }) => {
                    if (loading) {
                        return 'Loading...';
                    }

                    if (error) {
                        return `Error!: ${error}`;
                    }

                    return (
                        <div>
                            <Select
                                options={this.props.selectedThings}
                                styles={this.stylesObj}
                                isClearable
                                isDisabled={this.props.loading}
                                isLoading={this.props.loading}
                                defaultValue={this.props.selectedThings.filter(
                                    obj => obj.isSelected
                                )}
                                isOptionSelected={this.isOptionSelected}
                                isMulti={true}
                                onChange={(newValue, actionMeta) =>
                                    this.handleChange(
                                        newValue,
                                        actionMeta,
                                        updateThings
                                    )
                                }
                                components={{
                                    MultiValueContainer
                                }}
                            />
                        </div>
                    );
                }}
            </Mutation>
        );
    }
}

export default ThingsList;
Zabba
  • 64,285
  • 47
  • 179
  • 207
doug31415
  • 275
  • 2
  • 9

1 Answers1

3

You are redefining MultiValueContainer on every render, which is not a good practice and may cause unexpected behavior. Try moving it into separate component to see if it helps.

n1stre
  • 5,856
  • 4
  • 20
  • 41
  • Thanks so much. That was exactly the problem, and had nothing to do with either Apollo or react-select. – doug31415 Sep 20 '18 at 16:34