Render method doesnt display the state value in React Native - reactjs

I am setting state inside a function which I call in componentDidMount(), but I am not accessing the value of state in the render.
How to access the state inside the render method on time?
state:
constructor() {
super();
this.state = {
check_for_amount: '',
};
}
componentdidmount() :
componentDidMount() {
this.check_amount_left();
}
function:
check_amount_left = () => {
const getSelected = this.props.navigation.state.params;
var ref = firebase.firestore().collection('discounts').where("rest_id", "==", getSelected.rest_id)
ref.onSnapshot((querySnapshot => {
var amount = querySnapshot.docs.map(doc => doc.data().amount);
this.setState({
check_for_amount: amount
});
}));
}
Render:
render() {
return(
<View/>
<Text>
{this.state.check_for_amount}
</Text>
</View>
)
}

You got wrong () at onSnapshot. Please check below to see it works for you. If not, try to log inside onSnapshot to see if it called properly.
check_amount_left = () => {
const getSelected = this.props.navigation.state.params;
var ref = firebase.firestore().collection('discounts').where("rest_id", "==", getSelected.rest_id)
ref.onSnapshot(querySnapshot => {
var amount = querySnapshot.docs.map(doc => doc.data().amount);
this.setState({
check_for_amount: amount
});
});
}

Related

Initialize Empty Class Array and populate from fetch Fetch function React

I am trying to populate a dropdown with values from API. I declared empty array in react class but cannot assign the values to it. I cannot use it as state variables as I have to make lot of changes to previously developed code. The way I did the code it says options is not defined.
The partial code is posted below which is causing the problem. Any help is really appreciated.
export default class LoadLimits extends Component {
constructor(props){
super(props);
this.options = []
this.getZoneOptions = this.getZoneOptions.bind(this)
}
render(){
return (
<PtSelect label="Assigned Zone" options={options}
onChange={this.onChangeDropdown}
disabled={this.props.disabled}
defaultVal={this.state.assignedZone} name="assignedZone" />
)
}
getZoneOptions = () =>{
const zoneOptions = []
const keys = []
fetch(`${config.server}/getzoneOptions/`+this.props.ownModel.agencyId)
.then(response=>
{
return response.json();
})
.then(data=>{
for (var i =0;i<data[0].length;i++){
if (data[0][i]['Zone_key']!==998){
zoneOptions.push(data[0][i]['description'])
keys.push(data[0][i]['Zone_key'])
}
}
let dropOptions = zoneOptions.map((option,idx)=>{
return {key:keys[idx],value: option, label:option}
});
this.options = dropOptions
})
.catch(error=>{
console.log(error);
});
}
}
Issue
The options being passed to PtSelect is not defined.
<PtSelect
label="Assigned Zone"
options={options} // <-- should be this.options
onChange={this.onChangeDropdown}
disabled={this.props.disabled}
defaultVal={this.state.assignedZone}
name="assignedZone"
/>
Solution
If you need a variable to hold a value that you don't want coupled to the React component lifecycle then you should probably use a React ref.
Import createRef from 'react'.
Create a mutable ref for the options.
Implement the componentDidMount lifecycle method to populate and set the current value of the options.
Pass the current options value to the PtSelect component.
Code
import React, { Component, createRef } from 'react';
class LoadLimits extends Component {
constructor(props) {
super(props);
this.getZoneOptions = this.getZoneOptions.bind(this);
this.options = createRef([]);
}
getZoneOptions = () => {
const zoneOptions = [];
const keys = [];
fetch(`${config.server}/getzoneOptions/` + this.props.ownModel.agencyId)
.then((response) => {
return response.json();
})
.then((data) => {
for (var i = 0; i < data[0].length; i++) {
if (data[0][i]["Zone_key"] !== 998) {
zoneOptions.push(data[0][i]["description"]);
keys.push(data[0][i]["Zone_key"]);
}
}
const dropOptions = zoneOptions.map((option, idx) => {
return { key: keys[idx], value: option, label: option };
});
this.options.current = dropOptions;
})
.catch((error) => {
console.log(error);
});
};
componentDidMount() {
this.getZoneOptions();
}
render() {
return (
<PtSelect
label="Assigned Zone"
options={this.options.current}
onChange={this.onChangeDropdown}
disabled={this.props.disabled}
defaultVal={this.state.assignedZone}
name="assignedZone"
/>
);
}
}
Alternative Solution - Use forceUpdate (not strongly suggested)
In addition to addressing the this.options issue in PtSelect, you can use forceUpdate to tell React to rerender regardless of any state and/or prop update. This should rerender the select with populated options.
component.forceUpdate(callback)
By default, when your component’s state or props change, your
component will re-render. If your render() method depends on some
other data, you can tell React that the component needs re-rendering
by calling forceUpdate().
Calling forceUpdate() will cause render() to be called on the
component, skipping shouldComponentUpdate(). This will trigger the
normal lifecycle methods for child components, including the
shouldComponentUpdate() method of each child. React will still only
update the DOM if the markup changes.
Normally you should try to avoid all uses of forceUpdate() and only
read from this.props and this.state in render().
getZoneOptions = () => {
const zoneOptions = [];
const keys = [];
fetch(`https://jsonplaceholder.typicode.com/users`)
.then((response) => {
return response.json();
})
.then((data) => {
for (var i = 0; i < data.length; i++) {
zoneOptions.push(data[i]["name"]);
keys.push(data[i]["id"]);
}
let dropOptions = zoneOptions.map((option, idx) => {
return { key: keys[idx], value: option, label: option };
});
this.options = dropOptions;
console.log("Options ", this.options);
this.forceUpdate(); // <-- trigger a rerender
})
.catch((error) => {
console.log(error);
});
};

