Kick off a function when two properties in state are set - javascript

I'm new to React JS and am slowly blundering my way along but hit an issues I figured I'd ask about here.
I have a component that is a container of the state and all the data I care about
class PirateContainer extends Component {
constructor(props) {
super(props);
this.state = {
PirateCaptains: [],
Year: [],
Ship: [],
PirateChoices: {
SelectedYear : "",
SelectedPirateCaptain : "",
SelectedShip : ""
}
};
this.handleYearChange = this.handleYearChange.bind(this);
this.handleShipChange = this.handleShipChange.bind(this);
this.handlePirateCaptainChange = this.handlePirateCaptainChange.bind(this);
}
popYear(accessLevel){
fetch('https://Pirateman/api/Years').then(response => {
return response.json();
})
.then((json) => {
this.setState(
prevState => ({
Year: json,
Choices: {
...prevState.PirateChoices,
SelectedYear: json[0]
}
}), () => {
console.log(this.state);}
);
});
}
popCaptain({
fetch('https://Pirateman/api/PirateCaptains').then(response => {
return response.json();
})
.then((json) => {
this.setState(
prevState => ({
PirateCaptain: json,
PirateChoices: {
...prevState.PirateChoices,
SelectedPirateCaptain: json[0]
}
}), () => {console.log(this.state);}
);
});
}
popShip(year, captain){
fetch('https://Pirateman/api/Ships/' + year + '/' + captain ).then(response => {
return response.json();
})
.then((json) => {
this.setState(
prevState => ({
Ship: json,
PirateChoices: {
...prevState.PirateChoices,
SelectedShip: json[0]
}
}), () => {console.log(this.state);}
);
});
}
handleYearChange(e) {
let value = e.target.value;
if(this.state.PirateChoices.SelectedYear === value)
{
console.log('Do nothing')
}
else
{
this.setState(
prevState => ({
PirateChoices: {
...prevState.PirateChoices,
SelectedYear: value
}
}), () => {console.log(this.state)}
);
}
}
handlePirateCaptainChange(e) {
let value = e.target.value;
if(this.state.PirateChoices.SelectedPirateCaptain === value)
{
console.log('Do nothing')
}
else
{
this.setState(
prevState => ({
PirateChoices: {
...prevState.PirateChoices,
SelectedPirateCaptain: value
}
}), () => {console.log(this.state)}
);
}
}
handleShipChange(e) {
let value = e.target.value;
if(this.state.PirateChoices.SelectedShip === value)
{
console.log('Do nothing')
}
else
{
this.setState(
prevState => ({
PirateChoices: {
...prevState.PirateChoices,
SelectedShip: value
}
}), () => {console.log(this.state)}
);
}
}
I have a generic component that loads a select form that takes the change function and triggers onchange.
So the first thing the app does is get Years and PirateCaptains from a service and sets the state of those (as well as the Selected Ship and PirateCaptain which is the first entry of the array shown). On change the selected is updated.
The problem I have is that the Ship data depends on the combination of SelectedPirateCaptain and SelectedYear.
I feel like the easy way of populating that is having it trigger on change of either of those two properties, but I can't figure out a way to set up a function to do that.
I've also tried (for initial loads) using promises or callbacks but can't seem to figure out how to get it to wait until the state of both selected properties has a value. (I tried a while loop but the function never seems to get the state with the value.)
Any help is greatly appreciated in advance, I suspect I'm being an idiot and missing something obvious so thanks in advance.

Related

REACT, Help me understand prevState

What is the difference between defining this counterHandler like this
counterHandler = () => {
this.setState(() => {
return { times: this.state.times + 1 }
});
}
And this?
counterHandler = () => {
this.setState((prevState) => {
return { times: prevState.times + 1 }
});
}
Does the state from a component always gets passed to setState automatically?
If you only use One data inside state, you dont need to make callback of prevState,
but if your state more than one, you need to callback of prevstate because this will make your other and previous data will not be lost.
for example
const [state, setState] = useState({
loading: false,
data: []
})
const handleLoading = () => {
setState({
loading: true
})
}
const handleData = () => {
setState({
data: [a,b,c] // you will lost your loading = true
})}
const handleData = () => {
setState((prevState) => {
...prevState,
data: [a,b,c] // you still have loading = true
})
}

Updating a single item in React context state

So I have a Context of the following format:
class UserProvider extends Component {
constructor(props) {
super(props)
this.initialize = (details) => {
this.setState(state => {
//Setting each item individually here
//e.g state.a = details.a
})
}
this.editA = () => {
this.setState(state => {
//change A here
})
}
this.editB = () => {
this.setState(state => {
//Change B here
})
}
this.state = {
a: null,
b: null,
editA: this.editA,
editB: this.editB
}
}
render() {
return (
<User.Provider value={this.state}>
{this.props.children}
</User.Provider>
)
}
}
So for each state, I have a separate function to update it. If I want to update only a single state, what should I do?
Consider implementing a generic function so that you can control your key and the corresponding value.
i.e.
const changeField = (key, value) => this.setState({ [key]: value});
Function call
changeField('a','value_a')
changeField('b','value_b')

state with different name but same array changed

So, I define 2 states with the same array, it's listRasioUom and showListRasioUom. but when I setstate to listRasioUom state, the showListRasioUom state also changed with the same value as listRasioUom. any help? this is my code
handleChange = (e) => {
if (["rasio", "uom"].includes(e.target.name)) {
let listRasioUom = this.state.listRasioUom
if(listRasioUom.some(el => el.uom === e.target.value))
{
alert('Duplicate uom')
}
else
listRasioUom[e.target.dataset.id][e.target.name] = e.target.value;
this.setState({ listRasioUom })
}
}
}
showListRasioUom state is used for fetching the data on my datatables, while listRasioUom state is used to modified the state, so i only want to change listrasiouom state, but when i use setstate on listRasioUom state, the showListRasioUom also change.
<MDBDataTable
info={false}
paging={false}
searching={false}
striped
bordered
small
data={{columns: this.state.columns, rows: this.state.showListRasioUom}}
noBottomColumns
/>
Object and arrayss in javascript are assigned by reference and if you mutate the original array the other array which also holds the same reference is also updated
You should update your data in an immutable manner
handleChange = (e) => {
if (["rasio", "uom"].includes(e.target.name)) {
let listRasioUom = this.state.listRasioUom
if(listRasioUom.some(el => el.uom === e.target.value))
{
alert('Duplicate uom')
}
else {
const id = e.target.dataset.id;
const {name,value} = e.target;
this.setState(prev => ({
listRasioUom: prev.listRasioUom.map((item, i) => {
if(i == id) return {...item, [name]: value};
})
return item;
}))
}
}
}

Update React state asynchronously from multiple sources

I'm trying to use immutability-helper to update my React state asynchronously from multiple sources (API calls). However, it seems to me that this is not the way to go, since state always gets updated with values from a single source only. Can someone explain me why is this the case and how to properly handle updates of my state?
import React from 'react';
import update from 'immutability-helper';
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
updateInterval: 60, // in seconds
apiEndpoints: [
'/stats',
'/common/stats',
'/personal/stats',
'/general_info',
],
items: [
{ itemName: 'One', apiUrl: 'url1', stats: {} },
{ itemName: 'Two', apiUrl: 'url2', stats: {} },
],
};
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
this.fetchData();
setInterval(() => this.fetchData(), this.state.updateInterval * 1000);
}
fetchData() {
this.state.apiEndpoints.forEach(endpoint => {
this.state.items.forEach((item, i) => {
fetch(`${item.apiUrl}${endpoint}`)
.then(r => r.json())
.then(res => {
// response from each endpoint contains different keys.
// assign all keys to stats object and set default 0 to
// those which don't exist in response
const stats = {
statsFromFirstEndpoint: res.first_endpoint ? res.first_endpoint : 0,
statsFromSecondEndpoint: res.second_endpoint ? res.second_endpoint : 0,
statsFromThirdEndpoint: res.third_endpoint ? res.third_endpoint : 0,
};
this.setState(update(this.state, {
items: { [i]: { $merge: { stats } } }
}));
})
.catch(e => { /* log error */ });
});
});
}
render() {
return (
<div className="App">
Hiya!
</div>
);
}
}
export default App;
You should use the prevState argument in setState to make sure it always use the latest state:
this.setState(prevState =>
update(prevState, {
items: { [i]: { $merge: { stats } } },
}));
Alternatively, map your requests to an array of promises then setState when all of them resolved:
const promises = this.state.apiEndpoints.map(endPoint =>
Promise.all(this.state.items.map((item, i) =>
fetch(), // add ur fetch code
)));
Promise.all(promises).then(res => this.setState( /* update state */ ));

