How to pass variables when calling components - reactjs

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;

Related

update the view in a funcional component reactjs

I have two components contact_list.jsx and contact.jsx and a model contact.class and I can not update the view of props conected.
contact_list.
const ContactListComponent = ({contact }) => {
const defaultContact = new Contact('pepe', 'perez','perez#perez.com',
);
/**
*
*
*/
const [conected, setconected] = useState(false);
const changeState = (changeState) =>
{
console.log(conected);
setconected(conected ===false);
}
useEffect( () => {
console.log('se ejecuta');
},[conected]);
return (
<div>
<div>
your Contacts:
</div>
<ContactComponent conected={conected} contact={defaultContact} changeState={changeState}></ContactComponent>
<div>
<button onClick={changeState}>
cambiar estado conectado
</button>
</div>
</div>
)
}
export default ContactListComponent;
contact.jsx
const ContactComponent = ({contact}) => {
return (
<div>
<h2>
nombre: { contact.name }
</h2>
<h3>
descripción: { contact.surname }
</h3>
<h4>
level: { contact.email }
</h4>
<h5>
this state is: {contact.conected ? 'disponible': 'no disponible'}
</h5>
</div>
);
}
ContactComponent.propTypes = {
contact: PropTypes.instanceOf(Contact),
}
contact.class
export class Contact {
name = '';
surname = '';
email = '';
conected = false;
// level = LEVELS.NORMAL;
constructor(name, surname, email, conected) {
this.name = name;
this.surname = surname;
this.email = email;
this.conected = conected;
}
}
export default ContactComponent;
app.js
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{/* <TaskListComponent></TaskListComponent> */}
<ContactListComponent></ContactListComponent>
</header>
</div>
);
}
export default App;
I dont know how to update with the button contact.conected from 'disponible' to 'no disponible'. in contact_list.jsx I have the hooks useState and useEffect I can change the conected props from true to false but I can not update the view.
I noticed some problems in your code.
You are initializing a "connected" state within the contact list component.
Although you are passing that state to the contact component by props, you are never using it inside it, always referencing contact.connected.
The use of the useEffect is not necessary at all.
I tried to replicate your functionality, in a cleaner way, but with the expected behavior:
import React, { useState } from "react";
const Contact = ({ contact }) => (
<div>
<h2>Nombre: {contact.name}</h2>
<h3>Apellido: {contact.surname}</h3>
<h4>Email: {contact.email}</h4>
<h5>Status: {contact.connected ? "Disponible" : "No disponible"}</h5>
</div>
);
const ContactsList = () => {
const [contacts, setContacts] = useState([{
name: 'Pepe',
surname: 'Pérez',
email: 'pepe#perez.com',
connected: false
}]);
const handleConnectedToggle = (index) => {
setContacts(prevState => {
const newContacts = [...prevState];
newContacts[index] = {
...prevState[index],
connected: !prevState[index].connected
};
return newContacts;
});
}
return (
<div>
{contacts.map((contact, index) => (
<div key={index}>
<Contact contact={contact} />
<button onClick={() => handleConnectedToggle(index)}>
Cambiar estado
</button>
</div>
))}
</div>
);
};
export default function App() {
return (
<div className="App">
<ContactsList />
</div>
);
}

Converting stateful to stateless component

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) => { ... };

how to delete item from todo list by clicking on it?

