I have a basic React app with a function in the App.js component to pass down as a prop for subsequent components to handle moving a book between shelves.
When I pass the function down one level, from App.js to ListBooks.js to be passed on the Books.js component which is where the user will select the new shelf and trigger the API call and state update, this works fine.
However when I then try to do the same from App.js > SearchBooks.js > Book.js it does not seem to work.
What I expect is for the updateShelf function to be called to update the book and the state.
Apologies for 'wall of code' if there's too much there, just not sure exactly where the issue is.
EDIT:
As suggested in comments, here is a CodeSandbox version:
https://codesandbox.io/s/github/richardcurteis/myreads-udacity
App.js
import React, { Component } from 'react'
import ListBooks from './ListBooks'
import SearchBooks from './SearchBooks'
import * as BooksAPI from './utils/BooksAPI'
import { Route } from 'react-router-dom'
class BooksApp extends Component {
state = {
books: []
}
componentDidMount() {
BooksAPI.getAll()
.then((books) => {
this.setState(() => ({
books
}))
})
}
updateShelf = (book, shelf) => {
this.state.books.forEach(b => {
if(b.id === book.id) {
b.shelf = shelf
this.setState((currentState) => ({
books: currentState.books
}))
BooksAPI.update(book, shelf)
}
});
}
render() {
return (
<div>
<Route exact path='/' render={() => (
<ListBooks
books={this.state.books}
onUpdateShelf={this.updateShelf}
/>
)} />
<Route exact path='/search' render={() => (
<SearchBooks
onUpdateShelf={this.updateShelf}
/>
)} />
</div>
)
}
}
export default BooksApp
SearchBooks.js
import React, { Component } from 'react'
import * as BooksAPI from './utils/BooksAPI'
import Book from './Book';
export default class SearchBooks extends Component {
state = {
query: '',
books: []
}
updateQuery(query) {
this.setState(() => ({
books: [],
query: query
}))
this.bookSearch(query)
}
bookSearch(e) {
if (e.length > 0) BooksAPI.search(e)
.then(books => this.setState(currentState => ({
books: books
})));
}
render() {
const { query, books } = this.state
const { onUpdateShelf } = this.props
return(
<div className="search-books">
<div className="search-books-bar">
<a className="close-search" >Close</a>
<div className="search-books-input-wrapper">
<input
type="text"
placeholder="Search by title, author or subject"
value={query}
onChange={(event) => this.updateQuery(event.target.value)}
/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid">
<li>
{ !books.error ? (
books.map((book) => (
<Book
key={book.id}
book={book}
updateShelf={onUpdateShelf}
/>
))
) : (
<h4>"{query}", is not a valid search</h4>
)}
</li>
</ol>
</div>
</div>
)
}
}
Book.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
class Book extends Component {
static propTypes = {
book: PropTypes.object.isRequired
}
render() {
const { book, updateShelf } = this.props
return(
<div key={book.id} className="book">
<div className="book-top">
<div className="book-cover" style={{ width: 128, height: 193, backgroundImage: `url(${book.imageLinks.thumbnail})` }}></div>
<div className="book-shelf-changer">
<select value={book.shelf ? book.shelf : 'none'} onChange={(e) => updateShelf(book, e.target.value)}>
<option 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 className="book-title">{book.title}</div>
<div className="book-authors">{book.authors}</div>
</div>
)
}
}
export default Book
Just for reference:
ListBooks.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import './App.css'
import { Link } from 'react-router-dom'
import Book from './Book'
const shelves = [
{
key: 'currentlyReading',
name: 'Currently Reading'
},
{
key: 'wantToRead',
name: 'Want To Read'
},
{
key: 'read',
name: 'Read'
}
];
class ListBooks extends Component {
static propTypes = {
books: PropTypes.array.isRequired,
onUpdateShelf: PropTypes.func.isRequired
}
render() {
const { books, onUpdateShelf } = this.props
function getBooksForShelf(shelfKey) {
return books.filter(book => book.shelf === shelfKey);
}
return(
<div className="app">
<div className="list-books">
<div className="list-books-title">
<h1>My Reads</h1>
</div>
<div className="list-books-content">
<div>
{ shelves.map((shelf) => (
<div key={shelf.key} className="bookshelf">
<h2 className="bookshelf-title">{shelf.name}</h2>
{ getBooksForShelf(shelf.key).length === 0 ? (
<div>
<h4>No books in this shelf</h4>
</div>
) : (
<div className="bookshelf-books">
<ol className="books-grid">
<li>
{ getBooksForShelf(shelf.key).map((book) => (
<Book key={book.id}
book={book}
updateShelf={onUpdateShelf}/>
))}
</li>
</ol>
</div>
)}
</div>
)) }
</div>
</div>
<Link
to='/search'
className="open-search">
Find a Book
</Link>
</div>
</div>
)
}
}
export default ListBooks
As we discussed it's not a problem with the prop passing but rather with updating the state. Here is the modified code that seems to work:
updateShelf = (book, shelf) => {
const bookFromState = this.state.books.find(b => b.id === book.id);
if (bookFromState) {
// update existing
bookFromState.shelf = shelf;
this.setState(currentState => ({
books: currentState.books
}));
BooksAPI.update(book, shelf);
} else {
// add new one
this.setState(prevState => ({ books: [...prevState.books, book] }));
BooksAPI.update(book, shelf);
}
};
Or better without mutating the state:
updateShelf = (book, shelf) => {
this.setState(prevState => {
const booksCopy = prevState.books.filter(b => b.id !== book.id);
booksCopy.push({ ...book, shelf });
return { books: booksCopy }
});
BooksAPI.update(book, shelf);
};
Related
I want to filter product by size and also sort it by size but when I implement it in redux I get the error that map its not a function in product component here is my action Creator page. This is where I fetch the product from server
export const fetchProduct = () => (dispatch) => {
dispatch(productLoading(true));
return fetch(baseUrl + "products")
.then(
(response) => {
console.log('response',response)
if (response.ok) {
return response;
} else {
var error = new Error(
"Error " + response.status + ": " + response.statusText
);
error.response = response;
throw error;
}
},
(error) => {
var errmess = new Error(error.message);
throw errmess;
}
)
.then((response) => response.json())
.then((products) => dispatch(addProduct(products)))
.catch((error) => dispatch(productFailed(error.message)));
};
export const productLoading = () => ({
type: ActionTypes.PRODUCT_LOADINGS,
});
export const productFailed = (errmess) => ({
type: ActionTypes.PRODUCT_FAILURES,
payload: errmess,
});
export const addProduct = (products) => ({
type: ActionTypes.ADD_PRODUCTS,
payload: products,
});
export const filterProducts = (product, size) => (dispatch) => {
dispatch({
type: ActionTypes.FILTER_PRODUCTS_BY_SIZE,
payload: {
size: size,
products:
size === ""
? product
: product.filter((x) => x.availableSizes.indexOf(size) >= 0),
},
});
};
Here is where I short and filter the product
export const sortProducts = (sort, filteredProduct) => (dispatch) => {
const sortedProduct = filteredProduct.slice();
if (sort === "latest") {
sortedProduct.sort((a, b) => (a._id > b._id ? 1 : -1));
} else {
sortedProduct.sort((a, b) =>
sort === "lowest"
? a.price > b.price
? 1
: -1
: a.price > b.price
? -1
: 1
);
}
dispatch({
type: ActionTypes.SORT_PRODUCT_BY_PRICE,
payload: { sort: sort, products: sortedProduct },
});
};
This is my productReducer.js page
import * as ActionTypes from "./ActionsType";
export const productsReducer = (
state = {
isLoading: true,
errMess: null,
products: [],
},
action
) => {
switch (action.type) {
case ActionTypes.FILTER_PRODUCTS_BY_SIZE:
return {
...state,
size: action.payload.size,
filteredItems: action.payload.products,
};
case ActionTypes.SORT_PRODUCT_BY_PRICE:
return {
...state,
sort: action.payload.sort,
filteredItems: action.payload.products,
};
case ActionTypes.ADD_PRODUCTS:
console.log(" products: action.payload, filteredItems:action.payload", {
products: action.payload,
filteredItems: action.payload,
});
return {
...state,
isLoading: false,
errMess: null,
products: action.payload,
filteredItems: action.payload,
};
case ActionTypes.PRODUCT_LOADINGS:
return { ...state, isLoading: true, errMess: null, products: [] };
case ActionTypes.PRODUCT_FAILURES:
return {
...state,
isLoading: false,
errMess: action.payload,
products: [],
};
default:
return state;
}
};
This is my main component where I passed the product as props to product component
import React, { Component } from "react";
import Footer from "./FooterComponent";
import { Header } from "./HeaderComponent";
import Product from "./ProductsComponent";
import { Switch, Route, Redirect, withRouter } from "react-router-dom";
import { Sidebar } from "./Sidebars";
import {
addProductToCart,
addToCart,
decrementCartQuantity,
fetchProduct,
filterProducts,
incrementCartQuantity,
loginUser,
removeFromCart,
SignUpReq,
sortProducts,
} from "../redux/ActionCreators";
import { connect } from "react-redux";
import ProductDetailsComponent from "./ProductDetailsComponent";
import SignupForm from "./signup";
import AboutUs from "./AboutUs";
import { actions } from "react-redux-form";
import Signup from "./Signup copy 2";
import CartItemsComponent from "./CartItemsComponent";
import Filter from "./Filter";
const mapStateToProps = (state) => {
return {
products: state.product.filteredItems,
product: state.product.products,
size: state.product.size,
sort: state.product.sort,
filteredProducts: state.product.filteredItems,
};
};
const mapDispatchToProps = (dispatch) => ({
fetchProduct: () => dispatch(fetchProduct()),
filterProducts: () => dispatch(filterProducts()),
sortProducts: () => dispatch(sortProducts()),
},
});
class Main extends Component {
componentDidMount() {
this.props.fetchProduct();
}
render() {
const GetItem = ({ match, params }) => {
return (
<ProductDetailsComponent
product={
this.props.products.filter(
(product) => product._id === match.params.productId
)[0]
}
isLoading={this.props.products.isLoading}
errMess={this.props.products.errMess}
addToCart={this.props.addToCart}
/>
);
};
return (
<div>
<Header
loginUser={this.props.loginUser}
cartItems={this.props.cartItems}
/>
<Filter
filterProducts={this.props.filterProducts}
sortProducts={this.props.sortProducts}
product={this.props.products}
filteredProducts={this.props.filteredProducts}
/>
<Switch>
<Route
exact
path="/"
component={() => (
<Product
product={this.props.products}
/>
)}
/>
<Route path="/:productId" component={GetItem} />
<Route exact path="/contactus" />
<Redirect to="/default" />
</Switch>
<Footer />
</div>
);
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Main));
This is my ProductsComponent.js page
import React from "react";
import { Loading } from "./LoadingComponent";
import { GridWrapper } from "./StyleComponent";
import {
Card,
CardImg,
CardTitle,
Button,
Breadcrumb,
BreadcrumbItem,
} from "reactstrap";
import { Link } from "react-router-dom";
import { baseUrl } from "./shared/baseUrl";
import formatCurrency from "./shared/util";
function RenderProductItem({ product, addToCart }) {
return (
<div className="container">
<div className="card">
<Link to={`/${product._id}`}>
<div className="img-container p-5">
<img
src={baseUrl + product.image}
alt={product.title}
className="card-img-top"
/>
</div>
</Link>
<div className="d-flex justify-content-between">
<p className="align-self-center mb-0">{product.title}</p>
<h5 className="text-blue align-self-center font-italic mb-0">
<span className="mr-1">{formatCurrency(product.price)}</span>
</h5>
</div>
<div className="card-footer ">
<button
className="buttonContainer col-12"
onClick={() => {
addToCart(product);
}}
>
add-to-cart
</button>
</div>
</div>
</div>
);
}
function Product(props) {
console.log( "props", props)
const products = props.product.map((product) => {
return (
<div key={product._id} className="col-12 mx-auto col-md-5 my-3">
<RenderProductItem product={product} addToCart={props.addToCart} />
</div>
);
});
if (props.product.isLoading) {
return (
<div className="container">
<div className="row">
<Loading />
</div>
</div>
);
}
else if (props.product.errMess) {
return (
<div className="container">
<div className="row">
<h4>{props.product.errMess}</h4>
</div>
</div>
);
} else
return (
<>
<div className="container">
<div className="row">
<Breadcrumb>
<BreadcrumbItem>
<Link to="/home">Home</Link>
</BreadcrumbItem>
<BreadcrumbItem active>Products</BreadcrumbItem>
</Breadcrumb>
<div className="col-12">
<h3>Products</h3>
<hr />
</div>
</div>
<div className="row">{products}</div>
</div>
</>
);
}
export default Product;
This is my filter.js page
import React, { Component } from "react";
class Filter extends Component {
render() {
return !this.props.filteredProducts ? (
<div>Loading...</div>
) : (
<div className="filter">
<div className="filter-result">
{this.props.filteredProducts.length} Products
</div>
<div className="filter-sort">
Order{" "}
<select
value={this.props.sort}
onChange={(e) =>
this.props.sortProducts(
this.props.filteredProducts,
e.target.value
)
}
>
<option value="latest">Latest</option>
<option value="lowest">Lowest</option>
<option value="highest">Highest</option>
</select>
</div>
<div className="filter-size">
Filter{" "}
<select
value={this.props.size}
onChange={(e) =>
this.props.filterProducts(this.props.product, e.target.value)
}
>
<option value="">ALL</option>
<option value="XS">XS</option>
<option value="S">S</option>
<option value="M">M</option>
<option value="L">L</option>
<option value="XL">XL</option>
<option value="XXL">XXL</option>
</select>
</div>
</div>
);
}
}
export default Filter
I am new to React and I am trying to filter a list of emails in .users-list. I just want to return what the user is typing on the SearchBox but it does not work. Any suggestions?
Dashboard.js
import React, { Component } from "react";
import Button from "../Button/Button";
import SearchBox from "../SearchBox/SearchBox";
import "./Dashboard.css";
import fire from "../../fire"
class Dashboard extends Component {
constructor(){
super();
this.state = {
users:[],
searchField:''
}
}
handleLogout = () => {
fire.auth().signOut();
};
render() {
const {users, searchField} = this.state
const filteredUsers = users.filter(users => (users.users.toLowerCase.inc))
return (
<div>
<h2>Welcome</h2>
<button onClick={this.handleLogout}>Logout</button>
<div className="users-container">
<div>
<SearchBox
placeholder="Enter email..."
handleChange={(e) =>
this.setState({ searchField: e.target.value})
}
/>
</div>
<ul className="users-list">
<li>
<span>jean#gmail.com</span>
</li>
<li>
<span>albert#gmail.com</span>
</li>
<li>
<span>kevin#gmail.com</span>
</li>
<li>
<span>lucie#gmail.com</span>
</li>
</ul>
</div>
</div>
);
}
}
export default Dashboard;
SearchBox.js
import React from 'react';
const SearchBox = (props) => {
return(
<input
type='search'
className='search'
placeholder={props.placeholder}
onChange={props.handleChange}
/>
)
}
export default SearchBox
You can follow: codesandbox DEMO
For optimize performance:
useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
import React, { useMemo, useState } from "react";
import "./styles.css";
const usersData = [
{id:1,email: 'jean#gmail.com'},
{id:2,email: 'albert#gmail.com'},
{id:3,email: 'kevin#gmail.com'},
]
export default function App() {
const [search, setSearch] = useState("");
const filteredUsers = useMemo(() => {
if (search) {
return usersData.filter(
(item) =>
item.email
.toLowerCase()
.indexOf(search.toLocaleLowerCase()) > -1
);
}
return usersData;
}, [search]);
return (
<div className="App">
<h1>users list</h1>
<input type="search" name="search" value={search} onChange={e => setSearch(e.target.value)} />
<ul>
{filteredUsers.length > 0 ?
(filteredUsers && filteredUsers.map(item => (
<li key={item.id}>{item.email}</li>
))): <div>empty</div>
}
</ul>
</div>
);
}
I am learning React.
I am working on a Myreads book app project. My app renders fine at the start but the moment I click on the select tag, i get the above error message thrown up.
I thought it was a 'propTypes' issue at first and went ahead and installed prop-types but it is still not working.
Here is my cmp where there error occurs and my parent cmp where my method is defined:
// child component
import React from "react";
import Icon from "./icons/add.svg";
import SearchBar from "./SearchBar";
class Book extends React.Component {
render() {
return (
<div className="book">
<div className="book-top">
<div
className="book-cover"
style={{
width: 128,
height: 193,
backgroundImage: `${
this.props.book.imageLinks
? "url(this.props.book.imageLinks.thumbnail)"
: "Icon"
}`
}}
/>
<div className="book-shelf-changer">
<select
onChange={event =>
this.props.moveShelf(this.props.book, event.target.value)
}
value={this.props.book.shelf}
>
<option value="move" disabled>
Move to...
</option>
<option value="currentlyReading">Currently Reading</option>
<option value="wantToRead">Want to Read</option>
<option value="read">Read</option>
<option value="remove">Remove</option>
</select>
</div>
</div>
<div className="book-title">{this.props.book.title}</div>
<div className="book-authors">{this.props.book.authors}</div>
</div>
);
}
}
export default Book;
And the parent component, where method is defined:
// parent component
import React from "react";
import SearchBar from "./SearchBar";
import MainPage from "./MainPage";
import { Route } from "react-router-dom";
import "./App.css";
import * as BooksAPI from "./BooksAPI";
class BooksApp extends React.Component {
constructor(props) {
super(props);
this.moveShelf = this.moveShelf.bind(this);
this.state = {
books: [],
query: "",
searchedBooks: []
};
}
componentDidMount() {
BooksAPI.getAll().then(books => {
this.setState({ books: books });
});
}
moveShelf = (book, shelf) => {
BooksAPI.update(book, shelf); //call update method here to stack books
BooksAPI.getAll().then(books => {
//to have books update dynamically -->
this.setState({ books: books });
});
};
updateQuery = query => {
this.setState({
query: query
});
this.updateSearchedBooks(query);
};
updateSearchedBooks = query => {
query
? BooksAPI.search(query).then(searchedBooks => {
/*fetch searchedbooks using the method defined in BooksAPI */
searchedBooks.error
? this.setState({ searchedBooks: [] })
: this.setState({ searchedBooks });
})
: this.setState({ searchedBooks: [] });
};
render() {
return (
<div className="app">
<Route
exact
path="/"
render={() => (
<MainPage
books={this.state.books} //calling all my books
moveShelf={this.moveShelf}
/>
)}
/>
<Route
path="/search"
render={() => (
<SearchBar
searchedBooks={this.state.searchedBooks}
moveShelf={this.moveShelf}
/>
)}
/>
</div>
);
}
}
export default BooksApp;
I guess that you do not pass moveShelf to Book inside MainPage and/or SearchBar components.
You do not need this.moveShelf = this.moveShelf.bind(this); when you define it as an arrow function moveShelf = (book, shelf) => {...}
I am refactoring a small app that organises books onto respective shelves. I have extracted the JSX into a Book.js file to handle rendering each book object and call the updateShelf method to move books between shelves.
To that end, I have exported the updateShelf function from App.js and imported it into the Book.js component which is embedded in the ListBooks which uses the map function to pass each book object to Book to be rendered.
The issue I have is that when the onChange handler in Book fires I get the below exception:
TypeError: Object(...) is not a function
17 | <div className="book-top">
18 | <div className="book-cover" style={{ width: 128, height: 193, backgroundImage: `url(${book.imageLinks.thumbnail})` }}></div>
19 | <div className="book-shelf-changer">
> 20 | <select value={book.shelf} onChange={(e) => updateShelf(book, e.target.value)}>
21 | <option value="none" disabled >Move to...</option>
22 | <option value="currentlyReading" >Currently Reading</option>
23 | <option value="wantToRead" >Want to Read</option>
I am not sure what it is that I am doing wrong.
I've included quite a lot because I'm not sure exactly where the issue issue's source is. Let me know if I need to trim down the post.
App.js
import React, { Component } from 'react'
import ListBooks from './ListBooks'
import SearchBooks from './SearchBooks'
import * as BooksAPI from './utils/BooksAPI'
import { Route } from 'react-router-dom'
class BooksApp extends Component {
state = {
books: []
}
componentDidMount() {
BooksAPI.getAll()
.then((books) => {
this.setState(() => ({
books
}))
})
}
updateShelf(book, shelf) {
this.state.books.forEach(b => {
if(b.id === book.id && shelf !== '' && shelf !== 'none') {
b.shelf = shelf
this.setState((currentState) => ({
books: currentState.books
}))
BooksAPI.update(book, shelf)
}
});
}
render() {
return (
<div>
<Route exact path='/' render={() => (
<ListBooks
books={this.state.books}
onUpdateShelf={this.updateShelf}
/>
)} />
<Route exact path='/search' render={() => (
<SearchBooks
books={this.state.books}
/>
)} />
</div>
)
}
}
export default BooksApp
export const updateShelf = updateShelf;
ListBooks.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import './App.css'
import { Link } from 'react-router-dom'
import Book from './Book'
const shelves = [
{
key: 'currentlyReading',
name: 'Currently Reading'
},
{
key: 'wantToRead',
name: 'Want To Read'
},
{
key: 'read',
name: 'Read'
}
];
class ListBooks extends Component {
static propTypes = {
books: PropTypes.array.isRequired
}
render() {
const { books } = this.props
function getBooksForShelf(shelfKey) {
return books.filter(book => book.shelf === shelfKey);
}
return(
<div className="app">
<div className="list-books">
<div className="list-books-title">
<h1>My Reads</h1>
</div>
<div className="list-books-content">
<div>
{ shelves.map((shelf) => (
<div key={shelf.key} className="bookshelf">
<h2 className="bookshelf-title">{shelf.name}</h2>
<div className="bookshelf-books">
<ol className="books-grid">
<li>
{ getBooksForShelf(shelf.key).map((book) => (
<Book key={book.id} book={book}/>
))}
</li>
</ol>
</div>
</div>
)) }
</div>
</div>
<Link
to='/search'
className="open-search">
Find a Book
</Link>
</div>
}
</div>
)
}
}
export default ListBooks
Book.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { updateShelf } from './App'
class Book extends Component {
static propTypes = {
book: PropTypes.object.isRequired
}
render() {
const { book } = this.props
return(
<div key={book.id} className="book">
<div className="book-top">
<div className="book-cover" style={{ width: 128, height: 193, backgroundImage: `url(${book.imageLinks.thumbnail})` }}></div>
<div className="book-shelf-changer">
<select value={book.shelf} onChange={(e) => updateShelf(book, e.target.value)}>
<option value="none" 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 className="book-title">{book.title}</div>
<div className="book-authors">{book.authors}</div>
</div>
)
}
}
export default Book
You're exporting export const updateShelf = updateShelf. But updateShelf is a part of the Component, so it's most likely undefined. You should move the method outside of the class if you plan to export it, or pass it down as a prop if you expect it to change the component.
This might be a double of some question, but I couldn't find the answer to the specific question that I have. I have the following code:
import React, { Component } from 'react'
class FAQContent extends Component {
constructor(props) {
super(props);
this.state = {
opened: false,
};
this.toggleBox = this.toggleBox.bind(this);
}
toggleBox() {
const { opened } = this.state;
this.setState({
opened: !opened,
});
}
render() {
return (
<div>
<div className="question">
<div className="question-title" onClick={this.toggleBox}>
Title 1
</div>
{this.state.opened && (
<div class="answer">
Content 1
</div>
)}
</div>
<div className="question">
<div className="question-title" onClick={this.toggleBox}>
Title 2
</div>
{this.state.opened && (
<div class="answer">
Content 2
</div>
)}
</div>
</div>
)
}
}
export default FAQContent
This renders 2 question titles. However, when I click on any of the questions, the state change is triggered for all the questions. What is the most efficient way of showing the specific answer of the question without showing the rest of the components?
import React, { Component } from "react";
import { render } from "react-dom";
import { Link, BrowserRouter, Route } from "react-router-dom";
class App extends Component {
state = {
openedPost: "",
posts: [
{ question: "Question 1", id: 0, user: "lenny" },
{ question: "Question 2", id: 1, user: "benny" },
{ question: "Question 3", id: 2, user: "jenny" }
]
};
showPost = id => {
this.setState({ openedPost: id });
};
render() {
return (
<div>
<BrowserRouter>
<div>
<Route
path="/"
render={() => (
<Posts showPost={this.showPost} posts={this.state.posts} />
)}
/>
<Route
exact
path={`/posts/${this.state.openedPost}`}
render={() => (
<SinglePost
openedPost={this.state.openedPost}
showPost={this.showPost}
posts={this.state.posts}
/>
)}
/>
</div>
</BrowserRouter>
</div>
);
}
}
class Posts extends Component {
onClick = id => {
this.props.showPost(id);
};
render() {
const { posts, showPost } = this.props;
return (
<div>
{posts.map(item => (
<div onClick={() => this.onClick(item.id)}>
<Link to={`/posts/${item.id}`}>{item.question} </Link>{" "}
</div>
))}
</div>
);
}
}
class SinglePost extends Component {
render() {
const { posts, openedPost } = this.props;
const filtered = posts.filter(item => item.id === openedPost);
return (
<div>
{filtered.map(item => (
<div>
{" "}
QUESTION:{item.question} ID:{item.id}{" "}
</div>
))}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Example
You are using a same state to control different parts. How about you make a new question component and let it to manage its own state and just use the question component in the FAQContent component.
Question component:
export default class Question extends Component {
state = { opened: false };
toggleBox = () => this.setState(state => ({ opened: !state.opened }));
render() {
return (
<div className="question">
<div className="question-title" onClick={this.toggleBox}>
{this.props.title}
</div>
{this.state.opened && (
<div class="answer">
{this.props.content}
</div>
)}
</div>
);
}
}
FAQContent Component:
const FAQContent = () => (
<div>
<Question title="title 1" content="content 1" />
<Question title="title 2" content="content 2" />
</div>
);
export default FAQContent;