React function will not rerender even if state hook called - reactjs

In this function:
import React, { useEffect } from 'react';
import { BrowserRouter, Route, Link } from 'react-router-dom'
import HomeScreen from './Screens/HomeScreen'
import './App.css';
import currentStrings from './utils/currentlang'
function App() {
const [currentlang, setCurrentStrings] = React.useState(currentStrings)
return (
<BrowserRouter>
<div className="grid-container">
<header className="header">
<div className="brand">
<Link to="/" >
</Link>
</div>
<div className="header-side">
{currentlang.getCurrent.subtitle}
</div>
<div className="header-right">
<button onClick={() => setCurrentStrings(currentlang.switchLang())}> {currentlang.getCurrent.traduction} </button>
</div>
<div>
</div>
</header>
<main className="main">
<div className="content">
<Route path="/" exact={true} component={HomeScreen} />
</div>
</main>
<footer className="footer">
© 2020
</footer>
</div>
</BrowserRouter>
);
}
export default App;
I use a button (<button onClick={() => setCurrentStrings(currentlang.switchLang())}> ) to change one of the properties of the object currentStrings (or currentLang used for the useState hook)
However it seems my function does not rerender, i can confirm this since my logs do tell the object does change its properties when the button is clicked, but the screen does not show it.
import {strings as frstrings} from '../res/lang/fr/strings'
import {strings as engstrings} from '../res/lang/eng/strings'
class CurentLang {
constructor(){
this.current = engstrings;
}
switchLang() {
if(this.current === frstrings){
this.current = engstrings;
console.log("engstrings");
} else{
this.current = frstrings;
console.log("frstrings");
}
return new this.CurentLang;
}
get getCurrent(){
return this.current;
}
}
var currentStrings = new CurentLang()
export default currentStrings;

Related

Getting Invalid Hook Error in React App after trying to wrap app in provider

