localStorage after updated web site is reset - reactjs

In my react app I have 12 buttons and an array which has a length of 12 that is filled with the plus character: .fill("+");.
I have function that after clicking on one of these buttons that changes the Array(i) to ("-");
I need to save this Array in localStorage, but for this I create:
localStorge = localStorage.spots(spots=is my varryable in code) = JSON.stringify(Array(12).fill("-"));
I tap on my button and may local storage changed ==cool = everything works
but after reload my web my Array in local storage =
JSON.stringify(Array(12).fill("-"));
it is logic because I write it in beginning of the code, but I don't need it. I need to create local storage (Array(12).fill("-")) change it after clicking on my buttons and after reload local storage stayed changed.
app.js
export default class Hall extends React.Component {
constructor(props) {
super(props);
this.state = {
spots:JSON.parse(localStorage.spots),
}
}
handlClick(i) {
const spots =this.state.spots.slice();
spots[i]="бронь";
localStorage.spots=JSON.stringify(spots);
const color = this.state.color.slice();
color[i]="danger";
this.setState({
spots:JSON.parse(localStorage.spots),
color: color,
});
}
renderSquare(i) {
return (
<Spot
onClick={()=> this.handlClick(i)}
value={this.state.spots[i]}
/>
);
}
render() {
return (
<div className="BySpot">
<div className="date"></div>
<div className="inSpot">
<div className="screen">screen</div>
<div className="exit">exit</div>
<div className="board-row1">
board-row1
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row2">
board-row2
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row3">
board-row3
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
<div className="board-row4">
board-row4
{this.renderSquare(9)}
{this.renderSquare(10)}
{this.renderSquare(11)}
</div>
</div>
</div>
);
}
}
index.js
function Main(props) {
localStorage.spots = JSON.stringify(Array(12).fill("своб"));
return (
<main className="main" >
<Nav />
<WrapMain />
</main>
)
};
ReactDOM.render(
<Main />,document.getElementById("root")
);

That is a wrong syntax, you can't add value in localStorage that way. Try this:
localStorage.setItem('spots', JSON.stringify(spots))
and then you can get from localStorage
const spots = localStorage.getItem('spots')
and parse the spots variable if you need.
Here you can read more in docs.
https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage

Related

UseEffect not rerendring page once products loaded in redux store

I am working on e-commerce project (MERN).So i got Molla e-commerce React template for better UX/UI.
In my root function am getting all products and store them using redux like this :
const updateStore = () => {
store.dispatch( getAllProducts() );
}
Everything was working fine until i found out that if i try to access product page for the first time (with empty localstorage as in incognito mode) i get nothing and the product object was undefined , if i refresh the page then it works fine.
The problem is when i try to access the products page with empty redux store , it doesn't wait or rerender when the data are stored.
I tryed to use useEffect() to wait for product change to rerender but it's not working .
This is my product page code :
function SingleProduct( props ) {
let productLink= props.match.params.link;
const {product} = props;
const [productLoaded,setProductLoaded] = useState(false);
useEffect( () => {
if(productLoaded){
productGallery();
document.querySelector( '.skel-pro-single' ).classList.remove( 'loaded' );
let imgLoad = imagesLoaded( ".product-main-image", { background: true } );
imgLoad.on( 'done', function ( instance, image ) {
document.querySelector( '.skel-pro-single' ).classList.add( 'loaded' );
} );
}
}, [ productLink ] )
useEffect(()=>{
if(product){
setProductLoaded(true);
}
},[product])
return (
productLoaded ?
<>
<Helmet>
<title></title>
</Helmet>
<h1 className="d-none"></h1>
<div className="main">
<div className="page-content">
<div className="container">
<div className="product-details-top skeleton-body">
<div className="row skel-pro-single">
<div className="col-md-6">
<div className="skel-product-gallery">
</div>
<MediaOne link={ productLink } />
</div>
<div className="col-md-6">
<div className="entry-summary row">
<div className="col-md-12">
<div className="entry-summary1"></div>
</div>
<div className="col-md-12">
<div className="entry-summary2"></div>
</div>
</div>
<ProductDetailOne link={ productLink } />
</div>
</div>
</div>
<DescOne link={ productLink } />
<h2 className="title text-center mb-4">You May Also Like</h2>
<RelatedProducts />
</div>
</div>
<StickyBar link={ productLink } />
<QuickView />
</div>
</>
:
<></>
)
}
function mapStateToProps( state, props ) {
return {
product: state.data.products.filter( product => product.link == props.link )[ 0 ]
}
}
export default connect(mapStateToProps)(SingleProduct);
I tried to wait for product change using the useEffect and productLoaded state since it's undefined on first page render but it still showing undefined.
'product' variable, I think, is an object, be aware about what could happen if you use useEffect with that.
const a = {a:123};
const b = {a:123};
(a === b) === false
if it is possible see if the id or a string/number changes
The first useEffect will run before the second one so when this will run it will have productLoaded on false.
Try to gain productLoaded from props (you will not have to manage the asyncronicity of useEffect).
assuming that product is undefined before loading
const productLoaded = !!product;
useEffect( () => {
if(productLoaded){
..."do here what you need"
}
}, [ productLink, productLoaded ] )
maybe you can achieve a change in the css classes
<div className={`row skel-pro-single ${productLoaded?"loaded":""}`}>
as a suggestion try to use react as a templating with variables in the returning jsx, please don't mix js dom interaction and react interactions, they could disturb each other (react will try to report the part of the dom that he manages to the state he calculated, so could overwrite your changes made by
document.querySelector( '.skel-pro-single' ).classList.remove

