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);
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With