React Native: showing/hiding the element on click - reactjs

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;

Related

quantiy variable being increased twice in react

I am trying to implement a simple e-commerce application where I have a home component and a cart component whenever I call handle increment or handle decrement, quantity variable is increased or decreased twice in both cart array and items array.so {item.quantity} changes 0,2,4 and so on and this is happening on both cart page and home page. I feel like this has something to do with not using spread operator properly while updating an object in an array.
please help me understand why is this happening and how to resolve it.
Here is my code
shop.jsx
import React, { Component } from 'react';
import Navbar from './nav';
import Cart from './cart';
import Home from './home';
import { Switch, Route } from 'react-router-dom';
class Shop extends Component {
state = {
items: [],
cart: []
}
componentDidMount() {
let items = [...this.state.items];
items.push({ id: 1, name: "product1", price: 100, quantity: 0 });
items.push({ id: 2, name: "product2", price: 200, quantity: 0 });
this.setState({ items });
}
handleIncrement = (item) => {
console.log('handle increment called');
let items = [...this.state.items];
let cart = [...this.state.cart];
let productIndex = items.indexOf(item);
let cartIndex = cart.indexOf(item);
items[productIndex].quantity += 1;
if (cartIndex === -1) {
item.quantity += 1;
cart.push(item);
}
else {
cart[cartIndex].quantity += 1;
}
this.setState({ cart, items });
}
handleDecrement = (item) => {
console.log('handle decrement called');
let items = [...this.state.items];
let cart = [...this.state.cart];
let productIndex = items.indexOf(item);
let cartIndex = cart.indexOf(item);
items[productIndex].quantity -= 1;
cart[cartIndex].quantity -= 1;
this.setState({ cart, items });
}
render() {
return (
<div>
<Navbar />
<Switch>
<Route path="/home" render={(props) => <Home
items={this.state.items}
handleAdd={this.handleAdd}
handleIncrement={this.handleIncrement}
handleDecrement={this.handleDecrement}
{...props} />} />
<Route path="/cart" render={(props) => <Cart
cart={this.state.cart}
handleIncrement={this.handleIncrement}
handleDecrement={this.handleDecrement}
{...props} />} />
</Switch>
</div>
);
}
}
export default Shop;
Home.jsx
import React from 'react';
const Home = (props) => {
return (
<div>
Home
<ul>
{props.items.map((item) => {
return (
<li key={item.id}>
{item.name} <br />
{item.price} <br />
{
item.quantity === 0 ?
<button onClick={() => props.handleIncrement(item)}>add</button> :
<div>
<button onClick={() => props.handleDecrement(item)}>-</button>
<button>{item.quantity}</button>
<button onClick={() => props.handleIncrement(item)}>+</button>
</div>
}
</li>
)
})
}
</ul>
</div>
);
}
export default Home;
cart.jsx
import React from 'react';
const Cart = (props) => {
return (
<div>
cart
<ul>
{props.cart.map((item) => {
return (
<li key={item.id}>
{item.name} <br />
{item.price} <br />
{item.quantity} <br />
{item.price * item.quantity}
</li>
)
})}
</ul>
</div>
);
}
export default Cart;
nav.jsx
import React from 'react';
import { Link } from 'react-router-dom';
const Navbar = () => {
return (
<div>
Navbar
<Link to="/home">Home</Link>
<Link to="/cart">Cart</Link>
</div>
);
}
export default Navbar;
Remove items[productIndex].quantity += 1; from handleIncrement and items[productIndex].quantity -= 1; from handleDecrement method.
This will work.

ReactJS Call Function of another Component react-router