Redux - Fetch data, but render in another component

I'm currently fetching data in Component1, then dispatching an action to update the store with the response. The data can be seen in Component2 in this.props, but how can I render it when the response is returned? I need a way to reload the component when the data comes back.
Initially I had a series of functions run in componentDidMount but those are all executed before the data is returned to the Redux store from Component1. Is there some sort of async/await style between components?
class Component1 extends React.Component {
componentDidMount() {
this.retrieveData()
}
retrieveData = async () => {
let res = await axios.get('url')
updateParam(res.data) // Redux action creator
}
}
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
sortData = props => {
const { param } = this.props
let result = param.sort((a,b) => a - b)
}
}
mapStateToProps = state => {
return { param: state.param }
}
connect(mapStateToProps)(Component2)
In Component2, this.props is undefined initially because the data has not yet returned. By the time it is returned, the component will not rerender despite this.props being populated with data.
Assuming updateParam action creator is correctly wrapped in call to dispatch in mapDispatchToProps in the connect HOC AND properly accessed from props in Component1, then I suggest checking/comparing props with previous props in componentDidUpdate and calling sortData if specifically the param prop value updated.
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
componentDidUpdate(prevProps) {
const { param } = this.props;
if (prevProps.param !== param) { // <-- if param prop updated, sort
this.sortData();
}
}
sortData = () => {
const { param } = this.props
let result = param.sort((a, b) => a - b));
// do something with result
}
}
mapStateToProps = state => ({
param: state.param,
});
connect(mapStateToProps)(Component2);
EDIT
Given component code from repository
let appointmentDates: object = {};
class Appointments extends React.Component<ApptProps> {
componentDidUpdate(prevProps: any) {
if (prevProps.apptList !== this.props.apptList) {
appointmentDates = {};
this.setAppointmentDates();
this.sortAppointmentsByDate();
this.forceUpdate();
}
}
setAppointmentDates = () => {
const { date } = this.props;
for (let i = 0; i < 5; i++) {
const d = new Date(
new Date(date).setDate(new Date(date).getDate() + i)
);
let month = new Date(d).toLocaleString("default", {
month: "long"
});
let dateOfMonth = new Date(d).getDate();
let dayOfWeek = new Date(d).toLocaleString("default", {
weekday: "short"
});
// #ts-ignore
appointmentDates[dayOfWeek + ". " + month + " " + dateOfMonth] = [];
}
};
sortAppointmentsByDate = () => {
const { apptList } = this.props;
let dates: string[] = [];
dates = Object.keys(appointmentDates);
apptList.map((appt: AppointmentQuery) => {
return dates.map(date => {
if (
new Date(appt.appointmentTime).getDate().toString() ===
// #ts-ignore
date.match(/\d+/)[0]
) {
// #ts-ignore
appointmentDates[date].push(appt);
}
return null;
});
});
};
render() {
let list: any = appointmentDates;
return (
<section id="appointmentContainer">
{Object.keys(appointmentDates).map(date => {
return (
<div className="appointmentDateColumn" key={date}>
<span className="appointmentDate">{date}</span>
{list[date].map(
(apptInfo: AppointmentQuery, i: number) => {
return (
<AppointmentCard
key={i}
apptInfo={apptInfo}
/>
);
}
)}
</div>
);
})}
</section>
);
}
}
appointmentDates should really be a local component state object, then when you update it in a lifecycle function react will correctly rerender and you won't need to force anything. OR since you aren't doing anything other than computing formatted data to render, Appointments should just call setAppointmentDates and sortAppointmentsByDate in the render function.

