markusenglund / react-switch

A draggable toggle-switch component for React. Check out the demo at:
https://react-switch.netlify.com/
MIT License
1.33k stars 100 forks source link

Click doesn't toggle switch, only dragging works #35

Closed oquirozm closed 5 years ago

oquirozm commented 5 years ago

I'm attaching a gif showing the behavior. When I click on the switch it doesn't do anything at all. It can only be toggled by dragging it and even then the animation makes a jump. https://gfycat.com/VagueNeglectedBonobo

I'm using React 16.8.1 and react-switch 4.0.1 in a create-react-app project where I manually updated React. Here's how I'm using the switch:

<Switch
          onChange={checked => console.log(checked)}
          width={112}
          height={45}
          onColor={colorPalette.primary}
          offColor={colorPalette.gray}
          offHandleColor={colorPalette.dark}
          handleDiameter={30}
          uncheckedIcon={false}
          checkedIcon={false}
          checked={isRunning}
          onColor="#2e91e9"
          id="normal-switch"
  />

What could be causing the behavior?

markusenglund commented 5 years ago

The components state is based on the checked prop you pass to it. You need to change the value of ´isRunning´ in the onChange handler. It should look something like this if isRunning is part of your state:

<Switch
  onChange={checked => {
    this.setState({isRunning: checked})
  }}
  checked={isRunning}
/>
superzadeh commented 5 years ago

I am running into a similar issue, even though I am changing the state in the onChange handler.

  handleChange(checked) {
    this.setState({ checked });
  }

  render() {
    const { id, children } = this.props;
    const { checked } = this.state;
    return (
      <div>
        <label htmlFor={id}>
          {children}
          <Switch
            className="react-switch"
            uncheckedIcon={false}
            checkedIcon={false}
            onColor={colorPrimary}
            onChange={this.handleChange}
            checked={checked}
            id={id}
          />
        </label>
      </div>
    );
  }

In the GIF below, you can see:

switch-issue

I am also using React 16.8.1 and react-switch 4.0.1.

PS: I've just tried using touch and it does work when touching on the handle.

superzadeh commented 5 years ago

Ok, I may have found why this happens. My switch is within a Chrome Extension, which runs within an iframe. When clicking on the handle, the mouse down event is fired correctly (and handled by the react component), however, it's adding an event listener in window, which doesn't match the window in which the mouse up will be fired. The mouse-up event is then "missed" by the component and it doesn't toggle.

Any idea on how to mitigate this?

oquirozm commented 5 years ago

@markusenglund got it, solved it. I think we can close the issue.

@superzadeh I have no idea how you could work around this, sorry.

markusenglund commented 5 years ago

@superzadeh I tried to put the react-switch demo inside a chrome extension as a popup.

  "browser_action": {
    "default_popup": "index.html"
  }

It works fine for me (Chrome 71, MacOS). What do you do to make it break?

superzadeh commented 5 years ago

The setup is more complex in our case, as our extension is a sidebar running on other websites; to isolate CSS and other things, we run the sidebar within an iframe using react-frame-component.

I've written a repro example here: https://codesandbox.io/s/l5opn8q3p9

markusenglund commented 5 years ago

Thanks for the repro. It seems difficult to support iframes since they have their own window object.

If you really want to use react-switch I guess you could patch it in your project with patch-package to add a window prop to the component, and use it instead of normal window like:

  $onMouseUp(event) {
    const { window } = this.props;
    this.$onDragStop(event);
    window.removeEventListener("mousemove", this.$onMouseMove);
    window.removeEventListener("mouseup", this.$onMouseUp);
  }

But even so, the dragging will only work inside the iframe.

superzadeh commented 5 years ago

Yeah, I ended up rolling my own component; slightly more limited in functionality but at least it works in the iframe.

A nice update for this project would be to allow passing a custom window/callback to get the window object;

import ReactDOM from "react-dom";
import React, { Component } from "react";
import Switch from "react-switch";
import Frame, { FrameContextConsumer } from "react-frame-component";

class SwitchExample extends Component {
  constructor() {
    super();
    this.state = { checked: false };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(checked) {
    this.setState({ checked });
  }

  render() {
    return (
      <Frame>
        <FrameContextConsumer>
          {// Callback is invoked with iframe's window and document instances
          (frameContext) => {
            // Render Children
            return (
              <div>
                <p>Switch with default style</p>
                <Switch
                  window={frameContext.window}
                  onChange={this.handleChange}
                  checked={this.state.checked}
                />
              </div>
            );
          }}
        </FrameContextConsumer>
      </Frame>
    );
  }
}

ReactDOM.render(<SwitchExample />, document.getElementById("root"));