React update props from API - javascript

I'm having some trouble with updating information from my API with React.
For example, I have a js file that takes information about a client, after I change information about a client, for example balance, even if I click to another page and return information is not updated, only if I click F5 it will update info.
This is a file
class Portfolio extends Component {
render() {
const { authUser } = this.props;
return (
<Widget>
<h2 className="h4 gx-mb-3">Your Portfolio Balance</h2>
<Row>
<Col lg={12} md={12} sm={12} xs={24}>
<div className="ant-row-flex">
<h2 className="gx-mr-2 gx-mb-0 gx-fs-xxxl gx-font-weight-medium">{authUser ? authUser.balance : "Loading"}€</h2>
</div>
<p className="gx-text-grey"></p>
<div className="ant-row-flex gx-mb-3 gx-mb-md-2">
<Button className="gx-mr-2" type="primary">Deposit</Button>
<Button className="gx-btn-red" disabled>Withdraw</Button>
</div>
<p><Link className="gx-text-primary" to="/te">- Messages <Badge count={23} style={{ backgroundColor: '#52c41a' }} /></Link></p>
<p><Link className="gx-text-primary" to="/te">- Notifications <Badge count={320} style={{ backgroundColor: '#52c41a' }} /></Link></p>
<p><Link className="gx-text-primary" to="/te">- Active Servers <Badge count={5} style={{ backgroundColor: '#52c41a' }} /></Link></p>
<p><Link className="gx-text-primary" to="/te">- Billing </Link></p>
<p><Link className="gx-text-primary" to="/te">- Logout </Link></p>
</Col>
<Col lg={12} md={12} sm={12} xs={24}>
<div className="gx-site-dash">
<h4 className="gx-mb-3">Welcome back {authUser ? authUser.username : "Loading"}</h4>
<img alt="" width="160" src={authUser ? authUser.avatar : 'Loading'} />
</div>
</Col>
</Row>
</Widget>
)
}
}
const mapStateToProps = ({ auth }) => {
const { authUser } = auth;
return { authUser }
};
export default connect(mapStateToProps, { userSignOut })(Portfolio);
Do I need to use WebSockets to make it live and don't need to refresh the page or there is easier way?
Edit:
Reducer
const INIT_STATE = {
token: JSON.parse(localStorage.getItem('token')),
initURL: '',
authUser: JSON.parse(localStorage.getItem('user')),
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case INIT_URL: {
return {...state, initURL: action.payload};
}
case USER_DATA: {
return {
...state,
authUser: action.payload,
};
}
Action:
export const getUser = () => {
return (dispatch) => {
dispatch({ type: FETCH_START });
axios.get('http://127.0.0.1:8000/api/user/details/profile',
).then(({ data }) => {
if (data.username) {
dispatch({ type: FETCH_SUCCESS });
dispatch({ type: USER_DATA, payload: data });
} else {
dispatch({ type: FETCH_ERROR, payload: data.error });
}
}).catch(function (error) {
dispatch({ type: FETCH_ERROR, payload: error.message });
console.log("Error****:", error.message);
});
}
};

If my guess is correct, your component need to be:
class Portfolio extends PureComponent
One of the main difference between Component and PureComponent is the update method for props.
PureComponent update when props change, Component don't update.
PureComponent handle shouldComponentUpdate() for you.

Related

cant access state, react redux

I have a page with notes for a specific user, the problem is when i'm trying to access the state of 'my notes' i get 'undefined' on console, in postman, everything works when the user login, and I can see the notes.
notes reducer
import {
NOTES_LIST_REQUEST,
NOTES_LIST_SUCCESS,
NOTES_LIST_FAIL,
} from '../types/noteTypes';
export const noteListReducer = (state = { notes: [] }, action) => {
switch (action.type) {
// whenever call the api its going to:
// request the notes
case NOTES_LIST_REQUEST:
return { loading: true };
// if true..
case NOTES_LIST_SUCCESS:
return { loading: false, notes: action.payload };
//if fail..
case NOTES_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
notes action
import {
NOTES_LIST_REQUEST,
NOTES_LIST_SUCCESS,
NOTES_LIST_FAIL
} from '../types/noteTypes';
import axios from 'axios';
export const listNotes = () => async (dispatch, getState) => {
try {
// will set the loading to true
dispatch({ type: NOTES_LIST_REQUEST });
// fetching the user info from the state
const{
userLogin: { userInfo },
} = getState();
//just like sending bearer token from postman to backend
const options = {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
};
const data = await axios('http://localhost:5000/api/notes', options);
console.log(data);
// const returnDataFromServer = await data.json()
// .then(dataa=>console.log(dataa))
// console.log(returnDataFromServer);
// if request is success dispatch this action and pass the data to the notes state inside the reducer
dispatch({
type: NOTES_LIST_SUCCESS,
payload: data.data
});
} catch (err) {
const message =
err.response && err.response.returnDataFromServer.message
? err.response.returnDataFromServer.message
: err.message;
// if fails fire this action and pass the message
dispatch({
type: NOTES_LIST_FAIL,
payload: message,
});
}
};
notes page
here I'm trying to access the state of 'my notes' but get undefined
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import MainPage from '../../component/MainPage';
import { Badge, Button, Card, Accordion, AccordionCollapse, AccordionButton } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { listNotes } from '../../redux/actions/notesAction';
import Loading from '../../component/Loading';
import ErrorMessage from '../../component/ErrorMessage';
export default function MyNotes() {
//take notes out from our state
const dispatch = useDispatch();
// name to the state
const noteList = useSelector(state => state.noteList);
console.log(noteList);
//destructing what we need from state
// const { loading, error, notes } = noteList;
const deleteHandler = (id) => {
if (window.confirm('Are You Sure?')) {
}
};
useEffect(() => {
dispatch(listNotes())
}, [dispatch])
// render the notes that come from the backend
return (
<MainPage title="welcome back avi vovgen...">
<Link to='createNewNot'>
<Button className="btn btn-info" style={{ marginLeft: 10, marginBottom: 10 }} size="lg" >
Create new Note
</Button>
</Link>
{/*
{error && <ErrorMessage variant="danger">{error}</ErrorMessage>}
{loading && <Loading />} */}
{/* {notes.map((note) => (
<Accordion key={note._id}>
<Card style={{ margin: 10 }}>
<Card.Header style={{ display: "flex" }}>
<span
style={{
color: "black",
textDecoration: "none",
flex: 1,
cursor: "pointer",
alignSelf: "center",
fontSize: 18
}}>
<AccordionButton as={Card.Text} variant="link" >
{note.title}
</AccordionButton>
</span>
<div>
<Button href={`/note/${note._id}`}>Edit</Button>
<Button variant="danger" className="mx-2" onClick={() => deleteHandler(note._id)}>Delete</Button>
</div>
</Card.Header>
<AccordionCollapse eventKey="">
<Card.Body>
<h4>
<Badge className="btn btn-success">
category-{note.category}
</Badge>
</h4>
<blockquote className="blockquote mb-0">
<p>
{note.content}
</p>
<footer className="blockquote-footer">
Created on-date
</footer>
</blockquote>
</Card.Body>
</AccordionCollapse>
</Card>
</Accordion>
)) */}
}
</MainPage >
)
}

REACT JS not re-rendering when cartItem removed from state

I've been having an issue with react not rendering the updated cartItems in the state. The state updates perfectly fine but the items that I delete don't actually get removed from the rendering.
I'm very new to react, redux and state management but I assume nothing's actually wrong with the state itself, rather how/when the items are being iterated to be rendered. It's only when I refresh the page that it renders accurately.
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { Row, Col, ListGroup, Image, Button } from "react-bootstrap";
import Message from "../components/Message";
import { addToCart, removeFromCart } from "../actions/cartActions";
function Basket({ match, location, history }) {
const productId = match.params.id;
const qty = location.search ? Number(location.search.split("?")[1]) : 1;
const specID = location.search ? Number(location.search.split("?")[2]) : 0;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty, specID));
}
}, [dispatch, productId, qty, specID]);
const removeFromCartHandler = (cartItem) => {
dispatch(removeFromCart(cartItem));
};
const checkoutHandler = () => {
history.push("/login?redirect=shipping");
};
return (
<Row>
<Col md={8}>
{cartItems.length === 0 ? (
<Message variant="primary">
Your cart is empty{" "}
<Link to="/">
<strong>Go Back</strong>
</Link>
</Message>
) : (
<div>
<Link to="/">
<strong>Go Back</strong>
</Link>
<ListGroup variant="flush">
{cartItems.map((item) => (
<ListGroup.Item key={item.product} className="cart">
<Row>
{/* <Col xs={4} md={2}>
<Image src={item.img} alt={item.name} fluid rounded />
</Col> */}
<Col xs={4} md={3}>
<Link to={`/product/${item.product}`}>{item.name}</Link> (
{item.size})
</Col>
<Col xs={2} md={2}>
£{item.price}
</Col>
<Col xs={6} md={3}>
Qty
</Col>
<Col xs={6} md={1}>
<Button
type="button"
variant="light"
onClick={() => removeFromCartHandler(item)}
>
<i className="fas fa-trash" />
</Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
</div>
)}
</Col>
</Row>
);
}
export default Basket;
The reducer for the cart.
import { CART_ADD_ITEM, CART_REMOVE_ITEM } from "../constants/cartConstants";
export const cartReducer = (
state = { cartItems: [{ specList: [{ price: [] }] }] },
action
) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload;
const existItem = state.cartItems.find(
(x) => x.product === item.product && x.size === item.size
);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.product === existItem.product ? item : x
),
};
} else {
return { ...state, cartItems: [...state.cartItems, item] };
}
case CART_REMOVE_ITEM:
return {
...state,
cartItems: state.cartItems.filter((x) => x !== action.payload),
};
default:
return state;
}
};
import axios from "axios";
import { CART_ADD_ITEM, CART_REMOVE_ITEM } from "../constants/cartConstants";
export const addToCart = (id, qty) => async (dispatch, getState) => {
const { data } = await axios.get(`/api/products/${id}`);
dispatch({
type: CART_ADD_ITEM,
payload: {
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
},
});
localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
};
export const removeFromCart = (id) => async (dispatch, getState) => {
dispatch({
type: CART_REMOVE_ITEM,
payload: id,
});
localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
};
You are calling () => removeFromCartHandler(item) but at any other point assume the payload is id. You probably want to do () => removeFromCartHandler(item.id).

