Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'ValueChanging' does not exist on type 'Readonly<{}>'

I'm trying to implement a handler, in React, for a survey implemented in SurveyJS. This is for multiple-choice questions that may have answers like "None of the Above" or "Prefer Not To Answer". If one of those answers is selected, all other answers should be blanked out, and if a different answer is selected, these checkboxes should be cleared out. I'm doing fine with either one of these individually, but having problems with a question where both options are present, specifically when switching back & forth between the two special options.

What I think is happening is that when one answer triggers the handler, and unchecks the other checkbox, it triggers the handler again. My solution is to set a state that indicates when the handler is in the middle of this process, and not do it again at that time.

I got a JS solution for this here: https://github.com/surveyjs/editor/issues/125 - and below is my attempt to convert it to React. (Just the relevant parts of the code included.)

However, on compile, it gives the following error:

ERROR in [at-loader] ./src/components/Survey/SurveyContainer.tsx:55:19 TS2339: Property 'ValueChanging' does not exist on type 'Readonly<{}>'.

I can't find anything about this specific error. Other references to the state (i.e. where I'm setting it) are working. Why can't I read it?

Thanks!

Component:

import * as React from 'react';
import { Component } from 'react';
import { Survey, surveyStrings } from 'survey-react';
import 'whatwg-fetch';
import Marked from '../Marked';
import * as style from './style';

interface Props {
  surveyJson: object;
  complete: boolean;
  resultMarkdown: string;
  surveyTitle: string;
  sendSurveyAnswers: (answers: object[]) => void;
  noneOfTheAboveHandler: (survey: object, options: object) => void;
}

Survey.cssType = 'standard';
surveyStrings.progressText = '{0}/{1}';
surveyStrings.emptySurvey = '';

export default class SurveyComponent extends Component<Props, {}> {
  render() {
    const { surveyJson, sendSurveyAnswers, complete, surveyTitle, resultMarkdown, noneOfTheAboveHandler } = this.props;
    return (
      <style.Wrapper>
         { surveyJson && (!complete) &&
          <style.SurveyWrapper>
            <style.SurveyTitle>{ surveyTitle }</style.SurveyTitle>
            <style.Survey>
              <Survey
                onValueChanged={ noneOfTheAboveHandler }
                css={ style.surveyStyles }
                json={ surveyJson }
                onComplete={ sendSurveyAnswers }
              />
            </style.Survey>
          </style.SurveyWrapper>
         }
         { complete &&
         <style.Results>
           <Marked content={resultMarkdown} />
         </style.Results>
         }
      </style.Wrapper>
    );
  }
}

Container:

import * as React from 'react';
import { Component } from 'react';
import { connect } from 'react-redux';
import SurveyComponent from './SurveyComponent';

interface Props {
  id: string;
  surveyJson: object;
  complete: boolean;
  resultMarkdown: string;
  surveyTitle: string;
  getSurveyQuestions: (id: string) => void;
  sendSurveyAnswers: (answers: object[]) => void;
  noneOfTheAboveHandler: (survey: object, options: object) => void;
  clearSurvey: () => void;
}

class SurveyContainer extends Component<Props, {}> {
  constructor(props) {
    super(props);
    this.state = { ValueChanging: false };
  }

  componentDidMount() {
    this.noneOfTheAboveHandler = this.noneOfTheAboveHandler.bind(this);
    this.props.getSurveyQuestions(this.props.id);
  }

  specialValueSelected(options, specialValue) {
    const { question } = options;
    const prevValue = question.prevValue;
    const index = options.value.indexOf(specialValue);
    this.setState({ ValueChanging: true });
    //has special value selected
    if(index > -1) {
      //special value was selected before
      if(prevValue.indexOf(specialValue) > -1) {
        var value = question.value;
        value.splice(index, 1);
        question.value = value;
      } else {
        //special value select just now
        question.value = [specialValue];
      }
    }
    this.setState({ ValueChanging: false });
    return index > -1;
  }

  noneOfTheAboveHandler(survey, options) {
    const none = 'NA';
    const preferNotToAnswer = 'PN';
    const { question } = options;

    if(this.state.ValueChanging) {
      return;
    }

    if (!question || question.getType() !== 'checkbox') {
      return;
    }

    if (!question.prevValue || !options.value) {
      question.prevValue = options.value;
      return;
    }

    if (!this.specialValueSelected(options,none)) {
      this.specialValueSelected(options,preferNotToAnswer);
    }

    question.prevValue = options.value;
  }

  componentWillUnmount() {
    this.props.clearSurvey();
  }

  render() {
    return (
      <SurveyComponent
        noneOfTheAboveHandler={this.noneOfTheAboveHandler}
        {...this.props}
      />
    );
  }
}

const mapStateToProps = (state, ownProps) => ({
  surveyJson: state.survey.json,
  answers: state.survey.answers,
  resultMarkdown: state.survey.resultMarkdown,
  complete: state.survey.complete,
  surveyTitle: state.page && state.page.data ? state.page.data.title : ''
});

const mapDispatchToProps = dispatch => ({
  getSurveyQuestions: id => dispatch({ type: 'GET_SURVEY_QUESTIONS', id }),
  sendSurveyAnswers: answers => dispatch({ type: 'SEND_SURVEY_ANSWERS', answers: answers.data }),
  clearSurvey: () => dispatch({ type: 'CLEAR_SURVEY' })
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(SurveyContainer);
like image 595
Ian Klinck Avatar asked Sep 20 '17 12:09

Ian Klinck


People also ask

Does not exist in type readonly?

js error "Property does not exist on type 'Readonly<{}>'" occurs when we try to access the props or state of a class component which we haven't typed. To solve the error, use the generic on the React. Component class to type the props or state objects of the class. Here is an example of how the error occurs.

How do I use setState in React class component?

The setState() Method State can be updated in response to event handlers, server responses, or prop changes. This is done using the setState() method. The setState() method enqueues all of the updates made to the component state and instructs React to re-render the component and its children with the updated state.

Can't access property setState this is undefined?

The "cannot read property 'setState' of undefined" error occurs when a class method is called without having the correct context bound to the this keyword. To solve the error, define the class method as an arrow function or use the bind method in the classes' constructor method.

How do you define a state in React typescript?

To use state in functional component use useState hook. This hook returns a tuple of two values. The first one is the current value of the state, the second one is a function to update the state. // into useState as an argument.


1 Answers

The most likely cause for this is that you don't specify the type for your component's state in its class definition, so it defaults to {}. You can fix it by declaring interfaces for the types of props and state and providing these as type arguments to React.Component:

interface MyComponentProps { /* declare your component's props here */ }
interface MyComponentState { ValueChanging :  boolean }

class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
  constructor(props) {
  ...

You could provide the types directly between the < and >, but using interfaces usually leads to more readable code and promotes reuse. To prevent confusion with components it's also a good idea to use lower-case identifiers for the properties on props and state.

like image 149
Oblosys Avatar answered Oct 03 '22 14:10

Oblosys