How to remount the data after the textbox has been emptied -- REACT

I'm new to react. I have a component here that will automatically load the data from a database (mongo) via express server url when the page was loaded.
componentDidMount() {
var self = this;
fetch('/movies')
.then((resp) => resp.json())
.then(movies => self.setState({
movies: movies
}))
.catch((err) => console.log(err));
}
this will load all data and display them on screen and when the user types in to the textbox it will update the display to the search term:
movieSearch(term){
var self = this;
axios.get('/moviesbytitle', {
params: {
term: term
}
})
.then(movies => self.setState({
movies: movies.data
}))
.then(() => console.log('this is is a state >>>>+++', self.state))
.catch((err) => console.log(err));
}
render(){
const movieSearch = _.debounce((term) => { this.movieSearch(term) }, 300);
return (
<div>
<MovieTopBar />
<SearchBar onSearchTermChange={movieSearch}/>
<MovieList movies={ this.state.movies }/>
<MovieFooter />
</div>
);
}
};
class SearchBar extends Component{
constructor(props){
super(props);
this.state = { term: '' };
this.onInputChange = this.onInputChange.bind(this);
}
<input type="text" value={this.state.term} onChange={event => this.onInputChange(event.target.value)} className="form-control" id="" placeholder="Enter the title of the movie..." />
onInputChange(term){
this.setState({ term });
this.props.onSearchTermChange(term);
}
}
I am trying to find a way how can I update the data searched -> to the data when it was first mounted w/c displays the whole data whenever the textbox for search is emptied. Is there a specific lifecycle method to do that? or do I need to do a separate function? Thanks for the help in advance!
I would suggest
this.movieSearch = _.debounce((term) => { this.movieSearch(term) }, 300);
move it to constructor
so you can avoid creating a new function each render, this will be expensive on memory
you also do not need var self = this;
using anonymous functions like () => {} will preserve this
and coming back to question, if I understood it, you want to show original movie list when user deletes input in search,
componentDidMount() {
var self = this;
fetch('/movies')
.then((resp) => resp.json())
.then(movies => {
self.setState({ movies: movies });
this.originalResults = movies;
})).catch((err) => console.log(err));
}
you can do it by keeping reference to original results
and then you can add
movieSearch(term){
if (term === '') {
this.setState({movies:this.originalResults})
return; // exit the search function
} ...rest of function
As #AlexGvozden said, you should move your movieSearch to be part of the component class, so
class ShowMoviesList {
constructor() {
this.movieSearch = this.movieSearch.bind(this);
}
componentDidMount() {
this.getAllMovies();
}
getAllMovies() {
fetch('/movies')
.then((resp) => resp.json())
.then(movies => this.setState({
movies: movies
}))
.catch((err) => console.log(err));
}
getMovieByTitle(term) {
axios.get('/moviesbytitle', {
params: {
term: term
}
})
.then(movies => this.setState({
movies: movies.data
}))
.then(() => console.log('this is is a state >>>>+++', this.state))
.catch((err) => console.log(err));
}
movieSearch(term) {
// remember prevents blank spaces
const currentTerm = term ? term.trim() : term;
// remember clear last getMovieByTitle call
if (this.timer) {
clearTimeout(this.timer);
}
// if term is empty, so call getAllMovies again
// prevent call cache movies, because cache movie will always return the first call
// but won't render new movies if exists
if (!currentTerm) {
return this.getAllMovies();
}
this.timer = setTimeout(() => { this.getMovieByTitle(currentTerm); }, 300);
}
render() {
return (
<div>
<MovieTopBar />
<SearchBar onSearchTermChange={this.movieSearch}/>
<MovieList movies={ this.state.movies }/>
<MovieFooter />
</div>
)
}
}
if better call getAllMoviesfunction again when termis empty, because you will show the real current movies instead a cache movies version from first call.
I hope it can help you.

Categories

Resources