How to use the info received in a class into a function React Axios

Im new to react and I want to receive a list of items from an API using Axios in a react class, and then I want to use this info in a react function:
export class JobGetter extends Component {
state = {
Jobs: [],
};
componentDidMount() {
axios.get('/api/jobs/list-jobs', { headers: headers }).then(res => {
this.setState({
Jobs: res.data.map(Jobs => ({
value: Jobs.id,
title: Jobs.title,
companyName: Jobs.company_name,
InternalCode: Jobs.internal_code,
department: Jobs.department,
location: Jobs.location,
tags: Jobs.tags,
benefits: Jobs.benefits,
description: Jobs.description,
requirements: Jobs.requirements,
})),
}, () => {
console.log(this.state.Jobs);
});
});
}
render () {
return (
Jobs
)
}
}
export default function Jobs () {
const classes = useStyles();
return (
<div className='mainBox'>
<div className={classes.root}>
<ExpansionPanel style={{ backgroundColor: 'white' }}>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='panel1a-content'
id='panel1a-header'
style={{ backgroundColor: '#11ed7f' }}
>
<Typography className={classes.heading}>Job Opportunity</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Container className='jobContainer'>
<Typography className={classes.TextHeaders}>
<Row>
<Col>Title:{Jobs.title} </Col>
<Col>Company Name:{Jobs.companyName} </Col>
<Col>Internal Code:{Jobs.InternalCode} </Col>
</Row>
.....
but my GET request doesn't function and I don't receive any info/show any info. what is the correct way to implement this?
With below changes, you should be able to get the data
JobGetter
render() {
return <Jobs jobs={this.state.Jobs} />;
}
Jobs
export default function Jobs ({ jobs }) {
OR
export default function Jobs (props) {
const { jobs } = props;
JobGetter
import Jobs from './Jobs'
export class JobGetter extends Component {
state = {
jobs: [],
};
componentDidMount() {
axios.get('/api/jobs/list-jobs', { headers: headers }).then(res => {
this.setState({
jobs: res.data.map(job => ({
value: job.id,
title: job.title,
companyName: job.company_name,
InternalCode: job.internal_code,
department: job.department,
location: job.location,
tags: job.tags,
benefits: job.benefits,
description: job.description,
requirements: job.requirements,
})),
}, () => {
console.log(this.state.jobs);
});
});
}
render () {
return (
<Jobs jobs={this.state.jobs} />
)
}
}
Jobs
export default function Jobs ({jobs}) {
const classes = useStyles();
return (
<div className='mainBox'>
<div className={classes.root}>
<ExpansionPanel style={{ backgroundColor: 'white' }}>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='panel1a-content'
id='panel1a-header'
style={{ backgroundColor: '#11ed7f' }}
>
<Typography className={classes.heading}>Job Opportunity</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Container className='jobContainer'>
<Typography className={classes.TextHeaders}>
{jobs.map(job => (
<Row key={`job.value`}>
<Col>Title:{Jobs.title} </Col>
<Col>Company Name:{job.companyName} </Col>
<Col>Internal Code:{job.InternalCode} </Col>
</Row>)
)}
</Typography>
</Container>
</ExpansionPanelDetails>
</div>
</div>
Node: you are mixing functional components with classes.

How to add Search function with reactjs in with api

I am working on react app which uses redux for state management. In this app i have to implement search page. In which when user types on search box the app display the product related to that text input.
I have api endpoint like this api.XXXXXXX.com/buyer/product/filter?q=${inputValue}. I tried implement this but i am not able filter this. I haven't worked on this before and couldn't find good tutorial for this.
Here is my code
searchAction.js
export const searchProduct =(value) => dispatch => {
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_LOAD
});
new _rest().get(`/buyer/product/filter?q=${value}`)
.then(res => {
console.log(res);
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_SUCCESS,
payload: res
})
}).catch(err => {
console.log(err)
dispatch({
type: searchActionTypes.SEARCH_PRODUCT_ERROR,
error: err
})
});
}
searchActionreducer.js
const initialState = {
isFetching: false,
searchData: {},
isFetched: false,
isError: null
}
export default (state=initialState, action) =>{
switch (action.type) {
case searchActionTypes.SEARCH_PRODUCT_LOAD:
return {
...state,
isFetching: true
};
case searchActionTypes.SEARCH_PRODUCT_SUCCESS:
return {
...state,
isFetching: false,
searchData: action.payload.data._embedded,
isFetched: true
};
case searchActionTypes.SEARCH_PRODUCT_ERROR:
return {
...state,
isFetched: false,
};
default:
return {
...state
}
}
}
searchPage.js
class SearchPage extends React.Component {
state = {
inputValue: '',
}
componentDidMount() {
this.props.searchProduct('');
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('component update', prevState)
if(this.state.inputValue !== prevState.inputValue) {
this.props.searchProduct(this.state.inputValue);
}
}
render() {
console.log('SearchPage Result', this.props.product);
const {product} = this.props;
const {inputValue} =this.state;
const updateInputValue = (e) => {
e.preventDefault();
this.setState({
inputValue: e.target.value
})
}
return(
<Grid container>
<div style={{width: '100%', display:'flex', justifyContent: 'center' }}>
<form noValidate autoComplete="off">
<TextField value={inputValue} onChange={updateInputValue} id="standard-basic" label="Search Product" />
</form>
</div>
<Grid container spacing={8} style={{padding: '30px'}}>
{product && product.productResourceList &&
product.productResourceList.map((data, index) => {
return(
<Grid item xs={6}>
<MainCards key={data.productId} data={data} />
</Grid>
)
})}
</Grid>
</Grid>
)
}
}
const mapStateToProps =(state) => {
return {
product: state.search.searchData
}
}
const mapDispatchToProps = {
searchProduct,
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchPage);
Here In componentDidMount Initially when component mounts i made display all the product. After that in componentDidUpdate i trying get filtered data from api buy passing this.state.inputValue as value of q in api endpoint api.XXXXXXX.com/buyer/product/filter?q=${inputValue}. But component is not updating at all.

Clicking like icons increases the like count of other components

Upon the click of a single like, it is increasing the number of likes for both separate components. What is causing both like numbers to increase, and how can I code it to where only one like number increases upon clicking a like?
I have also include the console in the picture below where I have console logged the logic in my reducer. You can find the code for the reducer further below the picture.
Reducer code
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "../actions/types";
const initialState = {
goals: [],
goal: null,
loading: true,
error: {}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_GOALS:
return {
...state,
goals: payload,
loading: false
};
case GOAL_ERROR:
return {
...state,
error: payload,
loading: false
};
case UPDATE_LIKES:
return {
...state,
goals: state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) ? { ...goal, likes: payload.likes } : goal
),
loading: false
};
default:
return state;
}
}
Action code
import axios from "axios";
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "./types";
// Get goals
export const getGoals = () => async dispatch => {
try {
const res = await axios.get("/api/goal/goalfeed");
dispatch({
type: GET_GOALS,
payload: res.data
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Add like
export const addLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/like/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Remove like
export const removeLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/unlike/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
Goals component code
import React, { useEffect } from "react";
import Moment from "react-moment";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { addLike, removeLike } from "../../actions/goal";
import { getGoals } from "../../actions/goal";
import Spinner from "../layout/Spinner";
import Navbar from "../dashboard/Navbar";
import ThumbUpAltIcon from "#material-ui/icons/ThumbUpAlt";
import ThumbDownAltIcon from "#material-ui/icons/ThumbDownAlt";
import ChatIcon from "#material-ui/icons/Chat";
import DeleteIcon from "#material-ui/icons/Delete";
import DoneIcon from "#material-ui/icons/Done";
import {
Typography,
Container,
CssBaseline,
makeStyles,
Grid,
Avatar,
Paper,
Button
} from "#material-ui/core";
const useStyles = makeStyles(theme => ({
paper: {
height: "auto",
marginBottom: theme.spacing(3)
},
actionButtons: {
marginTop: "3vh"
},
profileHeader: {
textAlign: "center",
marginBottom: 20
},
avatar: {
width: theme.spacing(7),
height: theme.spacing(7)
}
}));
const Goals = ({
getGoals,
auth,
addLike,
removeLike,
goal: { goals, user, loading }
}) => {
useEffect(() => {
getGoals();
}, [getGoals]);
const classes = useStyles();
return loading ? (
<>
<Navbar />
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Spinner />
</div>
</Container>
</>
) : (
<>
<CssBaseline />
<Navbar />
<main>
<Container>
<Typography variant="h2" className={classes.profileHeader}>
Goals
</Typography>
{/* parent grid */}
<Grid container spacing={4}>
{goals.map(singleGoal => (
<Grid
className={classes.paper}
key={singleGoal._id}
spacing={1}
container
item
direction="row"
alignItems="center"
component={Paper}
>
<Grid
item
container
direction="column"
justify="center"
alignItems="center"
xs={3}
>
<Avatar className={classes.avatar} src={singleGoal.avatar} />
<Typography variant="caption">
{singleGoal.first_name} {singleGoal.last_name}
</Typography>
<Typography variant="caption" className={classes.postedOn}>
Posted on{" "}
<Moment format="MM/DD/YYYY">{singleGoal.date}</Moment>
</Typography>
</Grid>
<Grid container item direction="column" xs={9}>
<Typography variant="body1">{singleGoal.text}</Typography>
<Grid item className={classes.actionButtons}>
<Button size="small" onClick={e => addLike(singleGoal._id)}>
<ThumbUpAltIcon />
</Button>
<Typography variant="caption">
{singleGoal.likes.length}
</Typography>
<Button
size="small"
onClick={e => removeLike(singleGoal._id)}
>
<ThumbDownAltIcon />
</Button>
<Button href={`/goal/${singleGoal._id}`} size="small">
<ChatIcon />
</Button>
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DoneIcon />
</Button>
)}
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DeleteIcon />
</Button>
)}
</Grid>
</Grid>
</Grid>
))}
</Grid>
</Container>
</main>
</>
);
};
Goals.propTypes = {
getGoals: PropTypes.func.isRequired,
goal: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
goal: state.goal,
auth: state.auth
});
export default connect(mapStateToProps, { getGoals, addLike, removeLike })(
Goals
);
There exists a flaw in your conditional test.
state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) // What is this? it will always evaluate to true
? { ...goal, likes: payload.likes }
: goal
)
console.log('EQUAL?', console.log() === console.log()); // true
console.log('EQUAL?', console.log(3) === console.log(3)); // true
console.log('EQUAL?', console.log(3) === console.log('three')); // true
console.log('EQUAL?', console.log('apple') === console.log({})); // true
console.log('EQUAL?', console.log(42) === console.log(-42)); // true
The function console.log is a void return, i.e. undefined, so you are comparing undefined === undefined, which is always true.
console.log(undefined === undefined); // true
You are spreading in the new 'likes' value to every goal object.
Try instead:
state.goals.map(
goal => goal._id === payload.goalId
? { ...goal, likes: payload.likes }
: goal
)

Categories

Resources