This is my first post so be gentle if i wasn't specific enough or made stupid mistake
So i am building a shop for my semester project in ReactJS(basic Reactjs: no redux or any database), and if it is important i just started to learn about JS and REACT.
I"m using AntDesign UI and controlling the routes through react-router-dom, this is my file tree just for the understanding.
I have statefull component that return a cart icon with badge and this component is called from the Header.
lets say i"m inside the landingpage component which located in the folder src->landingpage, and each press on a specific button there should be increment the badge by 1.
now the problem is that i can see the badge increment only after clicking and not in "real time" on the header, how do i make it work in real time through any component?
Cart(badge).js code->
import React, { Component } from "react";
import { Icon, Col, Badge, Button, Switch } from 'antd';
import CartCount from '../CartCount'
const ButtonGroup = Button.Group;
class Cart extends Component {
constructor() {
super();
this.state = {
count: 0,
show: true,
}
};
componentDidMount() {
var counter1 = JSON.parse(localStorage.getItem("Carts"))
if (counter1 != this.state.count) {
this.setState({ count: counter1 })
}
}
componentDidUpdate() {
var counter1 = JSON.parse(localStorage.getItem("Carts"))
if (counter1 != this.state.count) {
this.setState({ count: counter1 })
}
}
render() {
return (
<Col offset={23}>
<Badge count={this.state.count}>
<span style={{ fontSize: "50px" }} className="head-example"><Icon type="shopping-cart" /></span>
</Badge>
</Col>
)
}
}
export default Cart;
Header(contain badge inside).js code->
import React, { Component } from 'react';
import { Link } from "react-router-dom";
import { Layout } from 'antd';
import Login from '../login/login'
import {withRouter} from 'react-router'
import Cart from '../CartIndex/Cart/Cart'
import { Row, Col, Button, Tabs, Icon } from 'antd';
import './Header.css'
class Header extends Component {
state=({
count: 0,
reload: false
})
render() {
const { Header } = Layout;
return (
<div>
{/*Login section*/}
<Row>
<Col span={24}>
<Login />
</Col>
</Row>
{/*Header section*/}
<Row>
<Header style={{ fontFamily: 'Anton', fontSize: "50px" }}>
<Col span="23">
<a style={{ color: "white" }} href="/">Store</a>
</Col>
<Link to="/CartIndex"><Cart/></Link>
</Header>
</Row>
</div>
);
}
}
export default withRouter(Header)
the onClickHandler function im doing in the landingpage->
onCartHandler = () => {
if (localStorage.getItem("Carts")) {
var counter1 = JSON.parse(localStorage.getItem("Carts"))
counter1 += 1;
localStorage.setItem("Carts", counter1)
}
else {
var Counter = JSON.stringify(1)
localStorage.setItem("Carts", Counter)
}
message.success('Successfully added to cart')
}
I hope you guys can resolve and make me understand what i did wrong or didn't even knew from the first place, i researched about all the lifecycle components but it was to generic for me to understand without see code examples.
Thanks a-lot and have a great day in the office
You badge counter is bound to your component state in Cart(badge).js.
This is where it's interesting to understand component lifecycle, i know it can be hard to understand but here is an article about it : https://blog.bitsrc.io/understanding-react-v16-4-new-component-lifecycle-methods-fa7b224efd7d
What is happening is a click on the badge launchs the componentDidUpdate function from your component lifecycle, this is why it get updated from searching in your local storage. There is not direct data binding between your local storage and this.state.count from your component, this is all made with your component lifecycle.
Now to correct this there is multiple solutions, one way or another you need to update your component state, or use some other mechanic than state, for example a Redux Store ( yeah this is more to learn again ).
Related
I am trying to return two things, a separate Mission component and a flat list. When I run my emulator, I can get the return on the Mission component but not the flat list. There are no errors, so I'm a bit lost with how to approach this issue. I even double-checked my imports. Here is the code:
import React, { Component } from "react";
import { ScrollView, Text, FlatList } from 'react-native';
import Mission from './Mission';
import PARTNERS from '../shared/partners';
import { ListItem } from 'react-native-elements';
class About extends Component {
constructor(props) {
super(props);
this.state = {
partners: PARTNERS,
}
};
static navigationOptions = {
title: 'About Us'
}
render() {
const { navigate } = this.props.navigation;
const renderPartner = ({ item }) => {
return (
<ListItem
title={item.name}
subtitle={item.description}
leftAvatar={{ source: require('./images/bootstrap-logo.png') }}
/>
);
};
return (
<ScrollView>
<Mission />
<FlatList
data={this.state.partners}
renderItem={renderPartner}
keyExtractor={item => item.id.toString()}
/>
</ScrollView >
);
}
}
export default About;
import React, { Component } from 'react';
import { FlatList, View, Text } from 'react-native';
import { ListItem, Card } from 'react-native-elements';
import { FILMS } from '../shared/films';
/*
When working correctly, when you hit "Tap to play" in the simulated mobile device to the right, you will see a list of films from shared/films.js.
Before updating this code:
- You must be logged into your Expo account (create an account if you do not have one already)
- Use the Save button on the upper right corner to fork this Snack to your account. Name it as you please, or accept the default random name.
Share the link to your version in the forum for this code challenge.
Your challenges: 1. Fix the component by adding a constructor and adding the data imported as FILMS to the component's state
2. In the FlatList, use that state data for films in the 'data' attribute.
3. In the ListItem, add the film title as the title, and the director as the subtitle.
4. Update films.js to add a third film of your choice.
Bonus Challenge: Write a custom view in the ListItem subtitle to show more details about each film, such as the release year, genre, language.
*/
class FilmCatalogue extends Component {
constructor(props) {
super(props);
this.state = {
films: FILMS
}
}
render() {
const renderFilm = ({item}) => {
return (
<ListItem
title={item.title}
titleStyle={{fontWeight: 700, color: 'dark-grey'}}
subtitle={
<View >
<Text style={{fontStyle: "italic", fontWeight: 500}}>{item.director}</Text>
<Text>{item.category}</Text>
<Text>{item.language}</Text>
</View>
}
rightSubtitle={item.year}
bottomDivider
/>
);
};
return (
<Card title="Film Catalogue">
<FlatList
data={this.state.films}
renderItem={renderFilm}
keyExtractor={item=>item.id.toString()}
/>
</Card>
);
}
}
export default FilmCatalogue;
i used this on an expo snack to display a list of the files i had in this file (the Film Catalogue Component.js)
hope this kind of helps!
Im making my first react ptoject. Im new in JS, HTML, CSS and even web app programing.
What i try to do, is to display some infomration on button click.
I have an API, that looks like this:
endpoint: https://localhost:44344/api/Projects
My Data from it:
[{"id":1,"name":"Mini Jira","description":"Description for first project in list","tasks":null},{"id":2,"name":"Farm","description":"Description for second one","tasks":null}]
And im fine with that, i can get it easily by axios in my react app.
Now i will show you my Project.js Component:
import React, { Component } from "react";
import { ListGroupItem, Button, ButtonToolbar } from "react-bootstrap";
import ProjectDetails from "./ProjectDetails";
class Project extends Component {
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name}</ListGroupItem>
<Button onClick={Here i want to display new component with details }bsStyle="primary">Details</Button>
</ButtonToolbar>
);
}
}
export default Project;
I have all data from api in project type.
My question is, how to display component that i named ProjectDetails.js on button click? I want to show all data stored in project from my api in separate view (new page or somethig like that).
View looks like this:
Thanks for any advices!
EDIT:
based on #Axnyff answer, i edited Project.js. it works ok. But when i want to (for testing) displat project.name, i get error map of undefined. My ProjectDetails.js:
import React, { Component } from "react";
class ProjectDetails extends Component {
state = {};
render() {
return <li>{this.props.project.name}</li>;
}
}
export default ProjectDetails;
EDIT2:
In Project.js in #Axnyff answet i just edited that line:
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
i passed project by props, now it works like i want too. After click it displays project.name that i clicked on.
You should use state in your React component.
Let's create a field called showDetails in your state.
You can initialize it in your constructor with
constructor(props) {
super(props); // needed in javascript constructors
this.state = {
showDetails: false,
};
}
Then you need to modify the onClick to set that state to true
<Button onClick={() => this.setState({ showDetails : true })} bsStyle="primary">Details</Button>
And then use that state to show or not the ProjectDetails:
{ showDetails && <ProjectDetails /> }
The full component should look like
import React, { Component } from "react";
import { ListGroupItem, Button, ButtonToolbar } from "react-bootstrap";
import ProjectDetails from "./ProjectDetails";
class Project extends Component {
constructor(props) {
super(props); // needed in javascript constructors
this.state = {
showDetails: false,
};
}
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name}</ListGroupItem>
<Button onClick={() => this.setState({ showDetails : true })} bsStyle="primary">Details</Button>
{ this.state.showDetails && <ProjectDetails /> }
</ButtonToolbar>
);
}
}
export default Project;
You can then modify the logic to add a toggling effect etc.
If you haven't done it, you should probably follow the official tutorial
function Bar() {
return <h1>I will be shown on click!</h1>;
}
class Foo extends React.Component {
constructor() {
super();
this.state = { showComponent: false };
}
handleClick = () => {
this.setState({ showComponent: !this.state.showComponent });
};
render() {
return (
<div>
{this.state.showComponent && <Bar />}
<button onClick={this.handleClick}>click</button>
</div>
);
}
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<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>
<div id="root"></div>
I am having problem figuring out why my application is doing endless render.
Inside, My stateful component, I am calling a redux action in componentDidMount method (calling componentWillMount also do endless render)
class cryptoTicker extends PureComponent {
componentDidMount() {
this.props.fetchCoin()
// This fetches some 1600 crypto coins data,Redux action link for the same in end
}
render() {
return (
<ScrollView>
<Header />
<View>
<FlatList
data={this.state.searchCoin ? this.displaySearchCrypto : this.props.cryptoLoaded}
style={{ flex: 1 }}
extraData={[this.displaySearchCrypto, this.props.cryptoLoaded]}
keyExtractor={item => item.short}
initialNumToRender={50}
windowSize={21}
removeClippedSubviews={true}
renderItem={({ item, index }) => (
<CoinCard
key={item["short"]}
/>
)}
/>
</View>
</ScrollView>
)
}
}
In CoinCard I am literally doing nothing besides this (Notice CoinCard inside Flat list)
class CoinCard extends Component {
render () {
console.log("Inside rende here")
return (
<View> <Text> Text </Text> </View>
)
}
}
Now, When I console log in my coincard render, I can see infinite log of Inside rende here
[Question:] Can anyone please help me figure out why this could be happening?
You can click here to see my actions and click here to see my reducer.
[Update:] My repository is here if you want to clone and see it by yourself.
[Update: 2]: I have pushed the above shared code on github and it will still log endless console.log statements (if you can clone, run and move back to this commit ).
[Update:3]: I am no longer using <ScrollView /> in <FlatList /> also when I mean endless render, I mean is that it is endless (& Unecessarily) passing same props to child component (<Coincard />), if I use PureComponent, it won't log endlessly in render () { but in componentWillRecieveProps, If I do console.log(nextProps), I can see the same log passed over and over again
There are some points to note in your code.
The CoinCard Component must be a PureComponent, which will not re-render if the props are shallow-equal.
You should not render your Flatlist inside the ScrollView component, which would make the component render all components inside it at once which may cause more looping between the Flatlist and ScrollView.
You can also a definite height to the rendered component to reduce the number of times component is rendered for other props.
Another thing to note is, only props in the component are rendered on scroll bottom, based on the log statement mentioned below.
import {Dimensions} from 'react-native'
const {width, height} = Dimensions.get('window)
class CoinCard extends React.PureComponent {
render () {
console.log(this.props.item.long) //... Check the prop changes here, pass the item prop in parent Flatlist. This logs component prop changes which will show that same items are not being re-rendered but new items are being called.
return (
<View style={{height / 10, width}}> //... Render 10 items on the screen
<Text>
Text
</Text>
</View>
)
}
}
UPDATE
This extra logging is due to the props being from the Flatlist to your component without PureComponent shallow comparison.
Note that componentWillReceiveProps() is deprecated and you should avoid them in your code.
React.PureComponent works under the hood and uses shouldComponentUpdate to use shallow comparison between the current and updated props. Therefore log console.log(this.props.item.long) in your PureComponent' render will log the unique list which can be checked.
Like izb mentions, the root cause of the pb is the business call that is done on a pure component whereas it is just loaded. It is because your component make a business decision (<=>"I decide when something must be showed in myself"). It is not a good practice in React, even less when you use redux. The component must be as stupid a possible and not even decide what to do and when to do it.
As I see in your project, you don't deal correctly with component and container concept. You should not have any logic in your container, as it should simply be a wrapper of a stupid pure component. Like this:
import { connect, Dispatch } from "react-redux";
import { push, RouterAction, RouterState } from "react-router-redux";
import ApplicationBarComponent from "../components/ApplicationBar";
export function mapStateToProps({ routing }: { routing: RouterState }) {
return routing;
}
export function mapDispatchToProps(dispatch: Dispatch<RouterAction>) {
return {
navigate: (payload: string) => dispatch(push(payload)),
};
}
const tmp = connect(mapStateToProps, mapDispatchToProps);
export default tmp(ApplicationBarComponent);
and the matching component:
import AppBar from '#material-ui/core/AppBar';
import IconButton from '#material-ui/core/IconButton';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
import { StyleRules, Theme, withStyles, WithStyles } from '#material-ui/core/styles';
import Tab from '#material-ui/core/Tab';
import Tabs from '#material-ui/core/Tabs';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import AccountCircle from '#material-ui/icons/AccountCircle';
import MenuIcon from '#material-ui/icons/Menu';
import autobind from "autobind-decorator";
import * as React from "react";
import { push, RouterState } from "react-router-redux";
const styles = (theme: Theme): StyleRules => ({
flex: {
flex: 1
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
root: {
backgroundColor: theme.palette.background.paper,
flexGrow: 1
},
});
export interface IProps extends RouterState, WithStyles {
navigate: typeof push;
}
#autobind
class ApplicationBar extends React.PureComponent<IProps, { anchorEl: HTMLInputElement | undefined }> {
constructor(props: any) {
super(props);
this.state = { anchorEl: undefined };
}
public render() {
const auth = true;
const { classes } = this.props;
const menuOpened = !!this.state.anchorEl;
return (
<div className={classes.root}>
<AppBar position="fixed" color="primary">
<Toolbar>
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Typography variant="title" color="inherit" className={classes.flex}>
Title
</Typography>
<Tabs value={this.getPathName()} onChange={this.handleNavigate} >
{/* <Tabs value="/"> */}
<Tab label="Counter 1" value="/counter1" />
<Tab label="Counter 2" value="/counter2" />
<Tab label="Register" value="/register" />
<Tab label="Forecast" value="/forecast" />
</Tabs>
{auth && (
<div>
<IconButton
aria-owns={menuOpened ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit"
>
<AccountCircle />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={this.state.anchorEl}
anchorOrigin={{
horizontal: 'right',
vertical: 'top',
}}
transformOrigin={{
horizontal: 'right',
vertical: 'top',
}}
open={menuOpened}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>My account</MenuItem>
</Menu>
</div>
)}
</Toolbar>
</AppBar>
</div >
);
}
private getPathName(): string {
if (!this.props.location) {
return "/counter1";
}
return (this.props.location as { pathname: string }).pathname;
}
private handleNavigate(event: React.ChangeEvent<{}>, value: any) {
this.props.navigate(value as string);
}
private handleMenu(event: React.MouseEvent<HTMLInputElement>) {
this.setState({ anchorEl: event.currentTarget });
}
private handleClose() {
this.setState({ anchorEl: undefined });
}
}
export default withStyles(styles)(ApplicationBar);
Then you will tell me: "but where do I initiate the call that will fill my list?"
Well I see here that you use redux-thunk (I prefer redux observable... more complicated to learn but waaaaaaaaaaaaaaaaay more powerful), then this should be thunk that initiates the dispatch of this!
To summarize:
Components: the stupidest element that normally should have only the render method, and some other method handler to bubble up user events. This method only takes care of showing its properties to the user. Don't use the state unless you have a visual information that belongs only to this component (like the visibility of a popup for example). Anything that is showed or updated comes from above: a higher level component, or a container. It doesn't decide to update its own values. At best, it handles a user event on a subcomponent, then bubble up another event above, and... well maybe at some point, some new properties will be given back by its container!
Container: very stupid logic that consists in wrapping a top level component into redux for it to plug events to actions, and to plug some part of the store to properties
Redux thunk (or redux observable): it is the one that handles the whole user application logic. This guy is the only one who knows what to trigger and when. If a part of your front end must contain the complexity, it's this one!
Reducers: define how to organize the data in the store for it to be as easily usable as possible.
The store: ideally one per top level container, the only one that contains the data that must be showed to the user. Nobody else should.
If you follow these principles, you should never face any issue like "why the hell this is called twice? and... who made it? and why at this moment?"
Something else: if you use redux, use an immutability framework. Otherwise you may face issues as reducers must be pure functions. For this you can use a popular one immutable.js but not convenient at all. And the late ousider that is actually a killer: immer (made by the author or mobx).
It seems Jacob in the above comment has managed to make the component render only twice.
This will definitely cause double initial render (and would cause an infinite render if it wasn't a PureComponent):
componentDidUpdate() {
var updateCoinData;
if (!updateCoinData) { // <- this is always true
updateCoinData = [...this.props.cryptoLoaded];
this.setState({updateCoinData: true}); // <- this will trigger a re render since `this.state.updateCoinData` is not initially true
}
...
}
Link to the issue in your repository
I cannot believe that I cannot update state by setState.
I want to update cardModalOpen state to close the Modal.
I add bind(this) but it still does not work.
(Modal is opened by click Card Component)
However, I did setState({cardModalOpen: false}) by closeModal() function but it is still true even after render method was called.
Can someone please explain what I am doing wrong.
This is my code.
index.js
import React, { Component }from 'react';
import { Button, Card, Image, Header, Modal, Form, Input } from 'semantic-ui-react'
class App extends React.Component {
state = { cardModalOpen:false }
showCardModal() {
this.setState({cardModalOpen:true})
}
closeModal(){
this.setState({cardModalOpen:false})
}
render() {
const messagesDataNew = [];
for (var i = 0; i < 3; i++) {
messagesDataNew.push(
<Card
onClick={() => {
this.showCardModal();
}}
>
<DetailModal
cardModalOpen={this.state.cardModalOpen}
closeModal={this.closeModal}
/>
</Card>
);
}
return <div>{messagesDataNew}</div>;
}
}
DetailModal.js
import React, { Component }from 'react';
import { Button, Card, Image, Header, Modal, Form, Input } from 'semantic-ui-react'
class DetailModal extends Component{
render(){
return(
<Modal open={this.props.cardModalOpen} onClose={()=>{this.props.closeModal()}} >
<Modal.Header>Select a Photo</Modal.Header>
<Modal.Content image>
<Image wrapped size='medium' src='https://react.semantic-ui.com/images/avatar/large/rachel.png' />
<Modal.Description>
<Header>Default Profile Image</Header>
<p>We've found the following gravatar image associated with your e-mail address.</p>
<p>Is it okay to use this photo?</p>
</Modal.Description>
</Modal.Content>
<Button onClick={()=>{this.props.closeModal()}}>Close</Button>
</Modal>
)
}
}
export default DetailModal;
Here is a codesandbox with issue reproduced https://codesandbox.io/s/jjk7nw647y
In codesandbox you shared there is no clickable trigger-element for any of the modals. That's because of you are rendering an empty content of Card.
Here is my changes to your example https://codesandbox.io/s/l97n95n2om
The only difference is line #20 - I added a text Some text so your Card component has valid visible (and clickable) DOM element.
Do not forget bind your state handler functions:
constructor(props){
super(props)
this.showCardModal=this.showCardModal.bind(this)
this.closeModal =this.closeModal.bind(this)
}
I am attempting to show some info based on what item was pressed.
To do so I am modifying the state's selectedSchedule value to different values according to which button was pressed.
The info(for now, the selectedSchedule value itself) displayed is always the one that should've been before. When you press 'Segmented' the displayed text is '', the value selectedSchedule was initialized with. Then, when you go back, and press, 'Uberman' 'Segmented appears'.
I don't know if this is an issue with the component lifecycle or if javascript/React handle functions asynchronously; the function written after setState() seems to get called first.
//SleepSchedules.js
import React, { Component} from 'react';
import { Container, Content, Card, CardItem, Text, Icon, Button } from 'native-base';
import ScheduleItem from './ScheduleItem';
export default class SleepSchedules extends Component {
constructor(props) {
super(props);
this.state = {selectedSchedule: ''};
}
_handlePress(schedule){
this.setState({
selectedSchedule: schedule
});
this._navScheduleItem()
}
_navScheduleItem(){
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: {scheduleName: this.state.selectedSchedule}
})
}
render() {
return (
<Container style={{paddingTop:64}}>
<Content>
<Card>
<CardItem button
onPress={()=> this._handlePress('Segmented')} >
<Text>Segmented</Text>
</CardItem>
<CardItem button
onPress={()=> this._handlePress('Everyman')}>
<Text>Everyman</Text>
</CardItem>
<CardItem button
onPress={()=> this._handlePress('Uberman')}>
<Text>Uberman</Text>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
Here is the component it's passing it's props to:
import React, { Component} from 'react';
import { Container, Content, Card, CardItem, Text, Icon, Button } from 'native-base';
import ComingSoon from './ComingSoon';
export default class ScheduleItem extends Component {
constructor(props){
super(props);
}
render(){
return(
{this.props.scheduleName}
);
}
}
Simple, setState is async in this case (it is sync, in some special cases, lol, right?). That means, never rely on setState being sync...
There are 2 possible solutions.
First: Don't wait until state is changed. Pass received value manually, immediately.
_handlePress(selectedSchedule) {
this.setState({ selectedSchedule });
this._navScheduleItem(selectedSchedule)
}
_navScheduleItem(scheduleName) {
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: { scheduleName },
})
}
Second: Wait until state change propagate. Read updated state value.
_handlePress(selectedSchedule){
this.setState({ selectedSchedule }, () => {
this._navScheduleItem()
});
}
_navScheduleItem(){
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: { scheduleName: this.state.selectedSchedule }
})
}