I am relatively new to React so sorry if this is a really easy/stupid question.
So I am making an app where you provide a number of months and depending on the number of months that number of text fields need to show up. Ex. 5 months = 5 new text fields. How would I go about doing that? I have tried various methods to no avail hence why I am asking here. Thank you for your help.
class MonthlyInputs extends React.Component {
render() {
let monthInputs = [1, 2, 3];
let items = [];
function updateInputs(event) {
monthInputs = [];
for (let i = 0; i < event.target.value; i++) {
monthInputs.push(i);
}
console.log(monthInputs)
for (let i = 0; i < monthInputs.length; i++) {
items.push(<li key={i}>list item {i}</li>);
}
}
for (const [index, value] of monthInputs.entries()) {
items.push(<li key={index}>{value}</li>)
}
return (
<div>
<input onChange={updateInputs} />
{items}
</div>
)
}
}
I attempted what you're trying to implement.
Let me know if you have any questions.
https://codesandbox.io/s/tender-liskov-iicor?fontsize=14&hidenavigation=1&theme=dark
Consider saving your variables in state. Also you shouldn't declare funcions in render funcion. Here is code:
class App extends Component{
constructor(){
super();
this.state = {
monthInputs: [],
}
}
updateInputs = (event) => {
let monthInputs = [];
for (let i = 0; i < event.target.value; i++) {
monthInputs.push(i);
}
this.setState({
monthInputs
})
}
render(){
const {monthInputs} = this.state;
return(
<div>
<input type="number" value={monthInputs} onChange={this.updateInputs} />
{monthInputs.length > 0 && monthInputs.map((number, index)=>
<li key={number}>{number}</li> )}
</div>
)
}
}
And here is link to codesandbox
Related
I try to append a component dynamically, here it's Boards that i try to add to MainComponent.
The function addChild work and the list boards is filling up when the loop for is running, but nothing else happen, nothing is displayed on the screen
const MainComponent = props => (
<div className={props.className}>
<p>
<a href="#" onClick={props.addChild}>Add Another Child Component</a>
</p>
{props.boards}
</div>
);
class MainPage extends React.Component {
state = {
numChildren: 0
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
render () {
const boards = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
var _id = "board-" + i;
console.log
boards.push(<Board id={_id} className="board" />);
};
return (
<div className="test">
<Title/>
<MainComponent addChild={this.onAddChild} className="flexbox">
{boards}
</MainComponent>
</div>
);
}
}
export default MainPage```
Try to add key for each ChildComponent
const boards = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
var _id = "board-" + i;
console.log
boards.push(<Board id={_id} key={_id} className="board" />);
};
because react depends on the key for rendering the component that is rendered inside loops
so react considers that all child that you are rendering is one component unless you give them a different key
I am trying to build a react app for weather. I am using openweathermap api.
i have a component that supposed to render 5 weather components, each one for each day (it's a 5 day forecast).
however, something is not working.
this is the weather component:
class Weather extends Component{
getAllDates = () =>{
const list = this.props.list;
let date = list[0]["dt_txt"].split(" ")[0];
const datesList = [{date: date, indexes: [0]}];
let indexes = [];
for(let i = 1; i < list.length; i++){
const currDate = list[i]["dt_txt"].split(" ")[0];
if(date !== currDate){
datesList.push({
date: currDate,
indexes: indexes});
date = currDate;
indexes = [];
}
else{
const toUpdate = datesList.pop();
const oldIndexes = toUpdate.indexes;
const newIndexes = oldIndexes.push(i);
toUpdate.indexes = newIndexes;
datesList.push(toUpdate);
}
}
return datesList;
}
render(){
const datesList = this.getAllDates();
return(
<React.Fragment>
{datesList.map((date, key) => {
return <DayWeather date = {date}
key = {key}
forecastList = {this.props.list} />
})}
</React.Fragment>
)
}
and in App.js I am rendering it conditionally like this:
{this.state.data === undefined ? <h1>Choose City</h1> :
<Weather list = {this.state.data.list}/>}
The problem is, the getAllDates is called twice from the render function in Weather component.
the loop goes for 2 iterations instead of 40, and then getAllDates is called again. I can't understand why because I have conditional rendering in App.js.
Also, the indexes variable at some point turns into a number and not an array,
and then I get an error that I can't do .push to indexes.
I have no idea what is hapenning here,
would appreciate any kind of help!
thank you!
**here I am fetching the data:
class App extends Component{
constructor(){
super();
this.state = {
data: undefined
}
}
onSelectHandler = (event) =>{
const city = event.target.value;
const apiCall = `http://api.openweathermap.org/data/2.5/forecast?q=${city},il&APPID=${API_KEY}`
fetch(apiCall)
.then(response => response.json())
.then(response =>
{
this.setState({
data: response
})
}
)
}
I am creating an app, where users should compose a correct sentence from shuffled one. Like on the following picture. The problem is that I am not implementing the app in a right way. For example, I do not change inputs through state. And my main question is how to clear input of a particular line (a bunch of inputs) ? I need it when a user types something wrong and needs to start again. I know that input field should be controlled through state, but it is impossible since all inputs are generated according to number of words. And in one line there maybe more than one input. Let me explain how my code works, so that you have an idea my implementation.
This is where I get data from.
export default {
id:'1',
parts:[
{
speaker:'Speaker1',
words:'Hello how are you?'
},
{ speaker:'Speaker2',
words:'I am OK, thanks'
},
{ speaker:'Speaker1',
words:'What are your plans for Saturday?'
}
]
}
import React, { Component } from 'react';
import {CopyToClipboard} from 'react-copy-to-clipboard';
import { MdDoneAll } from "react-icons/md";
// This is the array where data from inputs is pushed to
var pushArr = [];
// Initial points
var points = 0;
class DialogueShuffleFrame extends Component {
constructor(props) {
super(props)
this.state = {
// Variable to show correct or incorrect notification
showCorrect:false
}
this.writeSometihng = this.writeSometihng.bind(this)
}
// Function that is triggered when a tick is clicked
// Pushes value to the pushArr array when onBlur function is triggered
writeSometihng(e) {
e.preventDefault()
pushArr.push(e.target.value)
console.log(pushArr)
}
// Function check if array of value from input matches to an array from
// initial data source
checkLines(arr, lines) {
let joinedStr = arr.join(' ');
//console.log(joinedStr);
lines[0].parts.map((obj) => {
let line = obj.words
if (joinedStr === line) {
this.setState({
showCorrect:true
})
pushArr = [];
points += 80;
} else {
pushArr = [];
}
})
}
// Resets pushArr array
reset() {
pushArr.length = 0
console.log('clicked')
}
// Shuffles words
formatWords(words) {
const splittedWords = words.split(' ')
const shuffledArray = this.shuffle(splittedWords)
return (
shuffledArray.map((word, index) => (
<>
<input className="word-to-drop-input" id={index} onBlur={this.writeSometihng} size={2} />
<CopyToClipboard text={word}>
<span key={uid(word)} value={word} className="word-to-drop">{word}</span>
</CopyToClipboard>
</>
))
)
}
shuffle(a) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
render() {
const {lines} = this.props
const shuffles = lines[0].parts && (
lines[0].parts.map((element,i) => (
<>
<li className="line" key={i}><span>{element.speaker}{": "}</span><span>{this.formatWords(element.words)}</span></li>
<MdDoneAll type="button" onClick={() => {this.checkLines(pushArr, lines)}} style={{color:'white'}}/>
</>
))
)
return (
<>
<h1 className="centered" style={{color:'white'}}>Dialogue shuffle frame</h1>
<ul className="lines-container">
{shuffles}
</ul>
{<div className="reactangular">{this.state.showCorrect ? 'Correct' : 'Incorrect'}</div>}
<div>{points}</div>
<div className="reactangular" onClick={() => this.reset()}>Reset</div>
</>
)
}
}
export default DialogueShuffleFrame;
I'd recommend to use proper data structure with state management, but that's a different story.
To solve your specific issue of clearing input elements, you can use ReactDOM.findDOMNode to access the DOM node and traverse the input elements and set the value to empty string.
Something like this:
class App extends React.Component {
check = (ref) => {
const inputElements = ReactDOM.findDOMNode(ref.target).parentNode.getElementsByTagName('input');
[...inputElements].forEach(el => el.value = '')
}
render() {
return (
<div>
<ul>
<li>
<input />
<input />
<button onClick={this.check}>Check</button>
</li>
<li>
<input />
<input />
<input />
<button onClick={this.check}>Check</button>
</li>
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('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>
So I've created a quiz-app that fetches 10 random questions with three incorrect and one correct answer to choose from. Everything is working great at the moment except that I can't get the answers to pop up randomly. And by that I mean that the correct answer is always at the bottom of the choices presented.
I know that the answer is Math.floor(Math.random() * ... ) but I honestly have no idea where to put it. I've tried everything. I could really use some help.
import React, { Component } from "react";
import "./App.css";
const API =
"https://opentdb.com/api.php?amount=10&category=20&difficulty=medium";
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
score: []
};
}
componentDidMount() {
this.populateAppWithData();
}
populateAppWithData() {
fetch(API)
.then(response => response.json())
.then(data => this.setState({ results: data.results }))
.catch(error => console.error(error))
}
render() {
const { results } = this.state;
return (
<div className="App">
<h1>Quiz App</h1>
<TheCounter results={results}
Counter={this.state.score}
/>
</div>
);
}
}
class MythologyAnswers extends Component {
constructor(props) {
super(props);
this.state = {
answered: "",
isRight: null,
};
}
answerClicked(answer) {
const { hasAnswered, correct_answer } = this.props;
return event => {
const isRight = correct_answer === answer;
hasAnswered(isRight);
this.setState({
answered: answer,
isRight,
});
};
}
render() {
const { question, correct_answer, incorrect_answers } = this.props;
const { answered, isRight } = this.state;
return (
<div className="allAnswers">
{question}
{incorrect_answers && incorrect_answers
.concat(correct_answer)
.map(answer => (<div onClick={this.answerClicked(answer)}>{answer} </div>))}<br />
{answered && `You answered ${answered}`} {" "} <br />
<div className="correctAnswer"> {" "}{answered && isRight && "This is correct!"} </div> <br />
<div className="incorrectAnswer"> {" "}{answered && !isRight && `This is incorrect. The correct answer is ${this.props.correct_answer}`} {" "}</div>
</div>
)
}
}
class TheCounter extends Component {
constructor(props) {
super(props);
this.state = {
right: 0,
Counter: 0,
unanswered: 0,
};
}
questionAnswered = isRight => {
this.setState(({ Counter, right }) => ({
Counter: Counter + 1,
right: right + isRight,
}));
}
render() {
const { results } = this.props;
const { Counter } = this.state;
const unanswered = this.props.results && Counter;
if (unanswered >= 10) {
return <div className="theUnanswered"> You got {this.state.right} right out of {this.state.Counter} </div>;
}
const question = results[Counter];
return (
<div className="newQuestion">
<MythologyAnswers {...question} hasAnswered={this.questionAnswered} />
</div>
)
}
}
export default App;
We just need to apply the randomizer in the right place. You use .concat() to combine the two arrays, so it makes sense to use the randomizer right after that and before we call .map()
I've set up something that preserves alot of the existing logic you already wrote.
This will help create the new Arr and set-up the markup for your component.
renderChoices = () => {
const { correct_answer, incorrect_answers } = this.props;
let allAnswers = incorrect_answers
? incorrect_answers.concat(correct_answer)
: [];
//simple shuffle algorithm. Just inject your array and it'll pop out a new one.
function createRandom(arr) {
let myArr = [...arr]; //copy arr we pass in
let randomizedArr = []; //gets popuated by loop
while (myArr.length > 0) {
var randomIndex = Math.floor(Math.random() * myArr.length); //create random number
randomizedArr.push(myArr[randomIndex]); //add choice randomly to arr
myArr.splice(randomIndex, 1); //cut out a piece of the array then resart loop
}
//when loop has finished, return random array
return randomizedArr;
}
//call randomizer and get new Arr
let randomizedArr = createRandom(allAnswers);
//use .map to create markup with randomizedArr
return randomizedArr.map(answer => {
return <div onClick={this.answerClicked(answer)}>{answer}</div>;
});
};
So if you were to call the above function inside render, it will create the answer-set for you.
render() {
const { question } = this.props;
const { answered, isRight } = this.state;
return (
<div className="allAnswers">
{question}
{ this.renderChoices()}
<br />
{answered && `You answered ${answered}`} <br />
<div className="correctAnswer">
{answered && isRight && "This is correct!"}
</div>
<br />
<div className="incorrectAnswer">
{answered &&
!isRight &&
`This is incorrect. The correct answer is ${
this.props.correct_answer
}`}
</div>
</div>
);
}
Clean and not too-complex :)
Edit: so without changing your original code too much:
createRandom(arr) {
let myArr = [...arr]; //copy arr we pass in
let randomizedArr = []; //gets popuated by loop
while (myArr.length > 0) {
var randomIndex = Math.floor(Math.random() * myArr.length); //create random number
randomizedArr.push(myArr[randomIndex]); //add choice randomly to arr
myArr.splice(randomIndex, 1); //cut out a piece of the array then resart loop
}
//when loop has finished, return random array
return randomizedArr;
}
render() {
const { question, correct_answer, incorrect_answers } = this.props;
const { answered, isRight } = this.state;
const allAnswers =
incorrect_answers ? incorrect_answers.concat(correct_answer) : [];
const randomizedAnswers = this.createRandom(allAnswers)
return (
<div className="allAnswers">
{question}
{randomizedAnswers
.map(answer => (
<div onClick={this.answerClicked(answer)}>{answer} </div>
))}
<br />
{answered && `You answered ${answered}`} <br />
<div className="correctAnswer">
{answered && isRight && "This is correct!"}
</div>
<br />
<div className="incorrectAnswer">
{answered &&
!isRight &&
`This is incorrect. The correct answer is ${
this.props.correct_answer
}`}
</div>
</div>
);
}
So in the edited version we do a couple things:
Defined a function called createRandom() ... all it does is
randomize your answers choices.
In render, we created a new variable called allAnswers which just
concat() the incorrect_answers and correct_answer like you did
previously. If there are no incorrect_answers defined, we will use
an empty array [] as the default value.
Create a new variable called randomizedAnswers. We call
createRandom() and pass in allAnswers as the argument. It returns
the randomized array for us to use.
Then just .map() over randomizedAnswers to create your
answer-choices markup.
In below code , I suppose you are trying to concat incorrect_answer array with the correct_answer
incorrect_answers && incorrect_answers
.concat(correct_answer)
so array becomes [incorrect_answers,incorrect_answers,incorrect_answers,correct_answer]
i.e correct answer is at end
so if you want to insert correct answer at random position and suppose there are 4 options then first generate a random number between 0 and 3
let randonIndex = Math.floor(Math.random() * 4)
then insert correct answer at random index
incorrect_answers && incorrect_answers
.splice(randomIndex, 0, correct_answer);
I'd personally add it right after fetching the data. Using an inline shuffle like Fisher-Yales you don't need to modifiy your structure at all.
const fisherYalesShuffle = (a) => {
let j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
}
However, since you generally want to move as much data-logic as possible to your backend, you should also consider shuffling them there instead.
You can define a shuffle function (This one is Fisher-Yates (aka Knuth) shuffle):
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
And then just apply the function when setting the state like this:
this.setState({ results: this.shuffle(data.results) })
I'm mostly a C++ developer and I'm having a hard time understanding how can I create something as a 'Delegate' in React.
What I wanna achieve: Pass a custom component to a Table Component that has the required code to edit the data on a table cell correctly.
on my mainApp:
<TableComponent
headerData=["first", "second", "third"]
rowEditDelegates=[<input/>, <button></button>, <combobox/>]
/>
The code is much shorter for brievety.
class TableComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
editDelegates = this.props.rowEditDelegates;
displayEditors = false;
}
onEditToogled() {
/* Here I have no idea how to get the editDelegates
and pass data to it. */
setState({displayEditors : true});
}
}
render() {
let row = null;
if(this.state.displayEditors) {
row = this.state.editDelegates.map((item) => <td> {item} </td>)
} else {
row = this.props.bodyData.map((item) => <td> {item} </td>)
}
}
};
I cannot access the delegate's methods because it's a rendered component, and I didn't understand how to work with a component "pointer" (I don't even know if it exists), maybe my problem needs a different mindset than a c++ programmer one.
You have couple of options here:
1) Use cloneElement function:
onEditToogled()
{
setState({displayEditors : true});
}
render()
{
let row = null;
if(this.state.displayEditors)
{
row = this.state.editDelegates.map((item) => {
var alteredElement = React.cloneElement(item, {className: "newPropertyValue"});
return (<td> {alteredElement} </td>);
});
}
else
{
row = this.props.bodyData.map((item) => <td> {item} </td>)
}
}
2) Change the way how you pass the editor components, for example:
<TableComponent
headerData=["first", "second", "third"]
rowEditDelegates=[(props) => <input {...props}/>, (props) => <button {...props}/>, (props) => <combobox {...props}/>]
/>
And later:
render() {
let row = null;
if(this.state.displayEditors) {
row = this.state.editDelegates.map((itemFunc) => <td> {itemFunc({className: "newPropertyValue"})} </td>)
} else {
row = this.props.bodyData.map((itemFunc) => <td> {itemFunc()} </td>)
}
}
As a side note.
I do not think there is a need for you to copy and keep your "delegates" in state. They are unlikely to change? If so - just leave them in props.