componentDidMount function in React - reactjs

I am designing a quiz in React, and I am having one final problem with my code.
There are 7 questions in the MongoDB Database which I am pulling data from. The idea is, every time the "Next" button on my quiz is clicked, the next question should load after the user has answered the previous question. Currently, only the first question loads, and I am having trouble loading up the remaining questions. I have a componentDidMount function which I guess would be responsible for loading information from the MongoDB database.
componentDidMount() {
axios.get('/challenge', {
params: {
questionID: "Phish01",
//"Phish0" + this.state.currentQuestion;
},
})
For every question in the database, I have added a field named "questionID" which identifies the question. Since there are 7 questions, each question has an ID of Phish01, Phish02....Phish07.
Obviously looking at the code, only the first question loads up, which is fine. However, I am unsure as to the best method in loading up the remaining questions. I was thinking of using some form of concatenation as seen in the commented out piece of code above, but don't think that's the correct way of going about it.
I also was considering making a new function as seen below, but similar to before, I think I am doing it wrong.
/*setNextQuestion() {
axios.get('/challenge', {
params: {
questionID: "Phish0" + this.state.currentQuestion;
},
})
//questionID: "Phish0" + 1;
if (this.state.currentQuestion !== prevState.currentQuestion) {
this.setState(() => {
return{
disabled: true,
questionID: questionID,
}
});
}}*/
I thought I would also add this code sample in too:
constructor(props) {
super(props);
this.state = {
currentQuestion: 0,
userInput: null, // user has not answered a question yet at the start
points: 0,
options: [],
disabled: true, // Next button is disabled by default
optionEnabled: true, //options should be enabled by default
};
If anyone is able to help or point me towards a resource that would help me in understanding my issue, that would be great!
Thanks!
Added in the rest of the code file:
Class Challenge extends Component {
constructor(props) {
super(props);
this.state = {
currentQuestion: 0,
userInput: null, // user has not answered a question yet at the start
points: 0,
// options : ["Hello"],
options: [],
isLoaded: false,
quizzes: {},
disabled: true, // Next button is disabled by default
optionEnabled: true, //options should be enabled by default
intervalIsSet: false,
gameOver: false,
};
}
// this function below should be responsible for loading the data from the MongoDB database
componentDidMount() {
axios.get('/challenge', {
params: {
questionID: "Phish01",
//"Phish0" + this.state.currentQuestion;
},
})
.then((response) => {
const result = response.data;
console.log(response.data);
this.setState({
isLoaded: true,
quizzes: result,
options: result.options,
answer: result.answer,
answerDetails: result.answerDetails,
wrongAnswer: result.wrongAnswer,
});
}).catch((error) => {
this.setState({
isLoaded: true,
error,
});
});
}
nextQuestion = () => {
const { chosenAnswer, answer, points } = this.state;
alert(this.state.wrongAnswer);
alert(this.state.answerDetails);
if (chosenAnswer === answer) {
this.setState({
points: this.state.points + 1
});
}
this.setState({
currentQuestion: this.state.currentQuestion + 1
});
console.log(this.state.currentQuestion);
};
//this function should be responsible for loading the remaining questions
/*setNextQuestion() {
axios.get('/challenge', {
params: {
questionID: "Phish0" + this.state.currentQuestion;
},
})
//questionID: "Phish0" + 1;
if (this.state.currentQuestion !== prevState.currentQuestion) {
this.setState(() => {
return{
disabled: true,
questionID: questionID,
}
});
}
}*/
lastQuestion = () => {
if (this.state.currentQuestion === 7) { //array starts at 0
this.setState({
gameOver: true
});
}
};
//when user clicks on an option, the "Next" button should then be enabled
checkAnswer(chosenAnswer) {
if (chosenAnswer == this.state.answer) {
alert('You have chosen the correct answer!');
alert('Click Next to continue');
this.setState({
disabled: false,
optionEnabled: false,
});
//alert(this.state.answerDetails);
} else {
alert('You have selected the wrong answer');
alert('Click Next to continue');
this.setState({
disabled: false,
optionEnabled: false,
});
}
}
render() {
const {options, userInput, currentQuestion, gameOver} = this.state;
if (gameOver) {
return (
<div className="result">
<h3>The quiz is over. Final score is {this.state.points} points</h3>
</div>
);
} else {
return (
<div className="quizChallenge">
<h2>{this.state.quizzes.description} </h2>
<table id="t01">
<tbody>
<tr>
{this.state.options.map((option) => {
return (
<td key={option}>
<button
disabled={!this.state.optionEnabled}
onClick={() => this.checkAnswer(this.state.options.indexOf(option))}>
{option}
</button></td>
);
})}
</tr>
</tbody>
</table>
{currentQuestion < 8 &&
<button
className="ui inverted button"
disabled={this.state.disabled}
onClick={this.nextQuestion}
>Next
</button>
}
{currentQuestion === 7 &&
<button
className="ui inverted button"
disabled={this.state.disabled}
onClick={this.lastQuestion}
>Finish Challenge!
</button>
}
</div>
);
}
}
}
export default Challenge;

Related

Applying multiple dynamic filters

After now two full days of trial and error and googling, I am starting to lose my mind and really could use some help. This is my second React hobby-project, so please bear with me if it contains any "no-go's".
Situation:
I call an API, store the data in state (hotel rooms), display all rooms at first. After applying a search, I want to narrow it down further - and that works (with hardcoded values for testing).
I take all available rooms, filter them, store them into another array and let that array then get displayed.
The Problem:
My search is not dynamic. I can narrow the results down, but I can't "bring them back up". For example: if a user wants the rooms narrowed down by price and by "pets allowed", it gets displayed. But if he decides that bringing his pet is not that important and unchecks the filter, the results stay the same as before.
The handleClicks and Buttons are just there to quickly test things, they're not how the end result will be. Also, I've left out the fetchRoomsData(), as it's not important here.
If anyone could help me out here, it would be highly appreciated!
Thanks in advance :)
import React, { Component } from "react";
import Roomcard from "./RoomCard.js";
export default class AllRooms extends Component {
constructor() {
super();
this.state = {
data: undefined,
fetched: false,
roomsToDisplay: [],
hasFilter: {
price: 300,
capacity: 3,
pets: true,
breakfast: false,
},
};
}
componentDidMount() {
this.fetchRoomsData();
}
handleClick1() {
this.filterByPrice();
}
handleClick2() {
this.filterByPets();
}
handleClick3() {
this.filterByCapacity();
}
handleClick4() {
this.filterByBreakfast();
}
handleClick5() {
this.generateAllRooms();
}
filterByPrice() {
let tempArr = [];
this.state.roomsToDisplay.map((room) =>
room.props.price < this.state.hasFilter.price ? tempArr.push(room) : null
);
if (tempArr.length > 0) {
this.setState({ roomsToDisplay: tempArr });
} else {
this.setState({
roomsToDisplay: <h1>There are no matching results.</h1>,
});
}
}
filterByPets() {
let tempArr = [];
this.state.roomsToDisplay.map((room) =>
room.props.pets ? tempArr.push(room) : null
);
if (tempArr.length > 0) {
this.setState({ roomsToDisplay: tempArr });
} else {
this.setState({
roomsToDisplay: <h1>There are no matching results.</h1>,
});
}
}
filterByBreakfast() {
let tempArr = [];
this.state.roomsToDisplay.map((room) =>
room.props.breakfast ? tempArr.push(room) : null
);
if (tempArr.length > 0) {
this.setState({ roomsToDisplay: tempArr });
} else {
this.setState({
roomsToDisplay: <h1>There are no matching results.</h1>,
});
}
}
filterByCapacity() {
let tempArr = [];
this.state.roomsToDisplay.map((room) =>
room.props.capacity > this.state.hasFilter.capacity
? tempArr.push(room)
: null
);
if (tempArr.length > 0) {
this.setState({ roomsToDisplay: tempArr });
} else {
this.setState({
roomsToDisplay: <h1>There are no matching results.</h1>,
});
}
}
generateAllRooms() {
let finalDiv = [];
this.state.data.items.map((room) =>
finalDiv.push(
<Roomcard
price={room.fields.price}
titleImage={`https:${room.fields.images[0].fields.file.url}`}
allImages={room.fields.images.map((image) => image.fields.file.url)}
name={room.fields.name.toUpperCase()}
slug={room.fields.slug}
capacity={room.fields.capacity}
type={room.fields.type}
size={room.fields.size}
pets={room.fields.pets}
breakfast={room.fields.breakfast}
featured={room.fields.featured}
description={room.fields.description}
extras={room.fields.extras}
key={Math.random() * 1000}
/>
)
);
this.setState({ roomsToDisplay: finalDiv });
}
render() {
return (
<>
<div className="search-field-outer-box">
<button onClick={() => this.handleClick1()}> Filter By Price </button>
<button onClick={() => this.handleClick2()}> Filter By Pets </button>
<button onClick={() => this.handleClick3()}> Filter By capacity </button>
<button onClick={() => this.handleClick4()}> Filter By breakfast </button>
<button onClick={() => this.handleClick5()}> Reset Filter </button>
</div>
{this.state.data ? (
<div className="room-card-container">{this.state.roomsToDisplay}</div>
) : undefined}
</>
);
}
}
👋 Welcome to SO
First of all, don't store jsx elements in the state, prefer to store only values and create the jsx at render time.
Now, what I would do is to have the whole dataset in a state variable (and never modify it) and another for the filtered data
this.state = {
data:[],
filteredData:[]
};
// Here at some point when retrieving the data,
// this.setState({data: fetchedData, filteredData: fetchedData});
filterByBreakfast() {
const dataFiltered = // your code to filter
this.setState({
filterdData: dataFiltered,
});
}
resetFilters() {
// Reset the Filtered Data
this.setState({
filterdData: this.state.data,
});
}
render() {
return {
<div>
<div>
<button onClick={this.filterByBreakfast}> Filter By breakfast </button>
<button onClick={this.resetFilters}> Reset Filter </button>
</div>
<div>
{filteredData.length > 0 ? filteredData.map(item => <div>{item}</div>) : <div>No results</div>}
</div>
</div>
}
}

React checkboxes. State is late when toggling the checkboxes

I have a group of 3 checkboxes and the main checkbox for checking those 3 checkboxes.
When I select all 3 checkboxes I want for main checkbox to become checked.
When I check those 3 checkboxes nothing happens but when I then uncheck one of those trees the main checkbox becomes checked.
Can someone explain to me what actually is happening behind the scenes and help me somehow to solve this mystery of React state? Thanks!
Here is a code snnipet:
state = {
data: [
{ checked: false, id: 1 },
{ checked: false, id: 2 },
{ checked: false, id: 3 }
],
main: false,
}
onCheckboxChange = id => {
const data = [...this.state.data];
data.forEach(item => {
if (item.id === id) {
item.checked = !item.checked;
}
})
const everyCheckBoxIsTrue = checkbox.every(item => item === true);
this.setState({ data: data, main: everyCheckBoxIsTrue });
}
onMainCheckBoxChange = () => {
let data = [...this.state.data];
data.forEach(item => {
!this.state.main ? item.checked = true : item.checked = false
})
this.setState({
this.state.main: !this.state.main,
this.state.data: data,
});
}
render () {
const checkbox = this.state.data.map(item => (
<input
type="checkbox"
checked={item.checked}
onChange={() => this.onCheckboxChange(item.id)}
/>
))
}
return (
<input type="checkbox" name="main" checked={this.state.main} onChange={this.onMainCheckBoxChange} />
{checkbox}
)
I can't make a working code snippet based on the code you provided, one of the issues was:
const everyCheckBoxIsTrue = checkbox.every(item => item === true);
where checkbox is not defined.
However, I think you confused about using the old state vs the new state, it'd be simpler to differentiate if you name it clearly, e.g.:
eventHandler() {
const { data } = this.state; // old state
const newData = data.map(each => ...); // new object, soon-to-be new state
this.setState({ data }); // update state
}
Here's a working example for your reference:
class App extends React.Component {
state = {
data: [
{ checked: false, id: 1 },
{ checked: false, id: 2 },
{ checked: false, id: 3 }
],
main: false,
}
onCheckboxChange(id) {
const { data } = this.state;
const newData = data.map(each => {
if (each.id === id) {
// Toggle the previous checked value
return Object.assign({}, each, { checked: !each.checked });
}
return each;
});
this.setState({
data: newData,
// Check if every checked box is checked
main: newData.every(item => item.checked === true),
});
}
onMainCheckBoxChange() {
const { main, data } = this.state;
// Toggle the previous main value
const newValue = !main;
this.setState({
data: data.map(each => Object.assign({}, each, { checked: newValue })),
main: newValue,
});
}
render () {
const { data, main } = this.state;
return (
<div>
<label>Main</label>
<input
type="checkbox"
name="main"
// TODO this should be automatically checked instead of assigning to the state
checked={main}
onChange={() => this.onMainCheckBoxChange()}
/>
{
data.map(item => (
<div>
<label>{item.id}</label>
<input
type="checkbox"
checked={item.checked}
onChange={() => this.onCheckboxChange(item.id)}
/>
</div>
))
}
</div>
);
}
}
ReactDOM.render(
<App />
, document.querySelector('#app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Side note: You might want to consider not to use the main state
You shouldn't be storing state.main to determine whether every checkbox is checked.
You are already storing state that determines if all checkboxes are checked, because all checkboxes must be checked if every object in state.data has checked: true.
You can simply render the main checkbox like this:
<input
type="checkbox"
name="main"
checked={this.state.data.every(v => v.checked)}
onChange={this.onMainCheckBoxChange}
/>;
The line this.state.data.every(v => v.checked) will return true if all of the checkboxes are checked.
And when the main checkbox is toggled, the function can look like this:
onMainCheckBoxChange = () => {
this.setState(prev => {
// If all are checked, then we want to uncheck all checkboxes
if (this.state.data.every(v => v.checked)) {
return {
data: prev.data.map(v => ({ ...v, checked: false })),
};
}
// Else some checkboxes must be unchecked, so we check them all
return {
data: prev.data.map(v => ({ ...v, checked: true })),
};
});
};
It is good practice to only store state that you NEED to store. Any state that can be calculated from other state (for example, "are all checkboxes checked?") should be calculated inside the render function. See here where it says:
What Shouldn’t Go in State? ... Computed data: Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation within render(). For example, if you have an array of list items in state and you want to render the count as a string, simply render this.state.listItems.length + ' list items' in your render() method rather than storing it on state.

Interupt code and wait for user interaction in a loop - React

I am trying to implement an "add all" button in my react app. to do that, i pass this function to the onClick method of the button :
for (element in elements) {
await uploadfunction(element)
}
const uploadfunction = async (element) => {
if (valid) {
// await performUpload(element)
}
else if (duplicate) {
//show dialog to confirm upload - if confirmed await performUpload(element)
}
else {
// element not valid set state and show failed notification
}
}
const performUpload = async (element) => {
// actual upload
if(successful){
// set state
}else{
// element not successful set state and show failed notification
}
}
the uploadfunction can have three different behaviors :
Add the element to the database and update the state
Fail to add the element and update the state
Prompt the user with the React Dialog component to ask for confirmation to add duplicat element and update the state accordingly
My problem now is since i'm using a for loop and despite using Async/await , i can't seem to wait for user interaction in case of the confirmation.
The behavior i currently have :
The for loop move to the next element no matter what the result
The Dialog will show only for a second and disappear and doesn't wait for user interaction
Wanted behavior:
Wait for user interaction (discard/confirm) the Dialog to perform the next action in the loop.
How can i achieve that with React without Redux ?
Here is an example of a component that might work as an inspiration for you.
You might split it in different components.
class MyComponent extends Component {
state = {
items: [{
// set default values for all booleans. They will be updated when the upload button is clicked
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_1',
}, {
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_1',
}, {
isValid: true,
isDuplicate: false,
shouldUploadDuplicate: false,
data: 'element_2',
}],
performUpload: false,
};
onUploadButtonClick = () => {
this.setState(prevState => ({
...prevState,
items: prevState.items.map((item, index) => ({
isValid: validationFunction(),
isDuplicate: prevState.items.slice(0, index).some(i => i.data === item.data),
shouldUploadDuplicate: false,
data: item.data
})),
performUpload: true,
}), (nextState) => {
this.uploadToApi(nextState.items);
});
};
getPromptElement = () => {
const firstDuplicateItemToPrompt = this.getFirstDuplicateItemToPrompt();
const firstDuplicateItemIndexToPrompt = this.getFirstDuplicateItemIndexToPrompt();
return firstDuplicateItemToPrompt ? (
<MyPrompt
item={item}
index={firstDuplicateItemIndexToPrompt}
onAnswerSelect={this.onPromptAnswered}
/>
) : null;
};
getFirstDuplicateItemToPrompt = this.state.performUpload
&& !!this.state.items
.find(i => i.isDuplicate && !i.shouldUploadDuplicate);
getFirstDuplicateItemIndexToPrompt = this.state.performUpload
&& !!this.state.items
.findIndex(i => i.isDuplicate && !i.shouldUploadDuplicate);
onPromptAnswered = (accepted, item, index) => {
this.setState(prevState => ({
...prevState,
items: prevState.items
.map((i, key) => (index === key ? ({
...item,
shouldUploadDuplicate: accepted,
}) : item)),
performUpload: accepted, // if at last an item was rejected, then the upload won't be executed
}));
};
uploadToApi = (items) => {
if (!this.getFirstDuplicateItemToPrompt()) {
const itemsToUpload = items.filter(i => i.isValid);
uploadDataToApi(itemsToUpload);
}
};
render() {
const { items } = this.stat;
const itemElements = items.map((item, key) => (
<MyItem key={key} {...item} />
));
const promptElement = this.getPromptElement();
return (
<div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
{itemElements}
</div>
<Button onClick={this.onUploadButtonClick}>Upload</Button>
{promptElement}
</div>
)
}
}

How to Add filter into a todolist application in Reactjs with using .filter

im new to react, trying to make an todolist website, i have the add and delete and displaying functionality done, just trying to add an search function, but i cant seem to get it working, where as it doesn't filter properly.
i basically want to be able to filter the values on the todos.title with the search value. such as if i enter an value of "ta" it should show the todo item of "take out the trash" or any item that matches with that string.
when i try to search, it gives random outputs of items from the filtered, i am wondering if my filtering is wrong or if i am not like displaying it correctly.
ive tried to pass the value into todo.js and display it there but didn't seem that was a viable way as it it should stay within App.js.
class App extends Component {
state = {
todos: [
{
id: uuid.v4(),
title: "take out the trash",
completed: false
},
{
id: uuid.v4(),
title: "Dinner with wife",
completed: true
},
{
id: uuid.v4(),
title: "Meeting with Boss",
completed: false
}
],
filtered: []
};
// checking complete on the state
markComplete = id => {
this.setState({
todos: this.state.filtered.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
};
//delete the item
delTodo = id => {
this.setState({
filtered: [...this.state.filtered.filter(filtered => filtered.id !== id)]
});
};
//Add item to the list
addTodo = title => {
const newTodo = {
id: uuid.v4(),
title,
comepleted: false
};
this.setState({ filtered: [...this.state.filtered, newTodo] });
};
// my attempt to do search filter on the value recieved from the search field (search):
search = (search) => {
let currentTodos = [];
let newList = [];
if (search !== "") {
currentTodos = this.state.todos;
newList = currentTodos.filter( todo => {
const lc = todo.title.toLowerCase();
const filter = search.toLowerCase();
return lc.includes(filter);
});
} else {
newList = this.state.todos;
}
this.setState({
filtered: newList
});
console.log(search);
};
componentDidMount() {
this.setState({
filtered: this.state.todos
});
}
componentWillReceiveProps(nextProps) {
this.setState({
filtered: nextProps.todos
});
}
render() {
return (
<div className="App">
<div className="container">
<Header search={this.search} />
<AddTodo addTodo={this.addTodo} />
<Todos
todos={this.state.filtered}
markComplete={this.markComplete}
delTodo={this.delTodo}
/>
</div>
</div>
);
}
}
export default App;
search value comes from the header where the value is passed through as a props. i've checked that and it works fine.
Todos.js
class Todos extends Component {
state = {
searchResults: null
}
render() {
return (
this.props.todos.map((todo) => {
return <TodoItem key={todo.id} todo = {todo}
markComplete={this.props.markComplete}
delTodo={this.props.delTodo}
/>
})
);
}
}
TodoItem.js is just the component that displays the item.
I not sure if this is enough to understand the issue 100%, i can add more if needed.
Thank you
Not sure what is wrong with your script. Looks to me it works fine when I am trying to reconstruct by using most of your logic. Please check working demo here: https://codesandbox.io/s/q9jy17p47j
Just my guess, it could be there is something wrong with your <TodoItem/> component which makes it not rendered correctly. Maybe you could try to use a primitive element such as <li> instead custom element like <TodoItem/>. The problem could be your logic of markComplete() things ( if it is doing hiding element works ).
Please let me know if I am missing something. Thanks.

React state variable changes unpredictably

I'm trying to write a front-end using React for the first time.
I stumbled upon a problem trying to give users the option to order a list of React components and later undo the ordering.
So what I tried to do is save the initial list order in a separate state variable skills_initial. This is done by cloning the skills list. If the ordering gets undone, the skills_initial state variable is used to reset it to the initial order.
class Day extends React.Component {
constructor(props) {
super(props);
var skills = [];
var skill;
var xp;
for (var i in skill_names) {
skill = skill_names[i];
xp = this.props.end[skill] - this.props.start[skill];
skills.push(<Skill name={skill} xp={xp} key={skill}/>);
}
var skills_clone = [];
for (var k=0; k < skills.length; k++) {
skills_clone.push(skills[k]);
}
this.state = {skills: skills, skills_initial: skills_clone, descend: false, ascend: false};
this.descend = this.descend.bind(this);
this.ascend = this.ascend.bind(this);
}
descend() {
document.getElementById('ascend').checked = false;
if (this.state.descend) {
document.getElementById('descend').checked = false;
this.setState(prevState => ({
skills: prevState.skills_initial,
descend: false,
ascend: false
}));
} else {
this.setState(prevState => ({
skills: prevState.skills.sort(
function(skill1, skill2) {
return skill2.props.xp - skill1.props.xp;
}),
descend: true,
ascend: false
}));
}
}
ascend() {
document.getElementById('descend').checked = false;
if (this.state.ascend) {
document.getElementById('ascend').checked = false;
this.setState(prevState => ({
skills: prevState.skills_initial,
ascend: false,
descend: false,
}));
} else {
this.setState(prevState => ({
skills: prevState.skills.sort(
function(skill1, skill2) {
return skill1.props.xp - skill2.props.xp;
}),
ascend: true,
descend: false
}));
}
}
render() {
return (
<ol id="skill_list_vertical" style={{listStyleType: "none"}}>
<input id="descend" type="radio" onClick={this.descend}/>descend
<input id="ascend" type="radio" onClick={this.ascend}/>ascend
{this.state.skills}
</ol>
);
}
}
var skill_names = [
"attack",
"defence",
"strength",
"constitution",
"ranged",
"prayer",
"magic",
"cooking",
"woodcutting",
"fletching",
"fishing",
"firemaking",
"crafting",
"smithing",
"mining",
"herblore",
"agility",
"thieving",
"slayer",
"farming",
"runecrafting",
"hunting",
"construction",
"clue_easy",
"clue_medium",
"bounty_rogue",
"bounty_hunter",
"clue_hard",
"LMS",
"clue_elite",
"clue_master"
];
So the first two times the radio button is checked, the list is correctly ordered. Only upon trying to undo the ordering the second time, the list remains ordered. In no place the state variable skills_initial is changed unless in the constructor which I thought to be only called once.
Any further advice on my code structure / react habits is greatly appreciated.

Resources