how to render divs dynamically based on the number of items

I want to show slideshow circle buttons dynamically based on the number of slides I will get from the server. I couldn't do it with loops since I have a number of slides predefined & no array. How can I show my buttons? Now I manually created 4 buttons. (I deleted unnecessary code so that only important snippets are present).
import React = require("react");
type NavigationEvent = "Backward" | "Forward" | "Pause" | "Play";
interface Props {
slidesAmount: number;
activeSlideIndex: number;
}
function SlideshowCarouselNavigationBarComponent(props: Props) {
const onPlayButtonClicked = () => {
props.navigate("Play");
}
const onPauseButtonClicked = () => {
props.navigate("Pause");
}
const onSlideButtonClicked = index => {
props.navigateToIndex(index);
}
return (
<div>
<div>
<div className={classes.dotsContainer}>
<div className={classes.dots}>
</div>
<div className={classes.dots}>
</div>
<div className={classes.dots}>
</div>
<div className={classes.dots}>
</div>
</div>
</div>
</div>
)
}
export = SlideshowCarouselNavigationBarComponent;
You can do the following (it is the most concise and understable way that I found out) : Iterating slidesAmount times, in order to display the circles:
<div className={classes.dotsContainer}>
{[...Array(props.slidesAmmount)].map((_)=>(
<div className={classes.dots}>
</div>
))}
</div>
You can use Array.fill() This will create array of preidentified length filled with unidentified values
new Array(props.slidesAmmount).fill().map((v, i){
return <div> .... </div>
});
please see How to create an array containing 1...N for more detailed answers

component is unmounted and constructor is called when rendering it in a different place in the DOM