how to handle race conditions in class components?

Suppose there is a component where ask server to do some search and response will be rendered. How to ensure most recent request's response is rendered even if server side for any reason answers in different ordering? I'm not asking about cancelling previous request since it's not always possible with reasonable efforts.
onClick = () => {
apiCall(this.state.searchQuery).then(items => this.setState({ items }));
};
Is there elegant way to handle that? By now I know few approaches:
disabling button till request comes(provides bad experiences in large amount of cases - say for searching while typing)
checking inside then() if request's params matches this.props/this.state data(does not handle case when we intentionally forced new search with same query - say by pressing Enter/clicking "Search" button)
onClick = () => {
const searchQuery = this.state.searchQuery;
apiCall(searchQuery)
.then(items =>
this.state.searchQuery === searchQuery
&& this.setState({ items })
);
};
marking requests somehow and checking if it's latest(works, but looks too verboose especially if there are few requests we need to check)
searchQueryIndex = 0;
onClick = () => {
this.searchQueryIndex++;
const index = this.searchQueryIndex;
apiCall(this.state.searchQuery)
.then(items =>
this.searchQueryIndex === searchQueryIndex
&& this.setState({ items })
);
};
I'd call that trio "ugly, broken and messy".
Is there something such clear way as hooks allow:
useEffect(() => {
const isCanceled = false;
apiCall(searchQuery).then(items => !isCanceled && setItems(items));
return () => {isCanceled = true;};
}, [searchQuery])
Your onClick handler suggest a class component since you use this and this.setState:
onClick = () => {
apiCall(this.state.searchQuery).then(items =>
this.setState({ items })
);
};
I adjusted onlyLastRequestedPromise to take a function that will return something (you can return Promise.reject('cancelled') or anything).
const onlyLastRequestedPromise = (promiseIds => {
const whenResolve = (
promise,
id,
promiseID,
resolveValue,
whenCancelled = () => Promise.reject('cancelled')
) => {
if (promise !== undefined) {
//called by user adding a promise
promiseIds[id] = {};
} else {
//called because promise is resolved
return promiseID === promiseIds[id]
? Promise.resolve(resolveValue)
: whenCancelled(resolveValue);
}
return (function(currentPromiseID) {
return promise.then(function(result) {
return whenResolve(
undefined,
id,
currentPromiseID,
result
);
});
})(promiseIds[id]);
};
return (id = 'general', whenCancelled) => promise =>
whenResolve(
promise,
id,
undefined,
undefined,
whenCancelled
);
})({});
A class example on how to use it:
class Component extends React.Component {
CANCELLED = {};
last = onlyLastRequestedPromise(
'search',
() => this.CANCELLED
);
onSearch = () => {
this.last(apiCall(this.state.searchQuery)).then(
items =>
items !== this.CANCELLED && this.setState({ items })
);
};
changeAndSearch = e => {
this.setState(
{}, //state with new value
() => this.onSearch() //onSearch after state update
);
};
render() {
return (
<div>
<SearchButton onClick={this.onSearch} />
<Other onChange={this.changeAndSearch} />
</div>
);
}
}
I agree it's a lot of code but since you put most of the implementation in the lib it should not clutter your components.
If you had a functional component you could create the last function with useRef:
//
function ComponentContainer(props) {
const CANCELLED = useRef({});
const last = useRef(
onlyLastRequestedPromise('search', () => CANCELLED)
);
const [searchQuery,setSearchQuery] = useState({});
const mounted = useIsMounted();
const onSearch = useCallback(
last(apiCall(searchQuery)).then(
items =>
items !== CANCELLED &&
mounted.current &&
//do something with items
)
);
}
Finally figured out how to utilize closure to mimic "just ignore that" approach from hooks' world:
class MyComponent extends React.Component {
const ignorePrevRequest = () => {}; // empty function by default
loadSomeData() {
this.ignorePrevRequest();
let cancelled = false;
this.ignorePrevRequest = () => { cancelled = true; }; // closure comes to play
doSomeCall().then(data => !cancelled && this.setState({ data }))
}
}

