I am working on a quiz in react. I want to show one question and their choices at the same time at the page.
Such as :
"question": "When the C programming language has first appeared?",
a.)1970
b.)1971
c.)1972
d.)1973
This is what I have done so far:
import React from 'react'
import axios from 'axios'
class QuizApp extends React.Component {
constructor(props) {
super(props);
this.state = {entered: false, correct: 0, wrong: 0}
}
state = {
questions: [],
choic: []
};
componentDidMount() {
axios.get("http://private-anon-c06008d89c-quizmasters.apiary-mock.com/questions").then((response) => {
const questions = response.data;
this.setState({questions});
const choic = response.data;
this.setState({choic});
console.log(response);
})
}
nickChange = (event) => {
this.setState({username: event.target.value});
};
cleanPage = () => {
this.setState({
entered: true
})
};
render() {
if (!this.state.entered) {
return (
<div>
<form target="_self" id="firstPage">
<input type="text" value={this.nick} onChange={this.nickChange}/>
<input type="submit" value="Start" name="cleanPage" onClick={this.cleanPage}/>
</form>
</div>
)
}
else {
return (
<div>
<ul>
{this.state.questions.map(que => <li>{que.question} </li>)}
{this.state.choic.map(cho => <li>{cho.choices.choice} </li>)}
</ul>
</div>
)
}
}
}
simplify set state in response
const { questions, choices } = response.data;
this.setState({questions, choices});
In render do like this (Assuming each question has corresponding array of choices in that index):
{this.state.questions.map((que, index) => {
<React.Fragment>
<li>{que.question} </li>
<ul>
{choices[index].map(choice => <li>{choice}</li>)
</ul>
<React.Fragment
})}
style the elements as needed
Related
I have this working stateful component in React:
import React, {Component} from "react";
class MyComponent extends Component {
constructor() {
super();
this.myRef = React.createRef();
this.state = {
array: [],
noresults: false
};
}
loadData = () => {
let data = this.myRef.current.value;
let url = "someurl="+data;
if(!data) {
return;
}
fetch(url)
.then((res) => res.json())
.then((json) => {
if(json.data.length > 0) {
this.setState({
array: json.data,
noresults: false
});
} else {
this.setState({
noresults: true
});
}
})
}
render() {
const { array, noresults } = this.state;
return (
<div>
<section>
<input ref={this.myRef} type="number"/>
<button onClick={this.loadData}>Click Here</button>
</section>
<ul>
{
array.map((e) => (
<li key = { e.key } >
{ e.data }
</li>
))
}
</ul>
{noresults && <div>No Records</div>}
</div>
);
}
}
export default MyComponent
I want to convert this to stateless like this:
function MyComponent() {
return (
<div>
<section>
<input type="number"/>
<button>Click Here</button>
</section>
<ul>
<li></li>
</ul>
<div>No Records</div>
</div>
);
}
export default MyComponent
Now how can I pass data of input to my method to make API call. Also how to pass the response from API to display as ul and li elements?
Just pass it as component props:
const MyComponent = ({array = [], loadData = () => {}}) => {
const [inputValue, setInputValue] = useState();
const handleInputChange = (evt) => {
setInputValue(evt.target.value);
};
return (
<div>
<section>
<input type="number" onChange={handleInputChange} />
<button onClick={e => loadData(e, inputValue)}>Click Here</button>
</section>
<ul>
{array.map((e) => (<li key={e.key}>{e.data}</li>))}
</ul>
{array.length === 0 && <div>No Records</div>}
</div>
);
}
For input, I created a local state which is updated on input change and passed it to loadData function. You can access the current value by parametrizing loadData function:
loadData = (e, currentInputValue) => { ... };
I am trying to implement an onChange method that when the user type something it gets updated in real time and displayed in the div. The component that I am talking about is at the end of the code and it's called and it is an input that will be rendered 4 times on the dom. For a reason no value get shown on the div I mean {this.state.stake}. Could anyone help me in fixing that? Thanks
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
stake: ''
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
changeStake = (e) => {
this.setState({
stake: [e.target.value]
})
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
onChange={this.changeStake} />
{this.state.stake}
</div>
</li>
))
}
</ul>
</div>
Pass this.state.stake as a prop of Stake component.
<Stake
onChange={this.changeStake}
stake={this.state.stake}
/>
Then inside of the Stake component assign stake prop to value on an the input. It would look something like this.
const Stake =({stake, onChange})=>{
return <input value={stake} onChange={onChange} />
}
I am a beginner in React. When I try to pass props from children to parent, the whole app is refreshed and the state recovery to initial. Is there any problem on my code? I have no idea how to solve it.
(ps: The following sentence is just for the number of words. Please don't see it. Why I have to add more details. If I have the ability to know every detail, I already solved it by myself)
Parent:
class App extends Component {
constructor(props) {
super(props);
this.state = {
stops: [],
legs: [],
driver: null,
finishedSign: false,
stopsSign: false,
legsSign: false,
driverSign: false
};
}
componentDidMount() {
console.log("-DID");
this.getStops();
this.getLegs();
this.getDriver();
}
// garentee all of data have received
checkFinished() {
const { stopsSign, legsSign, driverSign } = this.state;
const mark = stopsSign && legsSign && driverSign;
if (mark)
this.setState({
finishedSign: mark
});
}
// GET/STOPS API
getStops() {
fetch("/api/stops")
.then(res => res.json())
.then(stops => {
this.setState({ stops: stops, stopsSign: true }, () =>
console.log("stops fetched !", stops)
);
this.checkFinished();
});
}
// GET/LEGS API
getLegs() {
fetch("/api/legs")
.then(res => res.json())
.then(legs => {
this.setState({ legs: legs, legsSign: true }, () =>
console.log("driver fetched !", legs)
);
this.checkFinished();
});
}
// GET/Driver API
getDriver() {
console.log("-DRIVER");
fetch("/api/driver")
.then(res => {
return res.json();
})
.then(driver => {
this.setState(
{
driver: driver,
driverSign: true
},
() => console.log("driver fetched !", driver)
);
this.checkFinished();
});
}
// passing func
updateDriver(driver) {
console.log("update app!");
alert(driver);
}
renderMaps() {
return (
<Maps
stops={this.state.stops}
legs={this.state.legs}
driver={this.state.driver}
/>
);
}
renderDriverController() {
return (
<DiverController
legs={this.state.legs}
driver={this.state.driver}
update={this.updateDriver}
/>
);
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-3 col-md-3">
{this.state.finishedSign && this.renderDriverController()}
</div>
<div className="col-sm-8 col-md-8">
{
//this.state.finishedSign && this.renderMaps()
}
</div>
</div>
</div>
);
}
}
export default App;
children:
class DriverController extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.legs,
driver: this.props.driver
};
}
handleUpdate = e => {
const driver = null;
driver.activeLegID = this.refs.selectedLeg.value;
driver.legProgress = this.refs.selectedProgress.value;
if (driver.legProgress >= 0 && driver.legProgress <= 100)
this.props.update("test");
else alert("out of range!");
};
render() {
const { items, driver } = this.state;
console.log("items:", items);
return (
<form>
<hr />
<label>Driver Location:</label>
<div className="form-group">
<select
id="inputState"
className="form-control"
defaultValue={driver.activeLegID}
ref="selectedLeg"
>
{items.map(item => (
<option key={item.legID}>{item.legID}</option>
))}
</select>
<div className="input-group input-group-sm mb-3">
<div className="input-group-prepend">
<span className="input-group-text" id="inputGroup-sizing-sm">
Percentage:
</span>
</div>
<input
type="number"
className="form-control"
defaultValue={driver.legProgress}
ref="selectedProgress"
/>
</div>
<button onClick={this.handleUpdate} className="btn btn-primary">
Submit
</button>
<hr />
</div>
</form>
);
}
}
export default DriverController;
Try to use
onClick={() => this.handleUpdate}
You should not pass props from a child to its parent. Thats an anti-pattern.
You could pass a function from parent to child which will be triggered in
the child and hence updating the required state in the parent.
Refresh issue:
I think cause the child is wrapped inside a form.
Add
e.preventDefault() to your handleSubmit function to prevent the refresh
handleUpdate = e => {
e.preventDefault()
I have 3 components: App, Map and ListPlaces. In ListPlaces component, when a user types something in the input element, I want to change the state(markers's state) in App.js to show only related markers on the map.
Edit: When I edit my typo, the error was disappeared. However, I think the logic is still wrong. Because when I write something in the input element, markers array would be 0 immediately. And of course, all markers are disappeared.
More Explanation:
After componentDidMount, my markers array holds 7 items. And Map component takes this markers array and render markers on the map. However, I need to control my markers from ListPlaces component according to value of input element. So I put this: onChange={e => {this.updateQuery(e.target.value); changeMarkersHandler(e.target.value)}} in onChange attribute of input element. (Omit the this.updateQuery, for now, you can focus on only changeMarkersHandler).
This changeMarkersHandler runs changeMarkers function in App.js, but I don't know why my marker arrays would be 0 immediately while changeMarkers function is working.
Note: I am using react-google-maps and I've omitted some code blocks which aren't related to question.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
places: [],
markers: [],
markerID: -1,
newMarkers: []
};
this.changeMarkers = this.changeMarkers.bind(this);
}
componentDidMount() {
fetch("api_url")
.then(response => response.json())
.then(data => {
this.setState({
places: data.response.venues,
markers: data.response.venues
});
})
.catch(error => {
console.log("Someting went wrong ", error);
});
}
changeMarkers(value) {
const newMarkers = this.state.markers.filter(
place => place.name === value
);
this.setState({
newMarkers : newMarkers,
markers: newMarkers
})
}
render() {
return (
<div className="App">
<Map role="application"
places={this.state.places}
markers={this.state.markers}
openInfoHandler={this.openInfo}
closeInfoHandler={this.closeInfo}
markerID={this.state.markerID}
googleMapURL="url_here" />
<ListPlaces changeMarkersHandler={this.changeMarkers} />
</div>
);
}
}
ListPlaces.js
import React, { Component } from "react";
import escapeRegExp from "escape-string-regexp";
class ListPlaces extends Component {
state = {
searchQuery: ""
};
updateQuery = query => {
this.setState({ searchQuery: query});
};
render() {
const { toggleListHandler, locations, openInfoHandler, changeMarkersHandler} = this.props;
let showLocations;
if (this.state.searchQuery) {
const match = new RegExp(escapeRegExp(this.state.searchQuery), "i");
showLocations = locations.filter(location =>match.test(location.name));
} else {
showLocations = locations;
}
return (
<div>
<aside>
<h2>Restaurants</h2>
<nav>
<div className="search-area">
<input
className="search-input"
type="text"
placeholder="Search Restaurant"
value={this.state.searchQuery}
onChange={e => {this.updateQuery(e.target.value); changeMarkersHandler(e.target.value)}}
/>
</div>
<ul>
{showLocations.map(location => {
return (
<li
key={location.id}
onClick={e =>
openInfoHandler(e, location.id)
}
>
{location.name}
</li>
);
})}
</ul>
</nav>
<p>some text</p>
</aside>
<a
onClick={toggleListHandler}
id="nav-toggle"
className="position"
>
<span />
</a>
</div>
);
}
}
export default ListPlaces;
You have a typo in you constructor.
this.changeMarkers(this.changeMarkers.bind(this));
should be
this.changeMarkers = this.changeMarkers.bind(this);
I have a dynamic todo list I would like to add a "highlight" feature to. Each list item renders with markup for the highlight that should show only for the list item clicked.
export class Todo extends Component {
constructor(props) {
super(props);
this.state = {input: '', todos: this.getOldTodo()};
this.selectItem = this.selectItem.bind(this);
}
//shortened
selectItem(i) {
this.setState({selected: i});
if (this.state.selected == i) {
// --- this is the code that needs to change the right list items child's class
???.props.childen[2].className = "active";
// ---
console.log("true")
}
console.log(i);
}
render() {
//markup also shortened
this.state.todos.map((todos, i) => {
return (
//What do I pass to the method here?
<li key={todos.key} className="todo-li-item" onClick={this.selectItem.bind(this, i)}>
<span className="todo-item">{todos.text}</span>
<span onClick={this.deleteItem.bind(this, i)} className="delet-todo">✕</span>
// --- This is the child that needs its class changed when it's parent is clicked
<div id="todo-select" className={"hidden"}>
<span id="todo-select-top"></span>
<span id="todo-select-left"></span>
</div>
</li>
);
})
</ul>
</div>
);
}
}
This is painfully simple and yet so un-obvious as to what I use to do this in react, but hey I'm still learning. Thanks for your time.
You've been quite close. Here's my implementation.
Key takeaway: Don't mutate the state object.
selectItem(idx) {
this.setState(state => {
const todos = [
state.todos.slice(0, idx),
{ ...state.todos[idx], selected: ! state.todos[idx].selected },
state.todos.slice(idx + 1, state.todos.length),
]
return {
...state,
todos,
}
})
}
deleteItem(idx) {
this.setState(state => {
const todos = [...state.todos]
todos.splice(idx, 1)
return {
...state,
todos,
}
})
}
render() {
return (
<div>
<ul>
{this.state.todos.map((todo, idx) => (
<li
key={todo.key}
className={'todo-li-item'}
onClick={this.selectItem.bind(this, idx)}
>
<span className="todo-item">{todo.text}</span>
<span
onClick={this.deleteItem.bind(this, idx)}
className="delete-todo"
>
✕
</span>
<div id="todo-select" className={todo.selected && 'active'}>
<span id="todo-select-top" />
<span id="todo-select-left" />
</div>
</li>
))}
</ul>
</div>
)
}
The list item can be a stateless component, so the onSelect and onDelete become callback functions.
Deleting item with index may get you in trouble, since React will not re-render the entire list every time.
I don't know what's inside getOldTodo, but custructor cannot wait. So it will be null initially, if it's an async function.
There is an implementation using ES6 syntax.
Each list item is stateless:
const ListItem = props => {
const { todo, deleteItem, selectItem } = props;
return (
<li key={todo.key} className="todo-li-item" onClick={selectItem}>
<span className="todo-item">{todos.text}</span>
<span onClick={deleteItem} className="delet-todo">
✕
</span>
clicked
<div id="todo-select" className={'hidden'}>
<span id="todo-select-top" />
<span id="todo-select-left" />
</div>
</li>
);
};
All events are handled by a stateful component:
export class Todo extends Component {
state = {
input: '',
todos: [],
};
async componentDidMount() {
const todos = await this.getOldTodo();
this.setState({ todos });
}
render() {
return (
<div>
{this.state.todos.map(todo => (
<ListItem
todo={todo}
key={todo.key}
selectItem={() => {
this.selectItem(todo);
}}
deleteItem={() => {
this.deleteItem(todo);
}}
/>
))}
</div>
);
}
selectItem = todo => {
const idx = this.state.todos.findIndex(i => i.key === todo.key);
const todos = this.state.todos.slice();
const todo = { ...this.state.todos[idx] };
// change
todos[idx] = todo;
this.setState({
todos
});
}
deleteItem = todo => {
const idx = this.state.todos.findIndex(i => i.key === todo.key);
const todos = this.state.todos.splice(idx, 1);
this.setState({
todos
});
}
getOldTodo = async () => {
//...
}
}
Does this make sense to you?