I am trying to implement a minimize/maximize feature in React. I have a div that serves as an information panel and by clicking on a button I would like it to toggle between maximized / minimized states. My top level app component has a boolean state field (maximizedInfo in the example below) that tracks whether the information panel is maximized or not and accordingly renders either just the panel or the full grid of my application with many other DOM elements. The below code is obviously a minified example but the main idea is that the render() method of my top-level component generates two very different DOM trees depending on the state. Unfortunately, I have discovered that my information panel component keeps getting unmounted and the constructor is called on every state change, thus losing the state the information panel component had accumulated.
What is the proper way to address that and implement this sort of functionality in React?
const React = require('react');
class InformationPanel extends React.Component {
constructor(props) {
console.log('InformationPanel:: constructor'); // this keeps getting called
super(props);
}
render() {
return (
<div>
<a id='information' class='nav-link' href="#" onClick={this.props.toggleInfoPanel}>toggle</a>
short info
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
maximizedInfo: false
};
this.toggleInfoPanel = this.toggleInfoPanel.bind(this);
}
toggleInfoPanel() {
this.setState({maximizedInfo: !this.state.maximizedInfo});
}
render() {
if (this.state.maximizedInfo)
return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-12 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
)
else return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div class='col-2 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
);
}
}
1st of all - keep structure/tree unmodified [and more maintainable]:
render() {
return (
<div class='container-fluid'>
<div class='row no-gutters'>
{this.state.maximizedInfo &&
<div class='col-10'>
some other info that takes up more space ...
</div>
}
<div key="infoPanel" class='col-2 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
);
}
Adding key prop helps in reconcilation - article
After this change <InformationPanel/> should not be rerendered. Notice that change is on parent - the place where child nodes differs. Parent not changed, props not changed, no rerendering.
2nd - above is not enough - we want size change!
I'd say that's a 'structural problem' - styling should be done inside <InformationPanel/> with required change passed as prop (f.e.):
<InformationPanel key="infoPanel" wide={!this.state.maximizedInfo} toggleInfoPanel={this.toggleInfoPanel}/>
// and in render in <InformationPanel/>
<div className={`padding-0 ${this.props.wide ? 'col-12' : 'col-2'}`}>
...
Still use key prop!
Other options for conditional styling in this thread
xadm's answer was correct that key is essential for tree reconciliation. The thing is, I discovered that the key needs to be present in the parent components, not necessarily in the InformationPanel component. The below code works:
if (this.state.maximizedInfo)
return (
<div key='a' class='container-fluid'>
<div key='b' class='row no-gutters'>
<div key='c' class='col-12 padding-0'>
{informationPanel}
</div>
</div>
</div>
)
else return (
<div key='a' class='container-fluid'>
<div key='b' class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div key='c' class='col-2 padding-0'>
{informationPanel}
</div>
</div>
</div>
);
Since you don't want to lose state in InformationPanel, you can declare it outside conditional rendering so that it won't be getting unmounted on state change. Code will look something like below:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
maximizedInfo: false
};
this.toggleInfoPanel = this.toggleInfoPanel.bind(this);
}
toggleInfoPanel() {
this.setState({maximizedInfo: !this.state.maximizedInfo});
}
render() {
const informationPanel = <InformationPanel toggleInfoPanel={this.toggleInfoPanel} />
if (this.state.maximizedInfo)
return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-12 padding-0'>
{informationPanel}
</div>
</div>
</div>
)
else return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div class='col-2 padding-0'>
{informationPanel}
</div>
</div>
</div>
);
}
}

Switch to different component after button click with React

