React Redux store does not update when object is assigned - javascript

So I have this function:
send_data({ pathname, data })
{
$.ajax({
url: "src/php/search.php",
beforeSend: () =>{
// this.props.dispatch( { type: "set_loading", payload: true } );
},
data: {
type: "write_report",
part: pathname,
data
},
method: "post",
success: (r) =>
{
console.log("I am sending the data and getting something back")
console.log(r);
console.log("after r");
// this.props.dispatch( { type: "set_loading", payload: false } );
Important>> this.props.dispatch({ type: "set_report", payload: JSON.parse(r) });
}
})
}
What it does is that it sends some data to a php backend, which then responds with a json string, which I parse to create an object from.
This is my reducer code:
const componentReducer = (state = default_component, action) => {
switch(action.type)
{
case "set_report":
{
state = {...state, report: action.payload};
break;
}
}
return state;
I know the store is updating as when I print this.props.report (the object I'm currently using) it gets something different before and after:
Before:
Object {assessors=[3], report=Object, risks=[4], ...}
After:
Object {assessors=[2], report=Object, risks=[4], ...}
However the display does not update.
But!!! If I dispatch an empty object {}
this.props.dispatch({ type: "set_report", payload: {} });
it does re-render the component (I also have some code that checks whether or not the report object is empty and will thusly, return me an object)
Rendering component info (for brevity, things are missing):
add_repeat(default_user)
{
this.repeats.push(<StartMultiple key={this.count} default_user={default_user} users={this.props.users} id={this.count} delete_this={this.delete_this} />);
this.setState({ repeats: this.repeats });
this.count++;
}
render()
{
return(
<div>
<p><b>Date</b></p>
<span>Select the date when assessment was performed:</span>
<input id="datepicker" name="datepicker" type="text" readOnly/>
<div>
(<i>If the select is empty, but you know there should be someone in it. That person may be no longer at the institute. If that person should exist, please notify ITS.</i>)
</div>
<div>
{this.state.repeats}
</div>
<button className="btn btn-default" onClick={this.add_repeat.bind(this, "none")}>Add Assessor</button>
</div>
);
}
}
const Start = connect(
(store) =>
{
return {
number: store.component.number,
report_id : store.component.report_id,
assessors: store.component.report.assessors,
users: store.component.users,
date: store.component.report.report.date,
};
}) (StartComponent);
export default Start;
Child component:
render()
{
var { users } = this.props;
users = {"none": "", ...users};
return(
<div>
<p><b>Assessor</b></p>
<select name="assessor" defaultValue={this.props.default_user}>
{ Object.keys(users).map( (key, index) => <option key={index} value={users[key]}>{users[key]}</option> ) }
</select>
<span style={{ display : "inline-block", paddingLeft : "10px"}}>
<button className="btn btn-default" onClick={this.delete_this.bind(this, this )}>Delete</button>
</span>
</div>
);
}

Related

How do I watch the output of a function?

I have 2 buttons. One adds a movie to local storage, the other removes it from there. I made a function that basically switches the button. If the movie is added it shows "remove", if the movie's not been added it shows the button "add".
The function works but it doesn't know when the boolean changes so the button doesn't change. Someone explained that i should use watch property, but how am I supposed to watch an output of a function?
here is the code
<template>
<div>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!showButton(movie.id)" type="submit" #click="storeMovie(movie.id)" >
Aggiungi
</button>
<button v-show="showButton(movie.id)" type="submit" #click="removeMovie(movie.id)">
Rimuovi
</button>
</div>
<div class="card" v-for="favourite in watchlist"
:key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'HomeComp',
data () {
return {
movies: [],
watchlist: [],
movie: null,
}
},
mounted () {
axios
.get('https://api.themoviedb.org/3/movie/popular?api_key=###&language=it-IT&page=1&include_adult=false&region=IT')
.then(response => {
this.movies = response.data.results
// console.log(response.data.results)
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
},
watch: {
switchButton(oldValue, newValue) {
if (oldValue != newValue) {
this.showButton(id) = true;
} //made an attempt here
}
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id )
this.watchlist.push(favouriteMovie);
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
removeMovie(id) {
const removedMovie = this.watchlist.find(movie => movie.id === id )
const indexMovie = this.watchlist.indexOf(removedMovie);
if (indexMovie > -1) {
this.watchlist.splice(indexMovie, 1);
}
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
showButton(id) {
const favouriteMovie = this.watchlist.find(movie => movie.id === id )
if (favouriteMovie && favouriteMovie.length > 0) {
return true
} else{
return false
}
}
},
}
</script>
<style scoped lang="scss">
</style>
A better approach would be to store the state of a movie being stored or not in the watchlist directly on the movie object.
Then use a computed to get the watchlist from the movie list instead of using two different arrays.
<template>
<div>
<div class="card" v-for="movie in movies" :key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!movie.toWatch" type="submit" #click="storeMovie(movie.id)">
{{ movie.toWatch ? 'Rimuovi' : 'Aggiungi' }}
</button>
</div>
<div class="card" v-for="favourite in watchList" :key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
export default {
name: 'HomeComp',
data() {
return {
movies: [],
}
},
computed: {
// Get the watchList from the movies list
watchList() {
return this.movies.filter(movie => movie.toWatch)
}
},
watch: {
watchList(newWatchList) {
// Update the localStorage whenever the list changes
localStorage.setItem("watchlist", JSON.stringify(newWatchList));
}
},
mounted() {
// your axios call
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id)
if (favouriteMovie) {
// just reverse the boolean
favouriteMovie.toWatch = !favouriteMovie.toWatch
}
},
},
}
</script>

