0

I'm using the React Color lib to create a custom component in my application. My child component is composed by two components of React Colors: CirclePicker and ChromePicker. Both share the colorPicker state variable as value. So, if one change his value, the other will change too.

As you can see on my Child Component, I put one invisible input that shares the same colorPicker value (I don't know if it's the right wat to do it). And my goal here is: when the input change, I could do something, for example: alert('COLOR CHANGED') (this is on my code in handleOnChange function)

Child component:

import React from 'react';
import { CirclePicker, ChromePicker } from 'react-color';
import { Icon } from './../Icon/Icon';
import './color-picker.scss';

export interface ColorPickerProps {
  onChange: (value: string) => void;
}

export function ColorPicker(props: ColorPickerProps) {
  const [colorPicker, showColorPicker] = React.useState(false);
  const [finalColor, changeColor] = React.useState('#fff');
  function handleOnChange() {
    alert('COLOR CHANGED');
    props.onChange(finalColor);
  }
  return (
    <div className="relative-position">
      <input type="text" value={finalColor} onChange={() => handleOnChange} style={{display: "none"}}/>
      <CirclePicker
        color={finalColor}
        onChangeComplete={colore => changeColor(colore.hex)}
        colors={[
          '#004de8',
          '#2ecc71',
          '#ff9300',
          '#62708b',
          '#ff003a',
          '#20396a'
        ]}
        circleSize={24}
      ></CirclePicker>
      <a
        className="btn-select-color"
        onClick={() => showColorPicker(!colorPicker)}
      >
        <Icon viewIcone="ArrowDropDown" style={{ margin: '5px' }} />
      </a>
      {colorPicker ? (
        <span className="chrome-picker">
          <ChromePicker
            color={finalColor}
            onChangeComplete={colore => changeColor(colore.hex)}
            disableAlpha={true}
          />
        </span>
      ) : (
        ''
      )}
    </div>
  );
}

Situations:

I've tried more or less how they've explained here, but no matter what I do, the handleOnChange function is not called and I can't see the alert.

Furthermore, my goal is to use this component in parent, more or less like this: **Parent component: **

<ColorPicker onChange={e => this.state.color = e} />

So, on this way, I could have on parent state the color picked.

I can't get nothing in the Child function, neither having the state updated on the parent component.

Could someone help me? I'm a new user of React :(

2 Answers 2

1

Explanation

The input's value is linked to state. So when state changes, the value changes. BUT, no event was fired on the input. You were trying to use a react event handler on an input that wasn't firing any events; meaning handleOnChange never got called so props.onChange never got called...

Solution

Use useEffect to listen for when the input's value/state value changes. If you use useRef, you can stop your props.onChange from running when the component mounts. Check out the DEMO.

import * as React from "react";
import { FunctionComponent, useState, useEffect, useRef } from "react";
import { render } from "react-dom";
import { CirclePicker, ChromePicker } from "react-color";

const colors = [
  "#004de8",
  "#2ecc71",
  "#ff9300",
  "#62708b",
  "#ff003a",
  "#20396a"
];

export interface ColorPickerProps {
  onChange: (value: string) => void;
}

const ColorPicker: FunctionComponent<ColorPickerProps> = ({ onChange }) => {
  const [colorPicker, showColorPicker] = useState(false);
  const [finalColor, changeColor] = useState("#fff");
  const componentMounted = useRef(true);

  useEffect(() => {
    if (componentMounted.current) {
      componentMounted.current = false;
      console.log(
        "Don't run props.onChange when the component mounts with useRef"
      );
    } else {
      onChange(finalColor);
      alert("finalColor changed via useEffect");
    }
    return () => undefined;
  }, [finalColor]);

  return (
    <div className="relative-position">
      <input type="text" value={finalColor} style={{ display: "none" }} />
      <CirclePicker
        color={finalColor}
        onChangeComplete={colore => changeColor(colore.hex)}
        colors={colors}
        circleSize={24}
      />
      <br />
      <button onClick={() => showColorPicker(!colorPicker)}>click me</button>
      {colorPicker && (
        <span className="chrome-picker">
          <ChromePicker
            color={finalColor}
            onChangeComplete={colore => changeColor(colore.hex)}
            disableAlpha={true}
          />
        </span>
      )}
    </div>
  );
};

const rootElement = document.getElementById("root");
render(
  <ColorPicker
    onChange={() => {
      console.log("onChange");
    }}
  />,
  rootElement
);
Sign up to request clarification or add additional context in comments.

2 Comments

Hi @Ari, your solution worked perfectly. Could you help me understand what I did wrong that stopped the prop onChange?
Happy to help! Added an explanation to the answer. Please accept my answer.
1

useCallback may be a good option, refer to this QA, and document

import React from "react";
import ReactDOM from "react-dom";
import { CirclePicker, ChromePicker } from "react-color";

import "./styles.css";

function App(props) {
  const [colorPicker, showColorPicker] = React.useState(false);
  const [finalColor, changeColor] = React.useState("#fff");

  const handleColorChange = React.useCallback(console.log("Work via callback"));
  return (
    <div className="relative-position">
      <input value={finalColor} onChange={console.log("Work directly")} />
      <input value={finalColor} onChange={handleColorChange} />
      <CirclePicker
        color={finalColor}
        onChangeComplete={colore => changeColor(colore.hex)}
        colors={[
          "#004de8",
          "#2ecc71",
          "#ff9300",
          "#62708b",
          "#ff003a",
          "#20396a"
        ]}
        circleSize={24}
      />
      {colorPicker ? (
        <span className="chrome-picker">
          <ChromePicker
            color={finalColor}
            onChangeComplete={colore => changeColor(colore.hex)}
            disableAlpha={true}
          />
        </span>
      ) : (
        ""
      )}
      <p>{finalColor}</p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit silly-rgb-lf7k6

3 Comments

Thanks for your answer. It's strange, on my application doesn't work neither directly nor via callback.
@VítorResende checkout the online example, and remember to check the console in your browser.
I'm checking the console and you're right, the online example on CodeSandbox is working. I'll check carefully my app, thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.