I have implemented an app which uses react-router to handle the routes in my web-app. I want to trigger the function logintoggle which is on the Header.js component from a function from the Hompage.js component. The App.js has all the routes in one file.
Can anyone explain to me how this can be achieved with small code snippet?
App.js
render() {
const { location } = this.props;
return (
<IntlProvider
locale="a"
messages="s"
>
<Fragment>
<div>
<Headers />
<Switch>
<Route exact path="/women" component={HomePage} />
</Switch>
</div>
</Fragment>
</IntlProvider>
);
}
}
export default App;
Header
class Header extends React.Component {
constructor(props) {
super(props);
}
logintoggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
}
Homepage.js
class CheckOut extends Component {
constructor(props) {
super(props);
}
}
When you need to have a shared state among the components React.Context API is what you need. It allows you to create a separate context provider, which will provide the state and the methods to manipulate this state to all the components you need. In the example below I have a LoginContextProvider with activeTab state variable. I provide activeTab and setActiveTab to all the components inside LoginContextProvider's children. Header changes activeTab to 1, Homepage changes to 2 and LoginContextDebug represents the actual activeTab value.
const LoginContext = React.createContext(null);
const LoginContextProvider = ({ children }) => {
const [activeTab, setActiveTab] = React.useState(0);
return (
<LoginContext.Provider value={{ setActiveTab, activeTab }}>
{children}
</LoginContext.Provider>
);
};
const Header = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am header</h1>
<button onClick={() => setActiveTab(1)}>Set activeTab to 1</button>
</div>
);
};
const Homepage = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am homepage</h1>
<button onClick={() => setActiveTab(2)}>Set activeTab to 2</button>
</div>
);
};
const LoginContextDebug = () => {
const { activeTab } = React.useContext(LoginContext);
return (
<pre style={{ padding: 10, background: "lightgray" }}>
activeTab={activeTab}
</pre>
);
};
const App = () => (
<LoginContextProvider value={null}>
<Header />
<Homepage />
<LoginContextDebug />
</LoginContextProvider>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to put a function in route

As a beginner, I'm trying to put my list of "clients" in a function to display in the route/component, is this possible?
App.js
render() {
return (
<div className = "App">
<div>
<AddCliente onAdd = {this.onAdd} />
</div>
<Router>
<div>
<Link to = "/">Cli</Link>
<Route path = "/" exact component = {Adde} />
</div>
</Router>
</div>
);
}
}
I wanna do this:
function Adde() {
return <div>
{
this.state.clientes.map(cliente => {
return (
<ClienteItem
key = {cliente.nome}
{...cliente}
onDelete = {this.onDelete}
/>
);
})
}
</div>;
}
export default Add;
Error:
TypeError: Cannot read property 'state' of undefined
You can do something like this in your component.
import React, { Component } from 'react';
class componentName extends Component {
constructor(props) {
super(props);
this.state = {
clients:[
{
name:'sdfd',
title:'sdfd'
}
]
};
}
Adde=()=>{
return(
<div>
{
this.state.clientes.map(cliente => {
return (
<ClienteItem
key = {cliente.nome}
{...cliente}
onDelete = {this.onDelete}
/>
);
})
}
</div>
);
}
render() {
return (
<div className = "App">
<div>
<AddCliente onAdd = {this.onAdd} />
</div>
<Router>
<div>
<Link to = "/">Cli</Link>
<Route path = "/" exact component = {Adde} />
</div>
</Router>
</div>
);
}
}
export default componentName;

React pass function as prop

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);
};

access state of react component from other component

I have the following spinner
import React, { Component } from 'react'
import './Spinner.scss'
export default class Spinner extends Component {
constructor(props) {
super(props);
this.state = {showLoading: true};
}
render () {
return (
<div className="spinner">
<div className="double-bounce1"></div>
<div className="double-bounce2"></div>
</div>
)
}
}
and from other component I would like to show or hide this spinner here is the code of the component:
import React, { Component } from 'react'
import RTable from '../../../components/RTable/RTable'
import Spinner from '../../../components/Spinner/Spinner'
import CsvDownload from '../containers/CsvDownloadContainer'
export default class Table extends Component {
_renderBreadcrumb () {
const { breadcrumb, handleBreadcrumbClick } = this.props
return (
<ol className="breadcrumb">
{(breadcrumb || []).map(el => {
return (
<li key={el.datasetKey}>
<a onClick={() => { handleBreadcrumbClick(el.granularity, el.datasetKey, el.datasetKeyHuman) }}>
{el.datasetKeyHuman}
</a>
</li>
)
})}
</ol>
)
}
render () {
const { datasetRows, columns, metadata, showLoading } = this.props
return (
<div className="row">
<div className="col-sm-12">
{this._renderBreadcrumb()}
<RTable rows={datasetRows} columns={columns} metadata={metadata} />
{ this.props.showLoading ? <Spinner /> : null }
<CsvDownload />
</div>
</div>
)
}
}
as you can see I trying to show or hide the spinner using:
{ this.props.showLoading ? <Spinner /> : null }
but I'm always getting undefinde. Some help please.
You have to move this
constructor(props) {
super(props);
this.state = {showLoading: true};
}
to your <Table /> component, otherwise you access showLoading from <Table />'s props, but it is not passed from anywhere.
Then change also
{ this.props.showLoading ? <Spinner /> : null }
to
{ this.state.showLoading ? <Spinner /> : null }
To show / hide <Spinner /> just call this.setState({ showLoading: Boolean }) in your <Table /> component.

Resources