Get figure from function and set it as state once user types in input

I have a component that I want to use to update a 'balance' in a database.
To do this, I am pulling the figure in my componentDidMount using axios.get:
componentDidMount() {
axios.get("/api/fetch/fetchEditDebt", {
params: {
id: this.props.match.params.id
}
})
.then((response) => {
this.setState({
balance: response.data.balance,
})
})
}
I then have an input which takes the amount the user wants to add to the balance:
<form method="POST" onSubmit={this.onSubmit}>
<input className="edit-balance-input" type="number" name="add" value={this.state.add} onChange={this.onChange} step="1" />
<button className="edit-balance-button" type="submit">Save</button>
</form>
I then use a function to take the original balance from state, and the 'add' figure from the input state, and add them together:
const calculateUpdatedBalance = () => {
return parseInt(this.state.balance) + parseInt(this.state.add)
}
And this updated figure is then rendered inside of a span so the user can see the new balance:
<div className="edit-balance-balance-container">
<p className="edit-balance-balance-paragraph">Updated balance: </p>
<span className="edit-balance-updated">-£{calculateUpdatedBalance()}</span>
</div>
This all works great, and as expected - the difficulty comes in when I then want to post the updated balance to my database. I tried to post the add state, but unsurprisingly that just updates the balance to the amount the user put into the input.
So how do I access the figure generated by my calculateUpdatedBalance() function? I thought about trying to setState() in the function, but that produces a "too many state updates" error.
Does anyone have any suggestions for how I can get that updated figure, and post that to my database?
Here's my full component for reference:
class Add extends Component {
constructor(props) {
super(props)
this.state = {
balance: '',
add: 0,
updatedBalance: '',
fetchInProgress: false
}
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
this.setState({
fetchInProgress: true
})
axios.get("/api/fetch/fetchEditDebt", {
params: {
id: this.props.match.params.id
}
})
.then((response) => {
this.setState({
balance: response.data.balance,
fetchInProgress: false
})
})
.catch((error) => {
this.setState({
fetchInProgress: false
})
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
})
}
onChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
console.log(this.state.add)
}
onSubmit = async(e) => {
e.preventDefault();
console.log(this.props.match.params.id)
await axios.post("/api/edit/editDebtBalance", {
balance: this.state.add,
}, {
params: {
id: this.props.match.params.id
}
})
this.props.history.push('/dashboard');
}
render() {
const calculateUpdatedBalance = () => {
return parseInt(this.state.balance) + parseInt(this.state.add)
}
return (
<section className="edit-balance-section">
<div className="edit-balance-container">
<DashboardReturn />
<div className="edit-balance-content">
<p className="edit-balance-paragraph">How much would you like to add to your balance?</p>
<div className="edit-balance-balance-container">
<p className="edit-balance-balance-paragraph">Current Balance: </p>
<span className="edit-balance-original">-£{this.state.balance}</span>
</div>
<div className="edit-balance-balance-container">
<p className="edit-balance-balance-paragraph">Updated balance: </p>
<span className="edit-balance-updated">-£{calculateUpdatedBalance()}</span>
</div>
<form method="POST" onSubmit={this.onSubmit}>
<input className="edit-balance-input" type="number" name="add" value={this.state.add} onChange={this.onChange} step="1" />
<button className="edit-balance-button" type="submit">Save</button>
</form>
</div>
</div>
</section>
)
}
}
If you make calculateUpdatedBalance() a member method of the Add component, then you can call it from both render() and onSubmit():
calculateUpdatedBalance() {
return parseInt(this.state.balance) + parseInt(this.state.add)
}
onSubmit = async (e) => {
...
await axios.post("/api/edit/editDebtBalance", {
balance: this.calculateUpdatedBalance(),
...
};
render() {
return (
...
<span className="edit-balance-updated">-£{this.calculateUpdatedBalance()}</span>
...
}

Like counter - handleLike function

I am trying to add a like counter per challenge. Every time I click on the like icon, the handleLike() starts working and should add 1 like to that specific challenge. I tried to do this in my handleLike() function but struggle to make it work. I guess my approach is not changing the like in the specific challenge.
class Allchallenges extends React.Component {
constructor() {
super()
this.state = {
challenges: []
}
this.handleLike=this.handleLike.bind(this)
}
componentDidMount(){
axios({
method: "GET",
url: `${process.env.REACT_APP_API_BASE}/allchallenges`,
withCredentials: true
})
.then(response => {
console.log(response)
let challengeslist = response.data;
this.setState({challenges: challengeslist})
})
.catch(error => {
console.log("You've made an error charles: ",error)
})
}
handleLike(challengeId){
console.log("This is the handlelikebutton speaking!")
const likedchallenge = this.state.challenges.filter(challenge => challenge._id === challengeId)
likedchallenge.likes++
}
render(){
return (
<DefaultLayout>
<div className="challengeoverviewlist">
<h1>All challenges</h1>
<div className="challengeboxes">
{
this.state.challenges.map(challenge =>
(
<div className="totalbox" key={challenge._id}>
<div className="likedislikesbox">
<div className="likecontainer">
<div className="leftalignment"><FontAwesomeIcon icon={faThumbsUp} onClick={()=>this.handleLike(challenge._id)}/></div>
<p className="likestat">{challenge.likes}</p>
</div>
<div className="dislikecontainer">
<div className="leftalignment"><FontAwesomeIcon icon={faThumbsDown}/></div>
<p className="dislikestat">{challenge.dislikes}</p>
</div>
<div className="satisfactioncontainer">
<div className="leftalignment"><FontAwesomeIcon icon={faBalanceScale}/></div>
<p className="satisfactionstat">{challenge.likes/(challenge.dislikes + challenge.likes)*100}%</p>
</div>
</div>
<Challengebox
key={challenge._id}
id={challenge._id}
title={challenge.title}
description={challenge.description}
/>
<button className="deletebutton" onClick={()=> this.onDelete(challenge._id)}>
Delete
</button>
</div>
))
}
</div>
</div>
</DefaultLayout>
)
}
}
export default Allchallenges
const challengeSchema = new Schema({
title: String,
description: String,
initiator: {type: Schema.Types.ObjectId, ref:"User"},
likes: { type: Number, default: 0 },
dislikes: { type: Number, default: 0 },
satisfaction: { type: Number, default: 0 },
likealready: { type: Boolean, default: false },
dislikealready: { type: Boolean, default: false }
})
I can't speak to specific error messages since none were provided, but one observation:
You have:
handleLike(challengeId){
console.log("This is the handlelikebutton speaking!")
const likedchallenge = this.state.challenges.filter(challenge => challenge._id === challengeId)
likedchallenge.likes++
}
If you look at what Array.prototype.filter() does, it returns a copy of an array that matches your condition.
So in this case, you have potentially filtered the array to one item (with the matching challenge ID), but you are manipulating it as if it is the challenge object, not an array of up to one challenge.
You'd end up with something like
const likedchallenge = [Challenge];
// Try to increment likes
likedchallenge.likes++;
// This tries to increment lies on the ARRAY containing the challenge object
You probably want to GET the challenge object, e.g. using Array.prototype.find() instead.
UPDATE: Regarding React's setState
As noted in the comments below, manipulating state directly is not something you want to do. Instead, you should rely on informing React of your state change. Roughly, it might look something like:
handleLike(challengeId) {
this.setState(state => {
challenges: state.challenges.map(c => {
if (c._id === challengeId) {
c.likes++;
}
return c;
}),
});
}

How to disable only selected button onClick inside .map() function of REACT js

I have shopping Application in REACT js. Im displaying all products using .map() function, and also showing "ADD to CART" button infront of each product. When ADD to Cart btn is clicked, it adds the clicked product ID in local storage then I display these selected products in Shopping Cart by retrieving IDs from localStorage. It all works fine.
Now what I wanted is to disable the "ADD to Cart" button(for selected product only) when its clicked Once. I did it by setting state but it actually Disables ALL "ADD to Cart" Buttons infront of all PRODUCTS instead of disabling only selected button.
I searched this issue alot, and the soltion I got everywhere is just to setState to true/false to enable/disable button. I did it but no use because its doing it for ALL products on that Page. Please help me what to do.
Here is my REACT JS code:
export default class SpareParts extends Component
{
constructor()
{
super()
this.state = {
spareParts: [],
cart: [],
inCart: false,
disabledButton: false
};
this.ViewDeets = this.ViewDeets.bind(this);
this.AddToCart = this.AddToCart.bind(this);
}
ViewDeets= function (part)
{
this.props.history.push({
pathname: '/partdetails',
state: {
key: part
}
});
}
AddToCart(param, e)
{
var alreadyInCart = JSON.parse(localStorage.getItem("cartItem")) || [];
alreadyInCart.push(param);
localStorage.setItem("cartItem", JSON.stringify(alreadyInCart));
this.setState({
inCart: true,
disabledButton: true
})
}
componentDidMount()
{
console.log("Showing All Products to Customer");
axios.get('http://localhost/Auth/api/customers/all_parts.php', {
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}} )
.then(response =>
{
this.setState({
spareParts :response.data.records
});
})
.catch(error => {
if (error) {
console.log("Sorry Cannot Show all products to Customer");
console.log(error);
}
});
}
render()
{
return (
<div id="profileDiv">
{this.state.spareParts.map( part =>
<Col md="3" lg="3" sm="6" xs="6">
<Card>
<Image src={"data:image/png[jpg];base64," + part.Image}
id="partImg" alt="abc" style={ {width: "90%"}} />
<h4> {part.Name} </h4>
<h5> Rs. {part.Price} </h5>
<h5> {part.Make} {part.Model} {part.Year} </h5>
<h5> {part.CompanyName} </h5>
<button
onClick={()=> this.ViewDeets(part) }>
View Details
</button>
<button onClick={() => this.AddToCart(part.SparePartID)}
disabled={this.state.disabledButton ? "true" : ""}>
{!this.state.inCart ? ("Add to Cart") : "Already in Cart"}
</button>
</Card>
</Col>
)}
</div>
);
}
}
Do you only need to disable one button at a time? If so, change your state to be not a boolean but a number indicating which button is disabled. Then in render, only disable if the button you're rendering has the same index as found in the state.
this.state = {
disabledButton: -1
// ...
}
// ...
AddToCart(index, param, e) {
//...
this.setState({
inCart: true,
disabledButton: index
})
}
// ...
{this.state.spareParts.map((part, index) => {
// ...
<button onClick={() => this.AddToCart(index, part.SparePartID)}
disabled={this.state.disabledButton === index}>
{!this.state.inCart ? ("Add to Cart") : "Already in Cart"}
</button>
})}
If instead each button needs to be independently disableable at the same time, change your state to be an array of booleans with the same length as the spare parts, and in the render method have each button look up whether it should be disabled in that array.
this.state = {
spareParts: [],
disabledButtons: [],
// ...
}
// ...
axios.get('http://localhost/Auth/api/customers/all_parts.php', {
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}} )
.then(response =>{
this.setState({
spareParts: response.data.records,
disabledButtons: new Array(response.data.records.length).fill(false)
});
});
// ...
AddToCart(index, param, e) {
//...
this.setState(oldState => {
const newDisabledButtons = [...oldState.disabledButtons];
newDisabledButtons[index] = true;
return {
inCart: true,
disabledButtons: newDisabledButtons,
}
});
}
// ...
{this.state.spareParts.map((part, index) => {
// ...
<button onClick={() => this.AddToCart(index, part.SparePartID)}
disabled={this.state.disabledButtons[index]>
{!this.state.inCart ? ("Add to Cart") : "Already in Cart"}
</button>
})}

How to fill dropdown with JSON data in React?

I try to fill in a dropdown with data from the JSON format but for now the dropdown is empty (no results found...)
I certainly have a mistake and I can not understand where I'm confusing.
I will attach a screen of my API.
I want to get Station and NameStation..
API for Stations
My code:
import React, { Component } from 'react';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
function parseStations(stations){
return stations.map((station) => {
return { label: station.NameStation, value: station.Station };
});
}
export default class Weather extends Component {
constructor(props) {
super(props);
this.state = {
options: [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' }
], stations: [
],
value: null
}
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.setState({ value: event.value });
console.log('Boolean Select value changed to', event.value);
}
componentDidMount() {
this.getStations();
}
getStations() {
fetch('http://localhost:56348/api/stations', {
data: 'Station',
data: 'NameStation',
method: "GET"
}).then(res => res.json())
.then(res => this.setState({ stations: parseStations(res.stations) }))
//.then(res => this.setState({ stations: res.stations }))
//.catch(e => )
}
render() {
return (
<div className="MasterSection">
<div className="wrapper">
<div className="section">Изберете № на станция</div>
<Select
onChange={this.onChange}
//options={this.state.options}
options={this.state.stations}
value={this.state.value}
clearable={false}
/>
</div>
<div class="section">
<input type="text" class="form-control" placeholder="Брой дни назад" aria-label="Username" aria-describedby="basic-addon1"></input>
</div>
<div class="section">
<button type="button" class="btn btn-outline-dark">Покажи</button>
</div>
</div>
);
}
}
Seems you made a typo naming the prop stations instead of options :
<Select
onChange={this.onChange}
options={this.state.stations} // here
value={this.state.value}
clearable={false}
/>
Edit : you'll need to parse your json first to pass a proper array of objects like this : [{ label: nameStation, value: Station }]
Edit 2 : Here's a parser for your data :
function parseStations(stations){
return stations.map((station) => {
return { label: station.NameStation, value: station.Station };
});
}
You can call this in your async request before setting the state :
.then(res => this.setState({ stations: parseStations(res.stations) }))
componentDidMount() is executed only after render() is completed. so there's no way getStations() gets executed at the time your UI gets rendered. it is not a good idea to setState inside componentDidMount() as it triggers re rendering. use componentWillMount() instead.
correct the typo that Dyo mentioned and use options={this.state.stations}

Categories