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})
}
Related
I am trying to delete multiple items on click of checkbox using firestore. But, onSnapshot method of firestore is causing issue with the state.
After running the code I can click on checkbox and delete the items, the items get deleted too but I get an error page, "TyperError: this.setState is not a function" in onCollectionUpdate method.
After refreshing the page I can see the items deleted.
Here's my code:
class App extends React.Component {
constructor(props) {
super(props);
this.ref = firebase.firestore().collection('laptops');
this.unsubscribe = null;
this.state = { laptops: [], checkedBoxes: [] };
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.deleteProducts = this.deleteProducts.bind(this);
}
toggleCheckbox = (e, laptop) => {
if (e.target.checked) {
let arr = this.state.checkedBoxes;
arr.push(laptop.key);
this.setState = { checkedBoxes: arr };
} else {
let items = this.state.checkedBoxes.splice(this.state.checkedBoxes.indexOf(laptop.key), 1);
this.setState = {
checkedBoxes: items
}
}
}
deleteProducts = () => {
const ids = this.state.checkedBoxes;
ids.forEach((id) => {
const delRef = firebase.firestore().collection('laptops').doc(id);
delRef.delete()
.then(() => { console.log("deleted a laptop") })
.catch(err => console.log("There is some error in updating!"));
})
}
onCollectionUpdate = (querySnapshot) => {
const laptops = [];
querySnapshot.forEach((doc) => {
const { name, price, specifications, image } = doc.data();
laptops.push({
key: doc.id,
name,
price,
specifications,
image
});
});
this.setState({ laptops });
console.log(laptops)
}
componentDidMount = () => {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}
getLaptops = () => {
const foundLaptops = this.state.laptops.map((laptop) => {
return (
<div key={laptop.key}>
<Container>
<Card>
<input type="checkbox" className="selectsingle" value="{laptop.key}" checked={this.state.checkedBoxes.find((p) => p.key === laptop.key)} onChange={(e) => this.toggleCheckbox(e, laptop)} />
...carddata
</Card>
</Container>
</div>
);
});
return foundLaptops;
}
render = () => {
return (
<div>
<button type="button" onClick={this.deleteProducts}>Delete Selected Product(s)</button>
<div className="row">
{this.getLaptops()}
</div>
</div>
);
}
}
export default App;
In the toggleCheckbox function you set the this.setState to a object.
You will need to replace that with this.setState({ checkedBoxes: items})
So you use the function instead of setting it to a object
You probably just forgot to bind the onCollectionUpdate so this referes not where you expectit to refer to.
Can you pls also change the this.setState bug you have there as #David mentioned also:
toggleCheckbox = (e, laptop) => {
if (e.target.checked) {
let arr = this.state.checkedBoxes;
arr.push(laptop.key);
this.setState({ checkedBoxes: arr });
} else {
let items = this.state.checkedBoxes.splice(this.state.checkedBoxes.indexOf(laptop.key), 1);
this.setState({
checkedBoxes: items
})
}
}
If you already did that pls update your question with the latest code.
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
});
});
}
I have a function to update the state and call another function to
update object value in the setState callback method.
I also added a debugger on the breakpoint for the setState callback
method, what I observe is that the value always is the old one.
updateContactPath(path, index) {
const { contactPaths } = this.state;
const { setFieldValue } = this.props;
contactPaths[index] = path;
this.setState(
{
contactPaths,
},
() => setFieldValue('contactPaths', contactPaths),
);
}
We can do something like this to ensure updated state -:
updateContactPath(path, index) {
const { contactPaths } = this.state;
const { setFieldValue } = this.props;
this.setState(
{
[...contactPaths, [index]: path],
},
() => setFieldValue('contactPaths', this.state.contactPaths),
);
}
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 }))
}
}
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 };
});
}