I am new to React and It really interesting to work on it. As per my current application which is a Quiz app, If user selects any of the options it should change the background color to red or green. I am using the semantic UI and to the menu item, I am passing the data with an on-click event where I am storing the selected answer from the user. But I am unable to change the color wrt the correct answer or wrong answer. Here is the code.
import React, { Component } from 'react';
import {
Container,
Segment,
Item,
Divider,
Button,
Icon,
Message,
Menu,
Header
} from 'semantic-ui-react';
import Swal from 'sweetalert2';
import Loader from '../Loader';
import Countdown from '../Countdown';
import Result from '../Result';
import Offline from '../Offline';
import he from 'he';
import { getRandomNumber } from '../../utils/getRandomNumber';
class Quiz extends Component {
constructor(props) {
super(props);
this.state = {
quizData: null,
isLoading: true,
questionIndex: 0,
correctAnswers: 0,
userSlectedAns: null,
quizIsCompleted: false,
questionsAndAnswers: [],
isOffline: false,
bgColor: ""
};
this.timeTakesToComplete = undefined;
this.setData = this.setData.bind(this);
this.handleItemClick = this.handleItemClick.bind(this);
this.handleNext = this.handleNext.bind(this);
this.timesUp = this.timesUp.bind(this);
this.timeAmount = this.timeAmount.bind(this);
this.renderResult = this.renderResult.bind(this);
this.retakeQuiz = this.retakeQuiz.bind(this);
this.startNewQuiz = this.startNewQuiz.bind(this);
this.resolveError = this.resolveError.bind(this);
}
componentDidMount() {
const { API } = this.props;
fetch(API)
.then(respone => respone.json())
.then(result => setTimeout(() => this.setData(result.results), 1000))
.catch(error => setTimeout(() => this.resolveError(error), 1000));
}
resolveError(error) {
if (!navigator.onLine) {
this.setState({ isOffline: true });
console.log('Connection problem');
} else {
this.setState({ isOffline: true });
console.log('API problem ==> ', error);
}
}
setData(results) {
if (results.length === 0) {
const message =
"The API doesn't have enough questions for your query<br />" +
'(ex. Asking for 50 questions in a category that only has 20).' +
'<br /><br />Please change number of questions, difficulty level ' +
'or type of questions.';
return Swal.fire({
title: 'Oops...',
html: message,
type: 'error',
timer: 10000,
onClose: () => {
this.props.backToHome();
}
});
}
const quizData = results;
const { questionIndex } = this.state;
const outPut = getRandomNumber(0, 3);
const options = [...quizData[questionIndex].incorrect_answers];
options.splice(outPut, 0, quizData[questionIndex].correct_answer);
this.setState({ quizData, isLoading: false, options, outPut });
}
handleItemClick(e, { name }) {
const {
userSlectedAns,
quizData,
questionIndex,
} = this.state;
this.setState({ userSlectedAns: name });
console.log(name);
if (userSlectedAns === he.decode(quizData[questionIndex].correct_answer)) {
this.state.active = 'green';
}
}
handleNext() {
const {
userSlectedAns,
quizData,
questionIndex,
correctAnswers,
questionsAndAnswers
} = this.state;
let point = 0;
if (userSlectedAns === he.decode(quizData[questionIndex].correct_answer)) {
point = 1;
}
questionsAndAnswers.push({
question: he.decode(quizData[questionIndex].question),
user_answer: userSlectedAns,
correct_answer: he.decode(quizData[questionIndex].correct_answer),
point
});
if (questionIndex === quizData.length - 1) {
this.setState({
correctAnswers: correctAnswers + point,
userSlectedAns: null,
isLoading: true,
quizIsCompleted: true,
questionIndex: 0,
options: null,
questionsAndAnswers
});
return;
}
const outPut = getRandomNumber(0, 3);
const options = [...quizData[questionIndex + 1].incorrect_answers];
options.splice(outPut, 0, quizData[questionIndex + 1].correct_answer);
this.setState({
correctAnswers: correctAnswers + point,
questionIndex: questionIndex + 1,
userSlectedAns: null,
options,
outPut,
questionsAndAnswers
});
}
timesUp() {
this.setState({
userSlectedAns: null,
isLoading: true,
quizIsCompleted: true,
questionIndex: 0,
options: null
});
}
timeAmount(timerTime, totalTime) {
this.timeTakesToComplete = {
timerTime,
totalTime
};
}
renderResult() {
setTimeout(() => {
const { quizData, correctAnswers, questionsAndAnswers } = this.state;
const { backToHome } = this.props;
const resultRef = (
<Result
totalQuestions={quizData.length}
correctAnswers={correctAnswers}
timeTakesToComplete={this.timeTakesToComplete}
questionsAndAnswers={questionsAndAnswers}
retakeQuiz={this.retakeQuiz}
backToHome={backToHome}
/>
);
this.setState({ resultRef, questionsAndAnswers: [] });
}, 2000);
}
retakeQuiz() {
const { quizData, questionIndex } = this.state;
const outPut = getRandomNumber(0, 3);
const options = [...quizData[questionIndex].incorrect_answers];
options.splice(outPut, 0, quizData[questionIndex].correct_answer);
this.setState({
correctAnswers: 0,
quizIsCompleted: false,
startNewQuiz: true,
options,
outPut
});
}
startNewQuiz() {
setTimeout(() => {
this.setState({ isLoading: false, startNewQuiz: false, resultRef: null });
}, 1000);
}
render() {
const {
quizData,
questionIndex,
options,
userSlectedAns,
isLoading,
quizIsCompleted,
resultRef,
startNewQuiz,
isOffline
// outPut,
// correctAnswers,
} = this.state;
// console.log(userSlectedAns);
// console.log(questionIndex, outPut);
// console.log('Score ==>', correctAnswers);
if (quizIsCompleted && !resultRef) {
this.renderResult();
// console.log('Redirecting to result');
}
if (startNewQuiz) {
this.startNewQuiz();
}
return (
<Item.Header>
{!isOffline && !quizIsCompleted && isLoading && <Loader />}
{!isOffline && !isLoading && (
<Container>
<Segment>
<Item.Group divided>
<Item>
<Item.Content>
<Item.Extra>
<Header as="h1" block floated="left">
<Icon name="info circle" />
<Header.Content>
{`Question No.${questionIndex + 1} of ${
quizData.length
}`}
</Header.Content>
</Header>
<Countdown
countdownTime={this.props.countdownTime}
timesUp={this.timesUp}
timeAmount={this.timeAmount}
/>
</Item.Extra>
<br />
<Item.Meta>
<Message size="huge" floating>
<b>{`Q. ${he.decode(
quizData[questionIndex].question
)}`}</b>
</Message>
<br />
<Item.Description>
<h3>Please choose one of the following answers:</h3>
</Item.Description>
<Divider />
<Menu vertical fluid size="massive">
{options.map((option, i) => {
let letter;
switch (i) {
case 0:
letter = 'A.';
break;
case 1:
letter = 'B.';
break;
case 2:
letter = 'C.';
break;
case 3:
letter = 'D.';
break;
default:
letter = i;
break;
}
const decodedOption = he.decode(option);
return (
<Menu.Item
key={decodedOption}
name={decodedOption}
active={userSlectedAns === decodedOption}
onClick={this.handleItemClick}
// style={{backgroundColor: this.state.bgColor }}
>
<b style={{ marginRight: '8px' }}>{letter}</b>
{decodedOption}
</Menu.Item>
);
})}
</Menu>
</Item.Meta>
<Divider />
<Item.Extra>
<Button
primary
content="Next"
onClick={this.handleNext}
floated="right"
size="big"
icon="right chevron"
labelPosition="right"
disabled={!userSlectedAns}
/>
</Item.Extra>
</Item.Content>
</Item>
</Item.Group>
</Segment>
<br />
</Container>
)}
{quizIsCompleted && !resultRef && (
<Loader text="Getting your result." />
)}
{quizIsCompleted && resultRef}
{isOffline && <Offline />}
</Item.Header>
);
}
}
export default Quiz;
I checked your handleItemClick function and found that you're mutating the state directly. You should use setState() to update the state, that's the basic thing about React's state mutation.
Please change the following:
if (userSlectedAns === he.decode(quizData[questionIndex].correct_answer)) {
this.state.active = 'green';
}
To this way:
if (userSlectedAns === he.decode(quizData[questionIndex].correct_answer)) {
this.setState({
active: 'green'
});
}
Related
i have a grid with kendoreact that has a dropdown in its cell. i add a pager to the dropdown. but when i click on the pages, the dropdown close.
i have the same combobox in the form and it works good.
i tried a lot and worked on the pager and combobox props. but it was useless. perhaps its a behaviour from the grid. please help
import React, { useRef } from "react";
import { ComboBox } from "#progress/kendo-react-dropdowns";
import { filterBy } from "#progress/kendo-data-query";
import { Pager } from "#progress/kendo-react-data-tools";
import {
IntlProvider,
load,
LocalizationProvider,
loadMessages,
} from "#progress/kendo-react-intl";
import faMessages from "../../../../common/fa.json";
import persianJs from "persianjs";
// import Combobox from "../../../RPKCustomComponent/Combobox";
loadMessages(faMessages, "fa-IR");
const selectField = "selected";
const expandField = "expanded";
const dataItemKey = "value";
const textField = "label";
const subItemsField = "items";
const fields = {
selectField,
expandField,
dataItemKey,
subItemsField,
};
const initialType = "numeric";
const initialPageState = {
skip: 0,
take: 10,
total: 0,
buttonCount: 5,
type: initialType,
info: true,
// pageSizes: true,
previousNext: true,
responsive: true,
};
export default class ComboBoxCell extends React.Component {
state = {
data: [],
pageState: initialPageState,
take: 10,
skip: 0,
mammad: false,
};
handlepageChange = async (event) => {
// event.syntheticEvent.isDefaultPrevented();
// await this.setState({ skip: event.skip, take: event.take, mammad: true });
await this.props.getCombo({
id: this.props.dataItem.Id,
pageIndex: event.skip/10,
pageSize: 10,
search: "",
});
this.setState({mammad:true})
};
handleChange = (e) => {
this.props.onChange({
dataItem: this.props.dataItem,
field: this.props.field,
syntheticEvent: e.syntheticEvent,
value: e.target.value,
});
this.setState({mammad:true})
};
handleOnClick = async (e) => {
setTimeout(async () => {
if (!this.props.data && this.props.getCombo) {
await this.props.getCombo(true, this.state.skip / 10, 10, "");
}
}, 0);
this.setState({mammad:true})
};
componentDidMount() {
let dropdowntree = document.getElementById(`${this.props.id}`);
if (dropdowntree) {
dropdowntree.setAttribute("name", this.props.field);
}
this.textInput = React.createRef();
if (this.props.data?.List) {
let badData = this.props.data?.List.find((item) => {
if (item.value < 0 || item.label == null || item.label == undefined) {
return item;
}
});
if (badData) {
this.props.data.List.map((item) => {
if (item.label == null || item.label == undefined) {
item.label = "";
}
});
console.log("دیتای کمبو از سمت بک مشکل داره");
}
}
if (this.props.data?.List) {
let badData = this.props.data.List.find((item) => {
if (item.value < 0 || item.label == null || item.label == undefined) {
return item;
}
});
if (badData) {
this.props.data.List.map((item) => {
if (
item.label == null ||
item.label == undefined ||
item.label == false
) {
item.label = " ";
}
});
console.log("دیتای کمبو از سمت بک مشکل داره");
}
this.props.data.List.length > 0 &&
this.props.data.List.map(
(item) =>
(item.label =
item.label.length > 0 &&
persianJs(item.label)?.arabicChar()?._str)
);
}
this.setState({
data: this.props.data?.List,
pageState: {
...this.state.pageState,
total: this.props.data?.RecordCount ?? 0,
},
});
}
filterChange = (event) => {
this.setState({
data: this.filterData(event.filter),
});
};
filterData(filter) {
if (this.props.data?.List) {
const data = this.props.data?.List?.slice();
return filterBy(data, filter);
}
}
render() {
// let test=document.getElementsByClassName("comboFooterPageNumber")[0]
// console.log(test);
// document.getElementsByClassName("comboFooterPageNumber")[0]
// .addEventListener("click",(e)=>{
// console.log(e);
// });
// document
// .getElementsByClassName(this.props?.realurl)[0]
// .addEventListener("dblclick", this.AddInCell);
// console.log(this.state.skip);
console.log(this.state.mammad);
const { dataItem, field } = this.props;
const dataValue = dataItem[field] === null ? "" : dataItem[field];
const dataLableValue =
dataItem[field] === null ? "" : dataItem[field]?.label;
let dropValue = this.props.data?.List
? this.props.data?.List.find((c) => c.value == dataValue)
: null;
let dropLabel = this.props.data?.List
? this.props.data?.List.find((c) => c.value == dataValue)?.label
: null;
const listNoDataRender = (element) => {
const noData = (
<h4
style={{
fontSize: "1em",
}}
>
<span
className="k-icon k-i-warning"
style={{
fontSize: "1.5em",
}}
/>
<br />
<br />
موردی یافت نشد!
</h4>
);
return React.cloneElement(element, { ...element.props }, noData);
};
return (
<td className="cell-input">
{dataItem.inEdit && this.props.editable ? (
<ComboBox
{...this.props}
data={this.state.data ?? []}
onChange={this.handleChange}
textField={textField}
dataItemKey={dataItemKey}
filterable={true}
opened={this.state.mammad}
closed={!this.state.mammad}
onFilterChange={this.filterChange}
required={this.props.required}
onOpen={this.handleOnClick}
value={dropValue ?? dataValue}
listNoDataRender={listNoDataRender}
ariaLabelledBy={this.props.ariaLabelledBy ?? ""}
name={this.props.field}
footer={
// (!this.props.clientSide || this.props.allowNewForm) && (
<div>
{/* {this.props.allowNewForm && (
<>
<span className="comboFooter"></span>
<p onClick={others.setshowmodal}>افزودن گزینه جدید</p>
</>
)} */}
{/* {!this.props.clientSide && ( */}
<div className="comboFooterPageNumber">
<LocalizationProvider language="fa">
<Pager
skip={this.state.skip}
take={this.state.take}
total={this.state.pageState.total}
buttonCount={this.state.pageState.buttonCount}
info={this.state.pageState.info}
type={this.state.pageState.type}
previousNext={this.state.pageState.previousNext}
onPageChange={this.handlepageChange}
ref={this.textInput}
/>
</LocalizationProvider>
</div>
{/* )} */}
</div>
// )
}
/>
) : (
dropLabel ?? dataLableValue
)}
</td>
);
}
}
My code pass in a search term and the promise api call returns one record and the data format is as below:
` json api data
0:
{
id:"aff3b4fa-bdc0-47d1-947f-0163ff5bea06"
keyword: somekeyword
URL:"mypage.html"
}
I need to retrieve the URL value, so I try to get URL by using response.data[0].URL. But I receive the error "Unhandled Rejection (TypeError): response.data[0] is undefined". How do I get the URL value? Thanks.
` autocomplete.js
export class Autocomplete extends Component {
state = {
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: "",
suggestions: [],
results: [],
URL: "",
};
componentDidMount() {
this.GetPrograms();
const { userInput } = this.state;
//this.runSearch();
}
GetPrograms = () => {
axios
.get("https://mydomain/GetPrograms/")
.then((response) => {
this.setState({ suggestions: response.data });
});
};
runSearch = async () => {
const response = await axios.get(
"https://mydomain/api/get",
{
params: {
searchTerm: this.state.userInput,
},
}
);
let results = response.data;
console.log("response", results);
this.setState({ results: results, URL: response.data[0].URL });
window.location.href =
"https://mydomain/" + this.state.URL;
};
onChange = (e) => {
const { suggestions } = this.state; //this.props;
const userInput = e.currentTarget.value;
const filteredSuggestions = suggestions.filter(
(suggestion) =>
suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
this.setState({
activeSuggestion: 0,
filteredSuggestions,
showSuggestions: true,
userInput: e.currentTarget.value,
});
};
onClick = (e) => {
this.setState({
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: e.currentTarget.innerText,
});
this.onSearch();
console.log(
"child component clicked and value=" + e.currentTarget.innerText
);
};
onKeyDown = (e) => {
const { activeSuggestion, filteredSuggestions } = this.state;
if (e.keyCode === 13) {
this.setState({
activeSuggestion: 0,
showSuggestions: false,
userInput: filteredSuggestions[activeSuggestion],
});
} else if (e.keyCode === 38) {
if (activeSuggestion === 0) {
return;
}
this.setState({ activeSuggestion: activeSuggestion - 1 });
} else if (e.keyCode === 40) {
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
this.setState({ activeSuggestion: activeSuggestion + 1 });
}
//this.setState({ searchTerm: e.currentTarget.value });
console.log("userinput:" + this.state.userInput);
};
render() {
const {
onChange,
onClick,
onKeyDown,
onKeyPress,
state: {
activeSuggestion,
filteredSuggestions,
showSuggestions,
userInput,
},
} = this;
let suggestionsListComponent;
if (showSuggestions && userInput) {
if (filteredSuggestions.length) {
suggestionsListComponent = (
<ul class="suggestions">
{filteredSuggestions.map((suggestion, index) => {
let className;
if (index === activeSuggestion) {
className = "";
}
return (
<li key={suggestion} onClick={onClick}>
{suggestion}
</li>
);
})}
</ul>
);
} else {
suggestionsListComponent = (
<div class="no-suggestions">
<em>No suggestions</em>
</div>
);
}
}
return (
<div>
<input
id="search-box"
placeholder="Search..."
type="search"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
{suggestionsListComponent}
</div>
);
}
}
export default Autocomplete;
`
The api call returns 0 record that causes the error.
I'am using react autosuggest npm package to get the json data and display it. I want to display only 5 items. How to do it?
Form.js
import React from 'react'
import Autosuggest from 'react-autosuggest';
import cities from 'cities.json';
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
);
};
const getSuggestionValue = suggestion => suggestion.name;
const renderSuggestion = suggestion => (
<div>
{console.log('suggestion', suggestion)}
{suggestion.name}
</div>
);
class Form extends React.Component {
constructor() {
super();
this.state = {
value: '',
suggestions: []
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Search City...',
value,
onChange: this.onChange
};
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<br/>
</div>
)
}
}
export default Form;
I want to render only 5 items, otherwise, computer hangs while loading huge data. Is there any other autocomplete react npm package, since I want only cities and country list. i.e when city is inputted, automatically the city name must be suggested with its relevant country.Any solution or suggestion highly appreciated. Thanks in advance
i modified you're getSuggestions() method a little i guess this should work for you.
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
).slice(0,5);
};
Use the Slice method with start index and last Index
suggestions={suggestions.slice(0, 5)}
import {
React
,Avatar
,axiosbase
} from '../../import-files';
import Autosuggest from 'react-autosuggest';
import './autosuggest.css';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Paper from '#material-ui/core/Paper';
import MenuItem from '#material-ui/core/MenuItem';
let suggestions = [ { label: 'Afghanistan' } ];
function renderInputComponent(inputProps) {
const { classes, inputRef = () => {}, ref, ...other } = inputProps;
return (
<TextField
className={classes.textField}
fullWidth
variant="outlined"
InputProps={{
inputRef: node => {
ref(node);
inputRef(node);
},
classes: {
input: classes.input,
},
}}
{...other}
/>
);
}
function renderSuggestion(suggestion, { query, isHighlighted }) {
return (
<MenuItem selected={isHighlighted} component="div">
<div>
<strong key={String(suggestion.id)} style={{ fontWeight: 300 }}>
<span className="sugg-option">
<span className="icon-wrap">
<Avatar src={suggestion.Poster}></Avatar>
</span>
<span className="name">
{suggestion.Title}
</span>
</span>
</strong>
</div>
</MenuItem>
);
}
function initSuggestions(value) {
suggestions = value;
}
function getSuggestionValue(suggestion) {
return suggestion.Title;
}
function onSuggestionSelected(event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) {
console.log('HandleSuggestion() '+suggestionValue);
}
const styles = theme => ({
root: {
height: 50,
flexGrow: 1,
},
container: {
position: 'relative',
},
suggestionsContainerOpen: {
position: 'absolute',
zIndex: 998,
marginTop: theme.spacing.unit,
left: 0,
right: 0,
overflowY: 'scroll',
maxHeight:'376%'
},
suggestion: {
display: 'block',
},
suggestionsList: {
margin: 0,
padding: 0,
listStyleType: 'none',
},
divider: {
height: theme.spacing.unit * 2,
},
});
class IntegrationAutosuggest extends React.Component {
state = {
single: '',
popper: '',
suggestions: [],
};
componentDidMount() {
initSuggestions(suggestions);
}
// Filter logic
getSuggestions = async (value) => {
const inputValue = value.trim().toLowerCase();
var _filter = JSON.stringify({
filter : inputValue,
});
return await axiosbase.post(`${apiCall}`, _filter);
};
handleSuggestionsFetchRequested = ({ value }) => {
this.getSuggestions(value)
.then(data => {
if (data.Error) {
this.setState({
suggestions: []
});
} else {
const responseData = [];
data.data.itemsList.map((item, i) => {
let File = {
id: item.idEnc,
Title: item.englishFullName +' '+item.arabicFullName,
englishFullName: item.englishFullName,
arabicFullName: item.arabicFullName,
Poster: item.photoPath,
}
responseData.push(File);
});
this.setState({
suggestions: responseData
});
}
})
};
handleSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
});
};
handleChange = name => (event, { newValue }) => {
this.setState({
[name]: newValue,
});
if(event.type=='click'){
if(typeof this.props.handleOrderUserFirstNameChange === "function"){
this.props.handleOrderUserFirstNameChange(newValue);
}
this.state.suggestions.filter(f=>f.Title===newValue).map((item, i) => {
//id
//Title
// Poster
if(typeof this.props.handleUserIDChange === "function"){
this.props.handleUserIDChange(item.id);
}
});
}
};
render() {
const { classes } = this.props;
// console.log('Re-render!!');
// console.log(this.props);
// console.log(this.state.suggestions);
const autosuggestProps = {
renderInputComponent,
suggestions: this.state.suggestions,
onSuggestionsFetchRequested: this.handleSuggestionsFetchRequested,
onSuggestionsClearRequested: this.handleSuggestionsClearRequested,
onSuggestionSelected: this.props.onSelect,
getSuggestionValue,
renderSuggestion,
};
return (
<div className={classes.root}>
<Autosuggest
{...autosuggestProps}
inputProps={{
classes,
placeholder: this.props.placeHolder,
value: this.state.single,
onChange: this.handleChange('single'),
}}
theme={{
container: classes.container,
suggestionsContainerOpen: classes.suggestionsContainerOpen,
suggestionsList: classes.suggestionsList,
suggestion: classes.suggestion,
}}
renderSuggestionsContainer={options => (
<Paper {...options.containerProps} square>
{options.children}
</Paper>
)}
/>
<div className={classes.divider} />
</div>
);
}
}
export default withStyles(styles)(IntegrationAutosuggest);
I cannot get my reducer to update. I can step into the action when I fire this.completeQuiz(id) in my debugger but my state doesn't get updated. Any ideas?
import {
submitAnswer,
resetQuiz,
nextQuestion,
completeQuiz
} from "../actions/videos";
class TestYourselfScreen extends React.Component {
constructor(props) {
super(props);
this.onCapture = this.onCapture.bind(this);
}
completeQuiz = id => {
let video = this.props.videos.find(obj => obj.id == id);
let correctAnswers = video.results.correctAnswers;
const questionsFiltered = video.questions.filter(obj => obj.question != "");
completeQuiz({
id,
totalScore: correctAnswers.length / questionsFiltered.length
});
};
render() {
.....
return (
{questionsFiltered.length > 0 && !completed && (
<View
style={{
flex: 1
}}
>
....
<Button
title={lastQuestion ? "Finish" : "Next"}
buttonStyle={[styles.button]}
disabled={
!results.correctAnswers.includes(current) &&
!results.incorrectAnswers.includes(current)
? true
: false
}
onPress={() =>
lastQuestion ? this.completeQuiz(id) : this.next(id, current)
}
/>
</View>
)}
{completed === true && (
<View
style={{
flex: 1
}}
>
<ViewShot ref="viewShot" options={{ format: "jpg", quality: 0.9 }}>
...
</View>
)}
</ScrollView>
);
}
}
const mapStateToProps = state => {
return {
videos: state.tcApp.videos
};
};
const mapDispatchToProps = dispatch => ({
submitAnswer: data => dispatch(submitAnswer(data)),
resetQuiz: id => dispatch(resetQuiz(id)),
nextQuestion: data => dispatch(nextQuestion(data)),
completeQuiz: data => dispatch(completeQuiz(data))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TestYourselfScreen);
Action:
export const completeQuiz = data => ({
type: "COMPLETE",
data
});
Reducer:
import { trimText } from "../helpers";
export function tcApp(
state = { videos: [], search: { videos: [], term: "" } },
action
) {
switch (action.type) {
....
case "COMPLETE": {
const { completed, totalScore, id } = action.data;
return {
videos: state.videos.map(video =>
video.id === id
? {
...video,
results: {
totalScore
},
completed: true
}
: video
),
search: { term: "", videos: [] }
};
}
default:
return state;
}
}
I think your action is available through props do it as this
completeQuiz = id => {
let video = this.props.videos.find(obj => obj.id == id);
let correctAnswers = video.results.correctAnswers;
const questionsFiltered = video.questions.filter(obj => obj.question != "");
this.props.completeQuiz({
id,
totalScore: correctAnswers.length / questionsFiltered.length
});
};
because we mapDispatchToProps
Hope it helps
I have implemented a little search functionality in my reactjs application.
The Problem is, that my "searchHandler" function is triggered after every single letter the user enters in the textfield... So e.g. for the term "Lorem" my function is fetching 5 times from my api :(
How can I solve this problem?
Here is my code:
const { scaleDown } = transitions;
function searchingFor(term){
return function(x){
return x.title.toLowerCase().includes(term.toLowerCase()) ||
x.body.toLowerCase().includes(term.toLowerCase());
}
}
class ViewAll extends React.Component{
constructor(props){
super(props);
this.state = {
term: '',
mounted: true,
tracks: [],
hasMoreItems: true,
page: 2,
}
this.searchHandler = this.searchHandler.bind(this);
this.focus = this.focus.bind(this);
this.keyPress = this.keyPress.bind(this);
}
loadContent() {
var requestUrl = this.props.url;
fetch(requestUrl + this.state.page + '&_limit=3').then((response)=>{
return response.json();
}) .then((tracks)=>{
this.setState({ tracks: this.state.tracks.concat(tracks)});
this.setState({page: this.state.page + 1});
if(this.state.page === 6){
this.setState({hasMoreItems: false})
}
}).catch((err)=>{
console.log("There has been an error");
});
}
componentDidMount() {
window.scrollTo(0, 0);
var requestUrl = this.props.url;
fetch(requestUrl + '1&_limit=3')
.then((response)=>{
return response.json();
}) .then((data)=>{
this.setState({tracks : data});
})
.catch((err)=>{
console.log("There has been an error");
});
//this.focus();
}
searchHandler(event){
this.setState({term: event.target.value});
var requestUrl = 'https://questdb.herokuapp.com/all?q='
fetch(requestUrl + this.state.term).then((response)=>{
return response.json();
}) .then((tracks)=>{
this.setState({ tracks: this.state.tracks.concat(tracks)});
}).catch((err)=>{
console.log("There has been an error");
});
}
focus() {
this.textInput.focus();
}
keyPress(e){
if(e.keyCode == 13){
console.log('value', e.target.value);
// put the login here
}
}
render() {
const {term, data, tracks} = this.state;
const loader = <div className="loader2"> </div>;
var items = [];
const imageUrl = require(`../assets/Book.jpg`)
tracks.filter(searchingFor(term)).map(function(title, i)
{
items.push(
<div>
<MuiThemeProvider>
<Paper style={{ borderRadius: "2em",
background: '#ffffff'
}} zDepth={1} >
<ItemViewAll
key={title.id}
/>
</Paper>
</MuiThemeProvider>
</div>
);
}, this);
return (
<div>
<Fade in={true} timeout={1000}>
<div >
<MuiThemeProvider>
<TextField hintText='Bot suchen...'
type="Text"
onChange={this.searchHandler}
value={term}
underlineFocusStyle={{borderColor: '#B00020', borderWidth: 3}}
underlineStyle={{borderColor: '#B00020', borderWidth: 1.5, top: '45px'}}
hintStyle={{fontSize: '8.1vw', fontFamily: 'Anton', color: 'rgba(255,255,255,0.9)'}}
inputStyle={{fontSize: '8.1vw', fontFamily: 'Anton', color: '#ffffff'}}
ref={(input) => { this.textInput = input; }}
style={{caretColor: '#ffffff', width: '90%', maginLeft: 'auto', marginRight: 'auto', marginTop: '12%' }}
InputLabelProps={{ shrink: true }}
/>
</MuiThemeProvider>
</div>
</Fade>
<InfiniteScroll
pageStart={1}
loadMore={this.loadContent.bind(this)}
hasMore={this.state.hasMoreItems}
initialLoad={true}
>
{items}
</InfiniteScroll>
</div>
)
}
}
export default ViewAll;
Here you can check out the Website with the broken search function. As you can see the items are shown double or even triple... After the textfield is emptied, the search results should be removed and only the normal fetched ones should be shown.
https://www.genko.de (use the mobile version in chrome)
Thank you :)
Use lodash debounce. It is used for this exact use case
https://stackoverflow.com/questions/48046061/using-lodash-debounce-in-react-to-prevent-requesting-data-as-long-as-the-user-is
Sample:
import React, {Component} from 'react'
import { debounce } from 'lodash'
class TableSearch extends Component {
//********************************************/
constructor(props){
super(props)
this.state = {
value: props.value
}
this.changeSearch = debounce(this.props.changeSearch, 250)
}
//********************************************/
handleChange = (e) => {
const val = e.target.value
this.setState({ value: val }, () => {
this.changeSearch(val)
})
}
//********************************************/
render() {
return (
<input
onChange = {this.handleChange}
value = {this.props.value}
/>
)
}
//********************************************/
}
If you don't need full lodash package you can write it yourself:
function debounce(f, ms) {
let timer = null;
return function (...args) {
const onComplete = () => {
f.apply(this, args);
timer = null;
}
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(onComplete, ms);
};
}
The first arg (f) is your function which should not be performed more often than
second arg (ms) - amount of ms ). So in your case you can write your handler in next way:
handleChange = debounce((e) => {
const val = e.target.value
this.setState({ value: val }, () => {
this.changeSearch(val)
})
}, 1000) // second arg (1000) is your amount of ms