I am absolutely new to React and need to create a button to take the user from a current component (MovieView) to the main one (MainView). I managed to create it properly and I used onClick to trigger the display of MainView. I know I cannot call a class from a function (so as console says), so I created a second function to be called and trigger MainView. But as you may wonder, it does not work. This is the code I am using:
import React from 'react';
import { MainView } from '../main-view/main-view';
function NewButton() {
return <MainView />;
}
export class MovieView extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
const { movie } = this.props;
if (!movie) return null;
return (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={NewButton}>Back</button>
</div>
</div>
);
}
}
Could you any seeing this to point the path to take or how I can call this MainView class properly. I understand it's a simple task, but I am afraid I still haven't absorbed React principles yet. Thanks in advance to you all.
Without introducing additional dependencies, probably the easiest way for this example is to use state to track if the button has been clicked. If it has been clicked, render MovieView, if not render MainView.
For this you need to following:
Set state variable that tracks that MainView should be rendered in onClick event. (boolean will probably suffice)
in render():
if the state var is false, render the content of MovieView.
if the state var is true, render the MainView component.
Implementation details are left as an exercise :)
If you're using a router already (like react-router) you could probably just update the url to an url that is linked to the MainView component..
I make the Class into a Function Component instead, and implement it like this :-)
import React from "react";
import { MainView } from '../main-view/main-view';
export const MovieView = ({ movie }) => {
const { showMainView, setShowMainView } = React.useState(false);
if (!!movie) {
return null;
}
function toggleMainView() {
setShowMainView(true);
}
return (
<React.Fragment>
{ showMainView ? (
<MainView />
) : (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={() => toggleMainView()}>Back</button>
</div>
</div>
)}
</React.Fragment>
);
};
export default MovieView;
If you need to add or remove components or show a new view based on an user action in react we need to do this via state .
import React from 'react';
import { MainView } from '../main-view/main-view';
export class MovieView extends React.Component {
constructor() {
super();
this.state = {
showMainView: false;
};
}
toggleMainView = () => this.setState(prevState => ({ showMainView:
!prevState.showMainView}))
render() {
const { movie } = this.props;
const { showMainView } = this.state;
if (!movie) return null;
return (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={this.toggleMainView}>Back</button>
</div>
// render the mainView based on the state
{ showMainView && <MainView />}
</div>
);
}
}
I reached a solution that I'd not do with the help I had here. I have first pasted here some pieces of code but, I believe it can be helpful to give the whole answer.
So, the question I placed here was regarding a component called <MovieView, that was a part of an app that interacted with another component (in another file) called MainView.
The solution was the insertion of the method onClick on the live 14 of <MovieView> and the button on line 39 that you can see at this file on Github. If the file was updated, check the version for November 22nd.
The solution had, however, been found in the main component, <MainView>, adding code on lines 48 and 49. Here is the link to the file: https://github.com/cgobbet/react-client/blob/master/src/components/main-view/main-view.jsx
The repository with the whole app is on this page: https://github.com/cgobbet/react-client.
And sorry for the reviewers (I had accidentally deleted part of the fantastic answer submitted by #nick-miller.

How to render my Modal window and all the information contained inside ( in React)?

My application renders twelve random people fetched from a different website. Everything works fine apart from my modal component(it should render more information about the person you clicked). For some reason whenever I try to render it I get this error 'Modal.js:9 Uncaught TypeError: Cannot read property 'medium' of undefined' and more errors comes with it. I am printing props.modalInfo from the Modal component to the console and it does have all the information I need, but for some reasons it shows that props.modalInfo is undefined when I try to render it. I have never done modal box in React (I am a beginner). Could someone explain me how I can render my Modal and pass all the data successfully? Thank you in advance!
handleClick(id) {
this.setState((prevState) => {
const modalInfoToPass = prevState.employeeList.filter(employee =>
{
if(`${employee.name.first} ${employee.name.last}` === id){
// get only and only one object that fulfils the
// condition
return employee;
}
})
return {
displayModal: true,
// update the modalInfo state
modalInfo: modalInfoToPass
}
})
}
render(){
return (
<div className='container'>
<Header />
<main>
{
this.state.loading ? <h2 className='load-page'>Loading...</h2> :
this.state.employeeList.map(employee =>
<Employee key={`${employee.name.title}
${employee.name.last}`}
employeeInfo={employee}
**handleClick={this.handleClick}**
/>)
}
</main>
<Footer />
**{this.state.displayModal && <Modal modalInfo={this.state.modalInfo} />}**
</div>
);
}
function Modal(props) {
**console.log(props.modalInfo);**
return (
<div className='bg-modal'>
<div className='modal-content'>
<div className='modal-image'>
<img src={props.modalInfo.picture.medium} alt={`${props.modalInfo.name.title} ${props.modalInfo.name.first}`}/>
</div>
<div className='modal-info'>
<p className='name'>{props.modalInfo.name.first} {props.modalInfo.name.last}</p>
<p className='email'>{props.modalInfo.email}</p>
<p className='place'>{props.modalInfo.location.city}</p>
</div>
<hr />
<div className='modal-more-info'>
<p className='number'>{props.modalInfo.cell}</p>
<p className='address'>{`${props.modalInfo.location.street}, ${props.modalInfo.location.state}`}</p>
<p className='postcode'>{props.modalInfo.location.postcode}</p>
<p className='birthday'>{props.modalInfo.dob.date}</p>
</div>
</div>
</div>
);
}
What is id and is it on an employee? If it isn't available, you could just pass what you're filtering for in your handleClick:
handleClick={()=>this.handleClick(`${employee.name.first} ${employee.name.last}`)}
Or, you could just pass the employee:
handleClick={()=>this.handleClick(employee)}
and modify your handler:
handleClick(employee) {
this.setState({modalInfo: employee, displayModal: true})
}

Resources