Problem while creating this app, please advise:
How to delete an item that I add to the list
by clicking on the item itself ?
Please advise on how to improve this part.
The app was created as a try to solve some problems which I have with react and to learn this field.
Any advice or any guides in this field will be appreciated.
import React from "react";
import InputBar from "./InputBar";
import ListofItems from "./ListofItems";
class App extends React. Component {
state = {
listOfinputs: [] //name: ""
};
render() {
return (
<div className="ui segment ui container">
<InputBar
//name={"mocahel"}
onSubmitPress={
input =>
this.setState({
listOfinputs: [...this.state.listOfinputs, input]
// name: "matan"
}) //creare new pointer to array
//this.state.listOfinputs.push(input)
}
/>
<ListofItems data={this.state.listOfinputs} />
</div>
);
}
}
export default App;
import React from "react";
class InputBar extends React.Component {
state = { input: "" };
onInputChanged = event => {
//console.log(event)
this.setState({ input: event.target.value });
};
render() {
const { name, onSubmitPress } = this.props;
return (
<form
onSubmit={event => {
onSubmitPress(this.state.input);
event.preventDefault();
}}
>
<label>
Name:
<input
type="text"
value={this.state.input}
onChange={event => this.onInputChanged(event)}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default InputBar;
import React from "react";
const ListofItems = (props) => {
const { data } = props;
console.log(data, 'test2')
return (
<div className="ui segment">
<ul>
{data.map((input, index) =>
<li key={index}>{input}</li>)}
</ul>
</div>
);
};
export default ListofItems;
const ListofItems = (props) => {
const { data } = props;
console.log(data, 'test2')
return (
<div className="ui segment">
<ul>
{data.map((input, index) =>
<a onClick={() => props.deleteItem(index)}><li key={index}>{input}</li></a>)}
</ul>
</div>
);
};
class App extends React. Component {
state = {
listOfinputs: [] //name: ""
};
handleDelete(index) {
// HERE COMES THE DELETE LOGIC FROM THE STATE WITH INDEX
}
render() {
return (
<div className="ui segment ui container">
<InputBar
//name={"mocahel"}
onSubmitPress={
input =>
this.setState({
listOfinputs: [...this.state.listOfinputs, input]
// name: "matan"
}) //creare new pointer to array
//this.state.listOfinputs.push(input)
}
/>
<ListofItems deleteItem={this.handleDelete} data={this.state.listOfinputs} />
</div>
);
}
}

My search input and pagination aren't triggering anything in Reactjs

I'm fairly new to react.
My search input and pagination buttons aren't triggering anything and nothing comes up in the console, what is wrong with my code ?
I tried putting every functions in App.js to get it cleaner.
App.js
import React, { Component } from "react";
import List from './List';
let API = 'https://swapi.co/api/people/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
this.updateSearch = this.updateSearch.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 (
<div>
<List data={this.state} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { data } = this.props;
const { results, search, updateSearch, handleClick, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, number) => {
return (
<Person item={item} key={number} />
);
});
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={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}
onChange={updateSearch}
/>
</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;
The value of the input doesn't change one bit if I type in it and if I right click on a page number, the console gets me Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Element': '#4' is not a valid selector.
Any idea ?
The issue is that in the List class you attempt take updateSearch and handleClick out of data (which in turn comes from this.props). But updateSearch and handleClick are never placed inside data. If you log either of these methods to the console you'll see they are undefined.
To fix this, you need to pass updateSearch and handleClick from App to List. You can do this either by including the methods inside the data prop, or by passing them directly as their own props (which I would recommend).
For example, you can change the render method of App to look something like this:
render() {
return (
<div>
<List
data={this.state}
updateSearch={ this.updateSearch }
handleClick={ this.handleClick }
/>
</div>
);
}
Then in the render method of List you can do this:
const { data, updateSearch, handleClick } = this.props;
and remove the definitions of the two methods from the destructuring of data below.

React filter 'state' undefined

So I started using reactjs and I've managed to loop through some XML data but having issues adding a search/filter into it.
This is my code so far:
import React, { Component } from 'react';
import XMLMapping from 'xml-mapping';
import axios from 'axios';
class Guests extends Component {
constructor(props) {
super(props);
this.state = {
guests: [],
search: 'Search Guests'
};
}
componentDidMount() {
axios.get('http://localhost:8080/guestlist.xml')
.then(res => {
const xml = XMLMapping.load(res.data);
var guests = XMLMapping.tojson(xml);
this.setState({guests: guests});
//console.log(guests);
return guests;
});
}
updateSearch(event) {
this.setState({
// Limit to 10 characters only for search
search: event.target.value.substr(0, 10)
});
// console.log(this.state.search); // this will show the previous value of state.
}
render() {
function mapObject(object, callback) {
return Object.keys(object).map(function (key) {
return callback(key, object[key]);
});
}
const firstname = mapObject(this.state.guests, function (key, value) {
return <div key={key}>
{value.record
.map((item,index) => {
//console.log(index)
return <div className="columns" key={index}>
<div className="column" key={index}>{item.first_name.$t} {item.last_name.$t}</div>
<div className="column" >{item.company.$t}</div>
</div>;
})}
</div>
});
let filteredGuests = mapObject(this.state.guests, function (key, value) {
value.record.filter(
(contact) => {
return contact.first_name.$t.indexOf(this.state.search) !== -1;
//console.log(this.state.search)
}
);
});
return (
<div>
<div className="container">
<section className="section">
<h1 className="title">Guests attending Event A</h1> <input className="text" type="text" value={this.state.search} onChange={this.updateSearch.bind(this)} />
<div className="columns"><div className="column">Name</div><div className="column">Company</div></div>
{firstname}
</section>
</div>
</div>
);
}
}
export default Guests;
But it seems to be throwing an error TypeError: Cannot read property 'state' of undefined
This is the line return contact.first_name.$t.indexOf(this.state.search) !== -1;
Any advice or feedback would be appreciate!
As Prakash sharma wrote, you used the wrong function context.
Try to replace function with arrow function:
let filteredGuests = mapObject(this.state.guests, (key, value) => {
value.record.filter(
(contact) => {
return contact.first_name.$t.indexOf(this.state.search) !== -1;
//console.log(this.state.search)
}
);
})

Resources