how to render object array in react?

here is my componentDidmount
componentDidMount() {
for ( var i in course ) {
let title = course[i];
const ref = firestore.collection('courses');
ref.where("class", "array-contains", course[i]).get()
.then(querySnapshot => {
const count = querySnapshot.size
course_stats.push({
title: title,
value: count,
});
});
}
console.log(course_stats)
this.setState({
courses: course_stats,
})
}
here is my render
render() {
const { classes } = this.props;
if (this.state.courses) {
console.log(this.state.courses)
return (
<ul>
{course_stats.map(d => <li key={d.title}>{d.title}</li>)}
</ul>
)
}
on the line console.log, I can see the object array in that. However, when i try render that, it doesn't show anything.
this is the console.log capture
how can I render the title and value of array?
Thank you!
Adding to izb's answer, this.setState has already executed, so you should use async/await, or add a seperate callback function like this that returns a Promise
setAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}
handleChange = (event) => {
return this.setAsync({[event.target.name]: event.target.value})
}

React: dynamic GET URL in ComponentDidMount

I'm trying to update the state 'allMovieList' to render a list of movies.
The idea was to set a dynamic URL in my GET request, by updating the 'page' state when clicking on the button. Unfortunately this doesn't trigger a re-rendering since the request is made only one time in componentDidMount() method.
state = {
allMovieList: [],
page: 1
}
componentDidMount() {
this.changePage();
}
async changePage() {
try {
const response = await axios.get(`https://api.themoviedb.org/4/discover/movie?api_key=${apiKey}&page=${this.state.page}`);
const movieList = response.data.results.slice(0, 10);
const movies = movieList.map(movie => movie);
const totalPages = response.data.total_pages;
this.setState({
...this.state,
allMovieList: movies,
})
} catch (event) {
console.log(event);
}
}
onNextPage = () => {
this.setState((previousState, currentProps) => {
return { page: previousState.page + 1 };
});
}
render() {
return (
<div className='MovieList'>
...
<button onClick={this.onNextPage}></button>
</div>
);
}
To solve this, I tried to call the changePage() function inside my onNextPage() function.
onNextPage = () => {
this.setState((previousState, currentProps) => {
return { page: previousState.page + 1 };
});
this.changePage();
}
This partially solved this issue but for some reason the next page is actually only rendered on second click! I also noticed my component is being re-rendered twice on every click.
I also tried to call the changePage() function inside componentDidUpdate(), this solved the issue but now my app is constantly re-rendering which causes huge performance issues.
Can anyone help me with this? It would be greatly appreciated!
Option 1) Use setState's callback function:
this.setState((previousState, currentProps) => {
return { page: previousState.page + 1 };
}, this.changePage); // state will be updated when this gets called
Option 2) Use function arguments:
async changePage(page) {
try {
const response = await
axios.get(`https://api.themoviedb.org/4/discover/movie?page=${page}`);
...
}
}
...
onNextPage = () => {
this.setState((previousState, currentProps) => {
const page = previousState.page + 1
this.changePage(page);
return { page };
});
}

Resources