I did an input for an offline application form using React and built a view to display the form, but the view seems to keep giving error, i can't seem to identify it
Here is the code for the view:
import React from 'react'
export default class GradeData extends React.Component {
constructor (props) {
super(props)
this.state = {grades: []}
this.schoolDb = this.props.schoolDb
}
componentDidMount () {
this.updateGrades()
this.schoolDb.changes({
since: 'now',
live: true
}).on('change', (change) => {
this.updateGrades()
}).on('error', (err) => {
console.error(err)
})
}
updateGrades () {
this.schoolDb.allDocs({include_docs: true}).then((res) => {
var grades = res.rows.map((row) => row.grade)
this.setState({grades})
})
}
render () {
return (
<div className='eidsr-data'>
<div className='eidsr-data__header'>
<h3 className='eidsr-data__title'>Grades Overview</h3>
</div>
<div className='table-list'>
<table>
<thead>
<tr>
<th>Student ID</th>
<th>Semester</th>
<th>Period</th>
</tr>
</thead>
<tbody>
{this.state.grades.map((grade) => <DataRow key={grade._id} grade={grade} {...this.props} />)}
</tbody>
</table>
</div>
</div>
)
}
}
class DataRow extends React.Component {
render () {
let {grade} = this.props
return (
<tr >
<td>{grade.GradeInfo['studentID']}</td>
<td>{grade.GradeInfo['semester']}</td>
<td>{grade.GradeInfo['period']}</td>
</tr>
)
}
}
Here is the code for the form:
import React from 'react'
import GradeInfo from 'GradeInfo'
import {setUsers, getUsers, filterUsers} from 'UsersApi'
import UserList from 'UserList'
export default class GradeForm extends React.Component {
constructor (props) {
super(props)
this.state = {
grade: getUsers()
}
this.submitInfo = this.submitInfo.bind(this)
}
componentDidUpdate () {
setUsers(this.state.grade)
}
submitInfo (event) {
event.preventDefault()
let gradeInfo = Object.assign({}, this.props.grade)
this.setState({
grade: [
...this.state.grade,
{gradeInfo}
]
})
this.props.clearCurrentGrade()
}
render () {
let {
grade,
edit,
updateGrade,
updateGradeState,
} = this.props
return (
<div className='row'>
<div className='columns large-centered large-12 medium-12'>
<div className='form'>
<div className='container'>
<form action='' onSubmit={this.submitInfo}>
<div className='student-form__container'>
<GradeInfo edit={edit} handleChange={updateGrade('GradeInfo')} {...grade.GradeInfo} />
<button className='button expanded' type='submit'>Save</button>
</div>
</form>
</div>
</div>
</div>
</div>
)
}
}
Related
I am fetching data using axios and then map state to props with redux but I have a problem. If I dispatch the action in componentDidUpdate() the action execute indefinitely and if I used the constructor(props) I get undefined value for props so where I should fetch the data ?
import React, { Component } from 'react'
import {connect} from 'react-redux'
import { getUserPosts } from '../../actions'
class UserPosts extends Component {
//UNSAFE_componentWillMount() {
//}
constructor(props) {
super(props);
console.log(props);
}
componentDidUpdate() {
//this.props.dispatch(getUserPosts(this.props.user_reducer.login?.user._id));
}
showUserPosts = (user) => (
Array.isArray(user.userPosts) ?
user.userPosts.map((item, i) => (
<tr key={i}>
<td>{i}</td>
<td>author</td>
<td>date</td>
</tr>
))
: null
)
render() {
let user = this.props.user_reducer;
//console.log(user.userPosts);
return (
<div>
<div className="user_posts">
<h4>Your reviews:</h4>
<table>
<thead>
<tr>
<th>Name</th>
<th>Author</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{this.showUserPosts(user)}
</tbody>
</table>
</div>
</div>
)
}
}
function mapStateToProps(state) {
//console.log(state);
return {
user_reducer: state.user_reducer
}
}
export default connect(mapStateToProps)(UserPosts)
action:
export function getUserPosts(userId) {
const req = axios.get(`/api/user_posts?user=${userId}`)
.then(res => res.data);
return {
type: 'GET_USER_POSTS',
payload: req
}
}
componentDidMount() is the best placement for the call to fetch.
Here is an example implementation of the axios fetch from componentDidMount():
import React from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios'
class UserPosts extends React.Component {
constructor(props) {
super(props)
// Initiate state with an empty array of user posts
this.state = { userPosts: [] }
}
componentDidMount() {
axios.get('http://api-url-here')
.then((response) => {
// Set the userPosts when data is received.
// render method will show user posts when state changes
this.setState({userPosts: response.data})
})
}
showUserPosts = (user) => (
Array.isArray(user.userPosts) ?
user.userPosts.map((item, i) => (
<tr key={i}>
<td>{i}</td>
<td>author</td>
<td>date</td>
</tr>
))
: null
)
render() {
let user = this.state;
//console.log(user.userPosts);
return (
<div>
<div className="user_posts">
<h4>Your reviews:</h4>
<table>
<thead>
<tr>
<th>Name</th>
<th>Author</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{this.showUserPosts(user)}
</tbody>
</table>
</div>
</div>
)
}
}
ReactDOM.render(<UserPosts />, document.getElementById('root'))
I have been working with code that uses table,
working code
export class BoardList extends Component {
static propTypes = {
boards: PropTypes.array.isRequired,
getBoards: PropTypes.func.isRequired,
deleteBoard: PropTypes.func.isRequired,
}
componentDidMount() {
this.props.getBoards();
}
render(){
this.props.boards.sort((boardA, boardB) => {return boardA.id - boardB.id })
const sortRow = this.props.boards.map(board => {
return (
<tr key={board.id}>
<td>{board.id}</td>
<td>{board.author}</td>
<td>{board.title}</td>
<td>{board.created}</td>
<td>{board.updated}</td>
<td>
<button className="btn btn-danger btn-sm" onClick={this.props.deleteBoard.bind(this, board.id)}>
Delete
</button>
</td>
</tr>
)
})
return (
<Fragment>
<table className="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Author</th>
<th>Title</th>
<th>Created</th>
<th>Updated</th>
<th />
</tr>
</thead>
<tbody>
{this.props.boards.length > 0 && (sortRow)}
</tbody>
</table>
</Fragment>
)
}
}
const mapStateToProps = state => ({
boards: state.boards.boards
})
export default connect(mapStateToProps, {getBoards, deleteBoard})(BoardList)
However, if I use different element, it does not work.
export class BoardList extends Component {
constructor(props){
super(props)
this.createCard = this.createCard.bind(this)
}
static propTypes = {
boards: PropTypes.array.isRequired,
getBoards: PropTypes.func.isRequired,
deleteBoard: PropTypes.func.isRequired,
}
componentDidMount() {
this.props.getBoards();
}
createCard(board) {
return (
<div key={board.id}>
<div className="card text-white bg-primary mb-3" style="max-width:20rem">
<div className="card-header">{board.author}</div>
<div className="card-body">
<h4 className="card-title">{board.title}</h4>
<p className="card-text">{board.body}</p>
<img src={board.image} style="max-width:100px"/>
</div>
</div>
</div>
)
}
render() {
this.props.boards.sort((boardA, boardB) => { return boardA.id - boardB.id });
const cardBoard = this.props.boards.map(this.createCard);
return (
<Fragment>
<h2>Boards</h2>
{this.props.boards.length > 0 && (cardBoard)}
</Fragment>
)
}
const mapStateToProps = state => ({
boards: state.boards.boards
})
export default connect(mapStateToProps, {getBoards, deleteBoard})(BoardList)
I get Uncaught (in promise) TypeError: Cannot read property 'data' of undefined. That data is from the my redux action.
export const getBoards = () => (dispatch, getState) => {
axios
.get("api/boards/", tokenConfig(getState))
.then(res => {
dispatch({
type: GET_BOARDS,
payload: res.data
})
})
.catch(err => dispatch(returnErrors(err.response.data, err.response.status)));
}
}
I think both methods map the props, so there shouldn't be any issue, but since I am new to React, I must be missing something here.
I have a form that is wrapped from app.js who receive props from there to update my inputs and buttons on the file cadastro.js, but when I click on edit register my button change to edit from my function getDerivedStateFromProps my input with description doesn't change. It just updates after I click two times on the button edit.
But if I debug on the console in the function getDerivedStateFromProps show me at the right time. What is the problem with my code?
App.js
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Cadastrar from "./components/Cadastrar";
import Tabela from "./components/Tabela";
class App extends Component {
state = {
update: '',
idTamanhoEditar: ''
}
editarRegistro = (idRegistroEditarTabela) => {
this.setState({idTamanhoEditar: idRegistroEditarTabela})
}
updateTabela = (atualizarTabela) => {
this.setState({update: atualizarTabela})
}
render() {
return(
<div>
<Cadastrar atualizarTabela={this.updateTabela} editarFromParent={this.state.idTamanhoEditar}/>
<Tabela editarRegistro={this.editarRegistro} updateFromParent={this.state.update} />
</div>
)
}
}
export default App;
Cadastrar.js
import React, { Component } from 'react';
import './Cadastrar.css';
import axios from "axios";
class Cadastrar extends Component {
constructor(props) {
super(props);
this.state = {
tamanho: {
id: '',
descricao: '',
},
error: '',
sucess: '',
tipoAcao: 'Cadastrar'
};
this.atualizaDados = this.atualizaDados.bind(this);
this.cadastrar = this.cadastrar.bind(this);
}
atualizaDados(e) {
let tamanho = this.state.tamanho;
tamanho[e.target.name] = e.target.value;
this.setState({tamanho: tamanho});
}
cadastrar(e) {
const {tamanho} = this.state;
if(tamanho.descricao !== '') {
axios.post(`http://localhost/react-project/src/api/register.php`, { descricao: tamanho.descricao })
.then(res => {
if(res.data === 'sucess') {
this.setState({tamanho:{id:'', descricao: ''}})
//Tabela.atualizarItensTabela();
this.setState({sucess: 'Cadastro efetuado com sucesso!', error: ''})
this.props.atualizarTabela(true);
}
})
} else {
this.setState({error: 'Preencha o campo descrição!', sucess: ''})
}
e.preventDefault();
}
static getDerivedStateFromProps(props, state) {
if(props.editarFromParent !== state.tamanho.id ) {
console.log("Entrou");
state.tamanho.id = props.editarFromParent;
state.tipoAcao = 'Atualizar';
state = Cadastrar.consultarTamanho(state.tamanho.id, state);
}
return null;
}
static consultarTamanho(idTamanho, state) {
axios.post(`http://localhost/react-project/src/api/consult.php`, { id: idTamanho })
.then(res => {
if(res.data.descricao) {
state.tamanho.descricao = res.data.descricao;
}
})
return state;
}
render() {
return (
<div id='formulario-de-cadastro' className='container'>
<div className='page-header'>
<h2 className='titulo-cadastrar-tamanho'>Cadastrar Tamanho</h2>
</div>
<form onSubmit={this.cadastrar}>
<input type='hidden' name='id' value={this.state.tamanho.id} onChange={ this.atualizaDados } /><br/>
<div className='form-group'>
<label htmlFor='descricao'>Descrição</label>
<input type='text' className='form-control' name='descricao' id='descricao' onChange={ this.atualizaDados } value={this.state.tamanho.descricao} /><br/>
<button type='submit' className='btn btn-primary'>{this.state.tipoAcao}</button>
<button type='submit' className='btn btn-danger ml-1'>Cancelar</button>
</div>
</form>
{this.state.error && <p className='alert alert-warning'>{this.state.error}</p>}
{this.state.sucess && <p className='alert alert-success'>{this.state.sucess}</p>}
</div>
);
}
}
export default Cadastrar;
Tabela.js
import React, { Component } from 'react';
import axios from 'axios';
import './Tabela.css';
class Tabela extends Component {
constructor(props) {
super(props);
this.state = {
tamanhos: [],
tamanho: {
id: '',
descricao: ''
},
}
this.apagarTamanho = this.apagarTamanho.bind(this);
this.atualizarItensTabela = this.atualizarItensTabela.bind(this);
}
componentDidMount() {
this.atualizarItensTabela();
}
atualizarItensTabela() {
let url = 'http://localhost/react-project/src/api/consultAll.php';
fetch(url)
.then((r) => r.json())
.then((json) => {
this.setState({tamanhos: json});
});
}
apagarTamanho(e, idTamanho) {
e.preventDefault();
axios.post(`http://localhost/react-project/src/api/delete.php`, { id: idTamanho })
.then(res => {
if(res.data === 'sucess') {
this.atualizarItensTabela();
}
})
}
editarTamanho(e, idTamanho) {
this.props.editarRegistro(idTamanho);
e.preventDefault();
}
render() {
return (
<div className='container mt-5'>
{this.props.updateFromParent && this.atualizarItensTabela()}
<table id='tabela-tamanhos' className='table table-hover'>
<thead>
<tr>
<th scope="col">Código</th>
<th scope="col">Descrição</th>
<th scope="col">Ações</th>
</tr>
</thead>
<tbody>
{this.state.tamanhos.map(
tamanho=>
<tr key={tamanho.id} className='row-tamanho'>
<th scope="row">{tamanho.id}</th>
<td>{tamanho.descricao}</td>
<td>
<button className='btn btn-primary mr-1' onClick={(e)=>this.editarTamanho(e, tamanho.id)}>Editar</button>
<button className='btn btn-danger' onClick={(e)=>this.apagarTamanho(e, tamanho.id)}>Apagar</button>
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
}
export default Tabela;
You are not returning anything from getDerivedStateFromProps you have to return an object to update the state
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
Change thegetDerivedStateFromProps `` method with the below. Return an object rather mutating the state.
state = Cadastrar.consultarTamanho(state.tamanho.id, state);
if (props.editarFromParent !== state.tamanho.id) {
console.log("Entrou");
return {
tamanho: {
id: props.editarFromParent,
descricao: '',
},
error: '',
sucess: '',
tipoAcao: 'Atualizar'
}
}
return null;
And Call the side effect state = Cadastrar.consultarTamanho(state.tamanho.id, state); in componentDidUpdate.
If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
I have implement React app getting database from MongoDB with Express Server.
For Pagination function is working well but when I implement search function is working only when typing in the input box. If I delete the character, it should search again but it is still.
Could anybody please help to verify my code??
IssueList.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import 'whatwg-fetch';
import Pagination from '../components/Pagination';
import IssueAdd from '../components/IssueAdd';
class IssueList extends Component {
constructor(props) {
super(props);
this.state = {
issues: [],
pageOfItems: [],
};
this.createIssue = this.createIssue.bind(this);
this.onChangePage = this.onChangePage.bind(this);
this.filterList = this.filterList.bind(this);
}
componentDidMount() {
this.loadData();
}
loadData() {
fetch('/api/issues').then(response => {
if (response.ok) {
response.json().then(data => {
data.records.forEach(issue => {
issue.created = new Date(issue.created);
if (issue.completionDate) {
issue.completionDate = new Date(issue.completionDate);
}
});
this.setState({ issues: data.records });
});
} else {
response.json().then(error => {
alert(`Failed to fetch issues ${error.message}`);
});
}
}).catch(err => {
alert(`Error in fetching data from server: ${err}`);
});
}
onChangePage(pageOfItems) {
this.setState({ pageOfItems: pageOfItems });
}
filterList = (e) => {
var updatedList = this.state.issues;
updatedList = updatedList.filter((item) => {
return item.title.toLowerCase().search(e.target.value.toLowerCase()) !== -1;
});
this.setState({ issues: updatedList });
}
render() {
return (
<div>
<h1>Issue Tracker</h1>
<hr />
<div className="filter-list">
<form>
<fieldset className="form-group">
<legend>Search</legend>
<input
type="text"
className="form-control form-control-lg"
placeholder="Search"
onChange={this.filterList}
/>
</fieldset>
</form>
</div>
<div className="panel panel-default">
<table className="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Owner</th>
<th>Created</th>
<th>Effort</th>
<th>Completion Date</th>
<th>Title</th>
</tr>
</thead>
<tbody>
{this.state.pageOfItems.map(issue => (
<tr key={issue._id}>
<td>{issue._id}</td>
<td>{issue.status}</td>
<td>{issue.owner}</td>
<td>{issue.created.toDateString()}</td>
<td>{issue.effort}</td>
<td>{issue.completionDate ? issue.completionDate.toDateString() : ''}</td>
<td>{issue.title}</td>
</tr>
))}
</tbody>
</table>
</div>
<Pagination
items={this.state.issues}
onChangePage={this.onChangePage}
/>
<hr />
<IssueAdd createIssue={this.createIssue} />
</div>
);
}
}
export default IssueList;
Edited
I've tried to add loadData() function to the filterList()
filterList = (e) => {
this.loadData();
var updatedList = this.state.issues;
updatedList = updatedList.filter((item) => {
return item.title.toLowerCase().search(e.target.value.toLowerCase()) !== -1;
});
this.setState({ issues: updatedList });
}
It can search but after that it goes back to the initial state (page 1).
you need to add the value parameter to your input in order to control it's value. This could be your issue. I updated this to include adding a holder in state that holds the unfiltered array.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import 'whatwg-fetch';
import Pagination from '../components/Pagination';
import IssueAdd from '../components/IssueAdd';
class IssueList extends Component {
constructor(props) {
super(props);
this.state = {
issues: [],
holder: [],
pageOfItems: [],
};
this.createIssue = this.createIssue.bind(this);
this.onChangePage = this.onChangePage.bind(this);
this.filterList = this.filterList.bind(this);
}
componentDidMount() {
this.loadData();
}
loadData() {
fetch('/api/issues').then(response => {
if (response.ok) {
response.json().then(data => {
data.records.forEach(issue => {
issue.created = new Date(issue.created);
if (issue.completionDate) {
issue.completionDate = new Date(issue.completionDate);
}
});
this.setState({ issues: data.records, holder: data.records });
});
} else {
response.json().then(error => {
alert(`Failed to fetch issues ${error.message}`);
});
}
}).catch(err => {
alert(`Error in fetching data from server: ${err}`);
});
}
onChangePage(pageOfItems) {
this.setState({ pageOfItems: pageOfItems });
}
filterList = (e) => {
let { value } = e.target
this.setState({ value }, () => {
//running this after setting the value in state because of async
var updatedList = this.state.holder;
updatedList = updatedList.filter((item) => {
return item.title.toLowerCase().search(this.state.value.toLowerCase()) !== -1;
});
this.setState({ issues: updatedList });
})
}
render() {
return (
<div>
<h1>Issue Tracker</h1>
<hr />
<div className="filter-list">
<form>
<fieldset className="form-group">
<legend>Search</legend>
<input
type="text"
className="form-control form-control-lg"
placeholder="Search"
value={this.state.value}
onChange={this.filterList}
/>
</fieldset>
</form>
</div>
<div className="panel panel-default">
<table className="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Owner</th>
<th>Created</th>
<th>Effort</th>
<th>Completion Date</th>
<th>Title</th>
</tr>
</thead>
<tbody>
{this.state.pageOfItems.map(issue => (
<tr key={issue._id}>
<td>{issue._id}</td>
<td>{issue.status}</td>
<td>{issue.owner}</td>
<td>{issue.created.toDateString()}</td>
<td>{issue.effort}</td>
<td>{issue.completionDate ? issue.completionDate.toDateString() : ''}</td>
<td>{issue.title}</td>
</tr>
))}
</tbody>
</table>
</div>
<Pagination
items={this.state.issues}
onChangePage={this.onChangePage}
/>
<hr />
<IssueAdd createIssue={this.createIssue} />
</div>
);
}
}
export default IssueList;
I am passing the state of the parent component from the parent component to the child component.And in the child component,I have a different state.I am performing some actions on the child component's state and the result of that has to be added to the parent component's state.So,in my parent component I have written a callback function which will update the state of the parent component.The code is:
updateState = (booksList) => {
this.setState({books : this.state.books.push(booksList)});
}
So,this function is then passed to the child component as props:
<BookSearch
books={this.state.books}
handleShelfChange={this.handleShelfChange}
updateState={this.updateState}/>
Then in my child component,I am trying to implement the callback function as :
let getBook = this.state.books.filter(filteredBook => filteredBook.shelf !== "none")
this.props.updateState(getBook)
But this is not working as expected.Is this the correct way?Can anyone please help me with this?
I have tried to solve my problem by implementing the solution provided here : How to pass data from child component to its parent in ReactJS? , but I am getting some errors.
EDIT
Parent component : App.js
import React from 'react'
import * as BooksAPI from './BooksAPI'
import { Link } from 'react-router-dom'
import { Route } from 'react-router-dom'
import './App.css'
import BookList from './BookList'
import BookSearch from './BookSearch'
class BooksApp extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [],
showSearchPage : false
};
//this.updateState = this.updateState.bind(this)
}
componentDidMount() {
BooksAPI.getAll().then((books) => {
this.setState({ books })
})
console.log(this.state.books);
}
filterByShelf = (bookName,shelfName) =>
bookName.filter(book => book.shelf===shelfName)
isTheBookNew = book => {
let is = false;
if (book.shelf === "none")
{ this.setState(state =>
{
books: state.books.push(book)});
is = true;
console.log(this.state.books);
}
return is;
};
handleShelfChange = (bookOnChange, newSehlf) => {
!this.isTheBookNew(bookOnChange) && this.setState(state => {
let newBooks = state.books.map(book =>
{ if (bookOnChange.id === book.id)
{ book.shelf = newSehlf; }
return book;
});
return {
books: newBooks
};
}
);
BooksAPI.update(bookOnChange, newSehlf);
};
updateState = (booksList) => {
const books = [...this.state.books, booksList]
this.setState({ books });
}
render() {
return (
<div className="app">
<Route exact path="/" render={() => (
<div className="list-books">
<div className="list-books-title">
<h1>MyReads</h1>
</div>
<BookList
books={this.filterByShelf(this.state.books,'currentlyReading')}
shelfName='Currently Reading'
handleShelfChange={this.handleShelfChange}/>
<BookList
books={this.filterByShelf(this.state.books,'wantToRead')}
shelfName='Want to Read'
handleShelfChange={this.handleShelfChange}/>
<BookList
books={this.filterByShelf(this.state.books,'read')}
shelfName='Read'
handleShelfChange={this.handleShelfChange}/>
<div className="open-search">
<Link
to="./search" />
</div>
</div>
)
} />
<Route path="/search" render={() =>
<BookSearch
books={this.state.books}
handleShelfChange={this.handleShelfChange}
updateState={this.updateState}/>
} />
</div>
)
}
}
export default BooksApp
BookSearch.js :
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import escapeRegExp from 'escape-string-regexp'
import sortBy from 'sort-by'
import * as BooksAPI from './BooksAPI'
import BookList from './BookList'
class BookSearch extends Component {
constructor(props) {
super(props);
this.state = {
search:'',
books:[]
}
}
updateSearch = (searchString) => {
this.setState({search: searchString.trim()})
let searchResults = BooksAPI.search(this.state.search,1).then((book_search) => {
if (book_search != undefined) {
console.log(book_search);
book_search.map((book) => book.shelf = 'none');
this.setState({ books : book_search }, this.check); // callback function to this.setState
console.log(this.state.books)
}
})
}
check = () => {
let parent_books = this.props.books;
console.log(this.state.books)
const book_result = this.state.books.map((book) => {
const parent = parent_books.find(parent => parent.title === book.title );
if(parent) {
//console.log(parent);
book.shelf = parent.shelf;
//console.log(book)
}
return book;
})
this.setState({books: book_result}, () => {console.log(this.state.books)})
}
updateParentState = () => {
let getBook = this.state.books.filter(filteredBook => filteredBook.shelf !== "none")
this.props.updateState(getBook)
}
render() {
return(
<div className="search-books">
<div className="search-books-bar">
<Link
to="/"
className="close-search">
Close
</Link>
<div className="search-books-input-wrapper">
<input
type="text"
placeholder="Search by title or author"
value={this.state.search}
onChange={(event) => this.updateSearch(event.target.value)}/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid">
<BookList
books={this.state.books}
handleShelfChange={this.props.handleShelfChange}
updateParentState={this.updateParentState}/>
</ol>
</div>
</div>
)
}
}
export default BookSearch
BookList.js
import React, { Component } from 'react';
import Book from './Book'
class BookList extends Component {
constructor(props) {
super(props);
this.state = {
showSearchPage : false
}
console.log(this.props.books)
}
render() {
return(
<div className="app">
<div>
<div className="list-books-content">
<div>
<div className="bookshelf">
<h2 className="bookshelf-title">{this.props.shelfName}</h2>
<div className="bookshelf-books">
<ol className="books-grid">
{this.props.books.map(book =>
<li key={book.title}>
<Book
book={book}
handleShelfChange={this.props.handleShelfChange}
update={this.props.updateParentState} />
</li>)
}
</ol>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default BookList;
Book.js
import React, { Component } from 'react'
class Book extends Component {
constructor(props) {
super(props);
this.props.updateParentState;
}
render() {
return(
<div className="book">
<div key={this.props.book.title}>
<div className="book-top">
<div className="book-cover" style={{width:128, height:193, backgroundImage: `url(${this.props.book.imageLinks.thumbnail})`}}>
<div className="book-shelf-changer">
<select id="bookName" value={this.props.book.shelf}
onChange={(event) => this.props.handleShelfChange(this.props.book, event.target.value)}>
<option value="moveTo" disabled>Move to...</option>
<option value="currentlyReading">Currently Reading</option>
<option value="wantToRead">Want to Read</option>
<option value="read">Read</option>
<option value="none">None</option>
</select>
</div>
</div>
</div>
<div className="book-title">{this.props.book.title}</div>
<div className="book-authors">{this.props.book.authors}</div>
</div>
</div>
)
}
}
export default Book
So, I have 4 components as shown above.From App component,I am calling BookSearch component where I can search for books and send the books to App component by selecting the value in dropdown. Each book in BookSearch component will initially be assigned a shelf property of "none".When a user selects a shelf value from Booksearch,the book will automatically be added to the App component.So,when I navigate back to the App component from BookSearch,I should be able to see the book in the assigned shelf.So,for this case,I am using the updateSearch function. The books are displayed through the Book component which has the dropdown value.
If i understood this correctly, you are mutating the state in the function updateState.
What you should be doing instead is,
const updateState = (booksList) => {
const books = [ ...this.state.books, ...booklist ];
this.setState({ books });
}
In parent component constructor add the following line:
this.updateState = this.updateState.bind(this)
More at https://reactjs.org/docs/handling-events.html