I am building a mock e-commerce store using react and after laying the groundwork for the website, I installed the package react-use-cart (https://www.npmjs.com/package/react-use-cart) to handle the items being added to the cart and to be used in a checkout page. I wrapped my app in the Cart Provider that was given by the react-use-cart package and set up the cart functions on my product pages but now my app does not load anything on startup and gives me an invalid hook call error. I have tried wrapping the app in the index.js file with the provider as well and that gives the same error.
import React, { useState } from "react";
import './css/App.css';
import {
BrowserRouter,
Routes,
Route,
} from "react-router-dom";
import NavBar from "./components/NavBar"
import ProductsScreen from "./screens/ProductsScreen"
import ProductDetailScreen from "./screens/ProductDetailScreen"
import HomeScreen from "./screens/HomeScreen"
import LoginScreen from "./screens/LoginScreen"
import RegisterScreen from "./screens/RegisterScreen";
import CartScreen from "./screens/CartScreen"
import { CartProvider } from "react-use-cart";
const App = () => {
return (
<>
<BrowserRouter>
<CartProvider>
<NavBar />
<Routes>
<Route path='/' element={<HomeScreen/>}/>
<Route path='/products' element={<ProductsScreen/>}/>
<Route path='/product/:id' element={<ProductDetailScreen/>}/>
<Route path='/login' element={<LoginScreen/>}/>
<Route path='/register' element={<RegisterScreen/>}/>
<Route path='/cart' element={<CartScreen/>}/>
</Routes>
</CartProvider>
</BrowserRouter>
</>
)
}
export default App;
import { useState, useEffect }from 'react'
import { Link, useParams } from 'react-router-dom'
import axios from 'axios'
import "../css/ProductDetailScreen.css"
import { useCart } from "react-use-cart";
// import Slideshow from '../components/SlideShow'
const { addItem, updateItemQuantity } = useCart();
const ProductDetailScreen = ( {match} ) => {
const [product, setProduct] = useState({})
const { id } = useParams()
useEffect(() => {
const getProduct = async () => {
const { data } = await axios.get(`/api/products/${id}`)
setProduct(data)
}
getProduct()
}, [])
return (
<>
<Link to ='/products'>Go Back</Link>
<div className="wrapper">
<div className="detail-grid">
<div className='imgContainer'>
<img className='cover_img1' src={product.cover_img} alt='Videogame Cover Art'/>
<div className='carousel'>
{product.images?.map((image, index) => (
<img key={index} src={image} alt="Videogame Gameplay"></img>
))}
</div>
{/*Replace carousel div with actual image slideshow*/}
</div>
<div>
<div className='textContainer'>
<h1 className='title1'>{product.title}</h1>
<h3 className='descriptionHeader'>Description:</h3>
<p className='description1'>{product.description}</p>
<h3 className='developer1'>Developer: {product.developer}</h3>
<h3 className='publisher1'>Publisher: {product.publisher}</h3>
<h3 className='releaseDate1'>Release Date: {product.releaseDate}</h3>
<h3 className='genre1'>Genre: {product.genre}</h3>
</div>
<div className='checkOutContainer'>
<h3 className='price1'>Price: ${product.price}</h3>
<button onClick={() => updateItemQuantity(product.id, product.quantity - 1)}> - </button>
<span>{product.quantity}</span>
<button onClick={() => updateItemQuantity(product.id, product.quantity + 1)}> + </button>
<button onClick={() => addItem(product)} className='addCart' type='button'>Add to Cart</button>
</div>
</div>
</div>
</div>
</>
)
}
export default ProductDetailScreen

i want to build sidebar on my own but the function don't work

Sorry for my bad English speak please help me to build a responsive sidebar menu. when i try to build a sidebar menu responsive I reach this type of error : TypeError: document.getElementByClassName is not a function. I haven't an idea where's the error exactly so this is my code of the sidebar menu:
import React from 'react';
import {useEffect} from 'react';
import SideNav, { Toggle, Nav, NavItem, NavIcon, NavText } from '#trendmicro/react-sidenav';
import '#trendmicro/react-sidenav/dist/react-sidenav.css';
import {listProducts} from '../actions/productActions';
import {useSelector, useDispatch} from 'react-redux';
function w3_open() {
document.getElementByClassName("sidebar").style.width = "100%";
document.getElementByClassName("sidebar").style.display = "block";
}
function w3_close() {
document.getElementByClassName("sidebar").style.display = "none";
}
export default function SideBarMenu() {
const dispatch = useDispatch();
useEffect(()=> {
dispatch(listProducts);
}, [])
const productList = useSelector((state) => state.productList);
const {products} = productList;
console.log(products);
return (
<div className="wrapper" >
<div className="section">
<div className="top_navbar">
<div className="hamburger">
<a href="#">
<i className="fa fa-bars" onClick={w3_open(), w3_close()} ><span className="bar-icon"> All products </span></i>
</a>
</div>
</div>
</div>
<div className="sidebar">
profile image & text
menu item
</div>
</div>
)
}
and this is the app.js file :
import React from 'react';
import {BrowserRouter, Routes, Route} from 'react-router-dom';
import HomePage from './Pages/HomePage';
import ProductPage from './Pages/productPage';
import SideBarMenu from './components/SideBarMenu';
function App() {
return (
<BrowserRouter>
<div className="grid-container" >
<header className="row" >
<div>
<a className="brand" href="/">My shop</a>
</div>
<div>
Cart
Sign In
</div>
</header>
<main>
<SideBarMenu ></SideBarMenu>
<Routes>
<Route path='/product/:id' element={<ProductPage /> } />
<Route path='/' element={<HomePage />} exact />
</Routes>
</main>
<footer className="row center" >All right reserved</footer>
</div>
</BrowserRouter>
);
}
export default App;
document.getElementByClassName(...) doesn't exist in JavaScript. You should use document.getElementsByClassName(...) (notice the plural in Elements).
Replace document.getElementByClassName("sidebar") with document.getElementsByClassName("sidebar")[0].
Update
To fix the error that you have right now, I would suggest to use a variable and useState Hook to display or hide the sidebar.
Example:
const [open, setOpen] = useState(false)
const handleSidebar = () => {
setOpen(!open)
}
return(
<div className = "wrapper" >
<div className = "section">
<div className = "top_navbar">
<div className = "hamburger">
<a href = "#">
<i className="fa fa-bars" onClick = {handleSidebar}><span className = "bar-icon"> All products </span></i>
</a>
</div>
</div>
</div>
{ open
? <div className = "sidebar">
profile image & text menu item
</div>
: null
}
</div>
)

Resutl showing book is not defined in console

I used to show my New arrival page from Home page when i select books. I Planned to show only a book that is selected in Home page book list. But its now showing in New arrival page right now. Please help me to get rid off.
import React from "react";
const BookDetails = (props) => {
console.log(props.book);
if (props.book === null) return <div></div>;
return (
<div>
<h3>{props.book.bookName}</h3>
</div>
);
};
export default BookDetails;
(This is another code i used my onclick fucntion)
import React from "react";
import Book from "../Representational/Book";
import { withRouter } from "react-router-dom";
const ComponentList = (props) => {
return props.books.map((item, index) => {
return (
<Book
bookName={item.bookName}
writer={item.writer}
publish={item.publish}
key={item.id}
selectedBookHandler={()=>props.selectedBookHandler(Book)}
/>
);
});
};
export default withRouter(ComponentList);
(Below are codes as main component)
import { Component } from "react";
import ComponentList from "./Lists/ComponentList";
import BookList from "../Assets/BookList";
import NewBook from "./Representational/NewBook";
import About from './Representational/About';
import {Route, NavLink} from 'react-router-dom';
import BookDetails from "./Representational/BookDetails";
class MainComponent extends Component {
constructor(props){
super(props)
this.state = {
books: BookList,
selectedBook: null
}
}
selectedBookHandler = books=>{
this.setState({
selectedBook: books
})
}
render() {
const newCopy = (
<ComponentList
books={this.state.books}
selectedBookHandler={this.selectedBookHandler}
/>
);
return (
<div className="book-project">
<nav className="nav-bar">
<ul>
<li>
<NavLink to="/" exact>Home</NavLink></li>
<li><NavLink to="/new">New Arrival</NavLink></li>
<li><NavLink to="/about-us">About Us</NavLink></li>
</ul>
</nav>
<div>
<Route path="/" exact render={()=> newCopy} />
<Route path="/new" exact component={NewBook}/>
<Route path="/about-us" exact component= {About}/>
<BookDetails book={this.state.selectedBook}/>
</div>
<div className="head">
<h1 className="appHeading head">New Book List</h1>
</div>
<div className="btn">
<button onClick={this.toggleBook}>Toggle</button>
<input type="text" onChange={this.changeInputState} />
</div>
</div>
);
}
}
export default MainComponent;
Please Help me.

how to re-render all related components with i18n strings once language is changed

this main App function has a custom hook that will trigger when the button is cliked:
import React, { useEffect } from 'react';
import { BrowserRouter, Route, Link } from 'react-router-dom'
import HomeScreen from './Screens/HomeScreen'
import './App.css';
import { useCurrentLang } from './utils/useCurrentLang'
import {strings as engstrings} from './res/lang/eng/strings'
function App() {
const currentStrings = useCurrentLang(engstrings);
return (
<BrowserRouter>
<div className="grid-container">
<header className="header">
<div className="brand">
<Link to="/" >
</Link>
</div>
<div className="header-side">
{currentStrings.currentlang.subtitle}
</div>
<div className="header-right">
<button {...currentStrings}>
{currentStrings.currentlang.traduction}
</button>
</div>
<div>
</div>
</header>
<main className="main">
<div className="content">
<Route path="/" exact={true} component={HomeScreen} />
</div>
</main>
<footer className="footer">
© 2020
</footer>
</div>
</BrowserRouter>
);
}
export default App;
However this component function that is routed from App also needs to use the same reference of the hook object found in App:
import React from 'react';
import terminalImage from '../res/images/GNOMETerminalIcon.png';
import {useCurrentLang} from '../utils/useCurrentLang'
import {strings as engstrings} from '../res/lang/eng/strings'
const { Link } = require("react-router-dom");
function HomeScreen() {
const currentStrings = useCurrentLang(engstrings);
return <div className="home">
<ul className="menu-list">
<li>
<div className="about-link section">
<Link to="/about">{currentStrings.currentlang.about}</Link>
</div>
</li>
<li>
<div className="projects-link section">
<Link to="/about">{currentStrings.currentlang.about}</Link>
</div>
</li>
<li>
<div className="contacts-link section">
<Link to="/about">{currentStrings.currentlang.about}</Link>
</div>
</li>
<li>
<div className="suggestions-link section">
<Link to="/about">{currentStrings.currentlang.about}</Link>
</div>
</li>
</ul>
<div className="home-main-image">
<img src={terminalImage} />
</div>
</div>
}
export default HomeScreen;
Is it possible for both function to rerender when the hook on App is triggered? if yes, how?
Edit: currentLang hook:
import {useState} from 'react'
import {strings as frstrings} from '../res/lang/fr/strings'
import {strings as engstrings} from '../res/lang/eng/strings'
export const useCurrentLang = initialState => {
if(initialState === 0){
initialState = engstrings
}
const [currentlang, setLang] = useState(initialState);
return {
currentlang: currentlang,
onClick: () => {
if(currentlang === engstrings){
setLang(frstrings)
} else {
setLang(engstrings)
}
}
}
}
Problem
The main problem you're facing is one part of your React Tree has no idea that the language has changed.
Solution
Use a Language Context which provides the updates to all your React tree and wrap it on the top of the application. In layman terms, now your app is on listening mode whenever lang changes. So basically, what React does now is whenever lang changes, it will find wherever lang is used from the context and update the component.
Docs on React context here
import React from 'react'
// import {strings as frstrings} from '../res/lang/fr/strings'
// import {strings as engstrings} from '../res/lang/eng/strings'
const lang = {
// in this way, you could dynamically add lang
// later on which worrying about if-elses in your component
en: {
hello: 'hello'
},
fr: {
hello: 'bonjour',
},
}
const langDict = (key) => lang[key]
const LanguageContext = React.createContext(null);
function LanguageProvider({ initialState = 'en', children }) {
const [lang, setLang] = React.useState(initialState);
return (
<LanguageContext.Provider value={[langDict(lang), setLang]}>
{children}
</LanguageContext.Provider>
)
}
function useLanguage() {
return React.useContext(LanguageContext);
}
export default function AppWrapper() {
return (
<LanguageProvider>
<App />
</LanguageProvider>
)
}
function App() {
const [lang, setLang] = useLanguage();
return (
<div>
<h1>{lang.hello}</h1>
<button onClick={() => setLang('fr')}>French</button>
<button onClick={() => setLang('en')}>English</button>
</div>
)
}

React Context: TypeError: Cannot read property 'areResultsVisible' of undefined

Can someone please help me? I'm trying to pass data through context from one component to another (from Search.js to Container.js). However, I'm getting a type error. I searched many questions in Web, but haven't found an answer. Sorry, if it's childish problem, I'm new.
Search.js:
import React, { Component } from 'react'
import { StyledFormSearchBar } from '../styles'
import { data } from '../data'
const SearchContext = React.createContext()
export default class Search extends Component {
constructor() {
super()
this.state = {
value: '',
results: [],
areResultsVisible: false
}
this.handleSearch = this.handleSearch.bind(this)
}
handleSearch(e) {
this.setState = {
value: e.target.value,
results: data.filter(item => {
return item.title.toLowerCase().includes(e.target.value.toLowerCase())
}),
areResultsVisible: true
}
}
render() {
return (
<StyledFormSearchBar>
<input type="search" name="search" className="border border-dark rounded" onSubmit={this.handleSearch}/>
<button type="submit" value="submit" className="bg-warning border-0 text-danger rounded-right position-relative">
<i className="fas fa-search"></i>
</button>
<SearchContext.Provider value = {{
...this.state,
handleSearch: this.handleSearch
}}>
{this.props.children}
</SearchContext.Provider>
</StyledFormSearchBar>
)
}
}
const SearchContextConsumer = SearchContext.Consumer
export { SearchContextConsumer }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Container.js:
import React from 'react'
import { Route, Switch } from "react-router-dom"
import { StyledDivGridContainer } from './styles'
import { SearchContextConsumer } from './header/Search'
import Carousel from './container/Carousel'
import MobilePhonesDiscount from './container/products/carousel/MobilePhonesDiscount'
import LaptopsDiscount from './container/products/carousel/LaptopsDiscount'
import TabletsDiscount from './container/products/carousel/TabletsDiscount'
import Products from './container/Products'
import SearchResults from './container/SearchResults'
import MobilePhones from './container/products/MobilePhones'
import Laptops from './container/products/Laptops'
import Tablets from './container/products/Tablets'
import ProductPage from './container/ProductPage'
import About from './container/About'
import ContactUs from './container/ContactUs'
export default function Container() {
return (
<StyledDivGridContainer>
<div className="no-gutters justify-content-between">
<SearchContextConsumer>
{
value => {
return (
!value.areResultsVisible
? <Switch>
<Route exact path="/" component={Carousel}/>
<Route exact path="/" component={Products}/>
</Switch>
: <Route exact path="/" component={SearchResults}/>
)
}
}
</SearchContextConsumer>
<Route path="/mobile_phones_discount" component={MobilePhonesDiscount}/>
<Route path="/laptops_discount" component={LaptopsDiscount}/>
<Route path="/tablets_discount" component={TabletsDiscount}/>
<Route path="/mobile_phones" component={MobilePhones}/>
<Route path="/laptops" component={Laptops}/>
<Route path="/tablets" component={Tablets}/>
<Route path="/product_page" component={ProductPage}/>
<Route path="/about" component={About}/>
<Route path="/contact_us" component={ContactUs}/>
</div>
</StyledDivGridContainer>
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Search.js is in Header.js:
import React, { Component } from 'react'
import { StyledHeader,
StyledSpanAccount } from './styles'
import { Link } from "react-router-dom"
import Catalogue from "./header/Catalogue"
import Search from "./header/Search"
export default class Header extends Component {
render() {
return (
<StyledHeader className="d-flex w-100 bg-light shadow justify-content-center">
<div className="d-flex flex-wrap justify-content-around align-items-center">
<div className="my-1 mr-3">
<Link to="/" className="logo">
<img src={require('../img/logo.webp')} alt="logo" className="img-tumbnail"/>
</Link>
</div>
<Catalogue/>
<Search/>
<div className="d-flex my-3">
<i className="fas fa-shopping-cart"></i>
<i className="fas fa-user-alt ml-3"></i>
<a href="#" className="d-flex flex-nowrap">
<StyledSpanAccount className="ml-2">Log in</StyledSpanAccount>
</a>
<a href="#" className="d-flex flex-nowrap">
<StyledSpanAccount className="ml-2">Sing up</StyledSpanAccount>
</a>
</div>
</div>
</StyledHeader>
)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Container.js is in App.js near Header.js:
import React from 'react';
import { Route } from "react-router-dom";
import { StyledDivWrapper } from './components/styles';
import Header from './components/Header';
import Container from './components/Container';
import Footer from './components/Footer';
export default function App() {
return (
<StyledDivWrapper className="d-flex flex-column">
<Route path="/" component={Header}/>
<Route path="/" component={Container}/>
<Route path="/" component={Footer}/>
</StyledDivWrapper>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Error screenshot
My folder tree
The problem is that your Container component is not within your SearchContext.Provider. To make sure your code works correctly you must have your SearchContext Provider directly inside you App Component so that it is a common parent of both Search and Container component
Firstly, create a context in a different file
// SearchContext.js
export const SearchContext = React.createContext();
const SearchContextConsumer = SearchContext.Consumer
export { SearchContextConsumer }
Secondly, create a component which acts as a provider
// SearchProvider.js
class SearchProvider extends React.Component {
constructor() {
super()
this.state = {
value: '',
results: [],
areResultsVisible: false
}
this.handleSearch = this.handleSearch.bind(this)
}
handleSearch(e) {
this.setState = {
value: e.target.value,
results: data.filter(item => {
return item.title.toLowerCase().includes(e.target.value.toLowerCase())
}),
areResultsVisible: true
}
}
render() {
return (
<SearchContext.Provider value = {{
...this.state,
handleSearch: this.handleSearch
}}>
{this.props.children}
</SearchContext.Provider>
)
}
}
Now you Search Component would simply be
// Search.js
export default class Search extends Component {
render() {
return (
<SearchContext.Consumer>
{({handleSearch}) => (
<StyledFormSearchBar>
<input type="search" name="search" className="border border-dark rounded" onSubmit={handleSearch}/>
<button type="submit" value="submit" className="bg-warning border-0 text-danger rounded-right position-relative">
<i className="fas fa-search"></i>
</button>
</StyledFormSearchBar>
)}
</SearchContext.Consumer>
)
}
}
Also no you can use your SearchProvider in App.js like
export default function App() {
return (
<SearchProvider>
<StyledDivWrapper className="d-flex flex-column">
<Route path="/" component={Header}/>
<Route path="/" component={Container}/>
<Route path="/" component={Footer}/>
</StyledDivWrapper>
</SearchProvider>
);
}
You first need to wrap your top level component with the context provider (Search), because the point of the context api is to pass the data top-down without having to explicitly pass it through every level, otherwise the context consumer won't have any context to consume in first place:
<Search>
<App />
</Search>
Also, you need to use the prop value to pass the context from the provider, and not any prop (like searchValue in your code):
<SearchContext.Provider value = {{
...this.state,
handleSearch: this.handleSearch
}}>
{this.props.children}
</SearchContext.Provider>
Import createContext like
import { React, createContext } from 'react';
export const MyContext = createContext();
this helped me.

Resources