Clicking noise when changing IIRFilter cutoff/gain parameters

Hi! In the JUCE IIRFilterDemo example (In the Example/DSP/ folder), when I change the parameters by moving the sliders, I can hear clicking noise. Is there a way to avoid that? With gain parameter for example, I can just smooth the value change with LinearSmoothedValue. But with IIRFilter, whenever there is a parameter change, I need to regenerate the coefficients. What would be the right way to update the coefficients without introducing clicking noise?

I also notice that the example code is updating the coefficient by calling *ProcessorDuplicator.state = coefficients. Will this have a thread safety issue?

    void updateParameters()
    {
        if (sampleRate != 0.0)
        {
            auto cutoff = static_cast<float> (cutoffParam.getCurrentValue());
            auto qVal   = static_cast<float> (qParam.getCurrentValue());

            switch (typeParam.getCurrentSelectedID())
            {
                case 1:     *iir.state = *IIR::Coefficients<float>::makeLowPass  (sampleRate, cutoff, qVal); break;
                case 2:     *iir.state = *IIR::Coefficients<float>::makeHighPass (sampleRate, cutoff, qVal); break;
                case 3:     *iir.state = *IIR::Coefficients<float>::makeBandPass (sampleRate, cutoff, qVal); break;
                default:    break;
            }
        }
    }

Here is the source code from the JUCE IIRFilter example. Anyone knows how to smooth the cutoff parameter change? Currently when the slider value changes, the example simply calls:

*iir.state = *IIR::Coefficients<float>::makeLowPass  (sampleRate, cutoff, qVal);

Which changes the coefficients value stored in the ProcessorDuplicator. I wonder what is the right way to smooth the coefficients value generation.

using namespace dsp;

//==============================================================================
struct IIRFilterDemoDSP
{
    void prepare (const ProcessSpec& spec)
    {
        sampleRate = spec.sampleRate;

        iir.state = IIR::Coefficients<float>::makeLowPass (sampleRate, 440.0);
        iir.prepare (spec);
    }

    void process (const ProcessContextReplacing<float>& context)
    {
        iir.process (context);
    }

    void reset()
    {
        iir.reset();
    }

    void updateParameters()
    {
        if (sampleRate != 0.0)
        {
            auto cutoff = static_cast<float> (cutoffParam.getCurrentValue());
            auto qVal   = static_cast<float> (qParam.getCurrentValue());

            switch (typeParam.getCurrentSelectedID())
            {
                case 1:     *iir.state = *IIR::Coefficients<float>::makeLowPass  (sampleRate, cutoff, qVal); break;
                case 2:     *iir.state = *IIR::Coefficients<float>::makeHighPass (sampleRate, cutoff, qVal); break;
                case 3:     *iir.state = *IIR::Coefficients<float>::makeBandPass (sampleRate, cutoff, qVal); break;
                default:    break;
            }
        }
    }

    //==============================================================================
    ProcessorDuplicator<IIR::Filter<float>, IIR::Coefficients<float>> iir;

    ChoiceParameter typeParam { { "Low-pass", "High-pass", "Band-pass" }, 1, "Type" };
    SliderParameter cutoffParam { { 20.0, 20000.0 }, 0.5, 440.0f, "Cutoff", "Hz" };
    SliderParameter qParam { { 0.3, 20.0 }, 0.5, 1.0 / std::sqrt(2.0), "Q" };

    std::vector<DSPDemoParameterBase*> parameters { &typeParam, &cutoffParam, &qParam };
    double sampleRate = 0.0;
};

struct IIRFilterDemo    : public Component
{
    IIRFilterDemo()
    {
        addAndMakeVisible (fileReaderComponent);
        setSize (750, 500);
    }

    void resized() override
    {
        fileReaderComponent.setBounds (getLocalBounds());
    }

    AudioFileReaderComponent<IIRFilterDemoDSP> fileReaderComponent;
};