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) => { ... };
Related
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'm building a mini app and I want to get it cleaner.
So basically I want to have 3 components : App, List and Person.
Here is the code :
App.js
import React, { Component } from "react";
import List from './List';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({ results: json.results });
};
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
updateSearch(event) {
this.setState({ search: event.target.value.substr(0, 20) });
}
render() {
return (
<List />
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { results, currentPage, todosPerPage } = this.state;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(this.state.search) !== -1;
});
const renderTodos = currentTodos.map((item, index) => {
return (
<Person item={this.state.item} index={this.state.index}/>
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={this.handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={this.state.search}
onChange={this.updateSearch.bind(this)}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
Person.js
import React, { Component } from 'react';
function Person(item, index) {
return (
<div className="col-lg-4 mb-4" key={index}>
<div className="card">
<div className="card-header">
<h4 className="mb-0">{item.name}</h4>
</div>
<div className="card-body">
<h5 className="card-title">Caractéristiques</h5>
<ul>
<li>Année de naissance : {item.birth_year}</li>
<li>Taille : {item.height} cm</li>
<li>Masse : {item.mass}</li>
<li>Couleur des yeux : {item.eye_color}</li>
<li>Couleur de cheveux : {item.hair_color}</li>
<li>Couleur de peau : {item.skin_color}</li>
</ul>
Sa fiche
</div>
</div>
</div>
)
}
export default Person;
My issue is that I get TypeError: Cannot read property 'results' of null when rendering.
Is it possible to have variable go into every file if I define them all in App.js ?
You are not passing the data the correct way. Try this:
In App.js pass to List component the needed data:
render() {
return (
<List data={this.state}/>
);
}
Then in render() method in List.js get the passed data prop, then extract the data from there:
render() {
const { data } = this.props;
const { results, search, currentPage, todosPerPage } = data;
// ...
// in currentTodos function dont use this.state.search but just "search", that we got above from the data variable
// ...
// also your renderTodos should look like this - use the item and index variables
const renderTodos = currentTodos.map((item, index) => {
return (
<Person item={item} index={index}/>
);
});
// ...
}
So your List.js should look like this:
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
// get the data
const { data } = this.props;
// get the properties
const { results, search, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
// use "search" variable
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, index) => {
return (
// use item and index
<Person item={item} index={index}/>
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={this.handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={search} // use search variable here too
onChange={this.updateSearch.bind(this)}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
And your function in Person.js should have the following declaration, because the parameters are extracted from the passed props:
function Person({item, index}) {
// ...
}
You can use pass variables in your props of <List /> component by passing state inside render function of App.js while calling <List /> like this
render() {
//Passing Data inside props
<List data={this.state} />
}
and inside your List.js, You can access the data variable
const { results, currentPage, todosPerPage } = this.props.data;
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 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
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?