I've got a simple component that renders a table. The rows are mapped like this:
render () {
return (
{this.state.data.map(function(row, i){
return <Row row={row} key={i}/>
}.bind(this))}
)
}
The state is initialized in the constructor:
this.state = {
data: props.hasOwnProperty('data') ? props.data : [],
route: props.hasOwnProperty('route') ? props.route : null
}
The data can be initialized in the DOM, or after, the route is passed to the container and bound correctly. In this case, I 'm doing it after in the componentDidMount method:
componentDidMount () {
axios.get(this.state.route)
.then(function(resp){
this.setStateParametersFromAjax(resp);
}.bind(this))
.catch(function(err){
console.log(err);
})
}
The setStateParametersFromAjax(resp) method is defined here:
this.setState({
data: resp.data.data,
});
This all works flawlessly on DOM load. However, there are buttons that will perform subsequent requests later on. These requests perform the same axios call.
The problem is, that even though the state is updated (verified by adding a callback as the 2nd argument to the setState method and logging this.state), the DOM does not update.
What do I need to do to make it so that the DOM updates with this new data as well?
Edit
I had simplified the code a bit, there is a method called fetch() that accepts an argument of params that defines the ajax call:
fetch (params) {
if(typeof params != "object") {
params = {};
}
axios.get(this.state.route, {
params
}).then(function(resp) {
this.setStateParametersFromAjax(resp);
}.bind(this))
.catch(function(err){
console.log(err);
})
}
The componentDidMount() method calls this on load:
componentDidmMount () {
this.fetch();
}
When a button is clicked later, this calls a function that calls the fetch method with parameters:
<li className="page-item" onClick={this.getNextPage.bind(this)}>
Where the function is defined:
getNextPage (event) {
event.preventDefault();
this.fetch({
arg: val
});
}
You should use a unique key value other than index. Even the state is updated, react may not update DOM if the key not changed.
https://facebook.github.io/react/docs/reconciliation.html
Related
I'm new to React and got some questions now.
I use componentDidUpdate to fetch data when the button is clicked. I manage the results data with redux. It's seem like componentDidUpdate is async function, because "console.log" always execute first before the fetch function is done. So I always got [] in the console the first time I clicked the button.
Suppose I have 2 actions I want to do when I clicked the button. First fetching the data, after that use that data and pass to another actions. How can I wait until the fetch data done after that the console.log is execute? Please help me.
state = {
searchClick: false,
keyword: "pizza",
};
componentDidUpdate(prevProps, prevState) {
if (
this.state.searchClick &&
prevState.searchClick !== this.state.searchClick
) {
// send get request to fetch data
this.props.searchResults(this.state.keyword);
//Update searchClick state
this.setState({ ...this.state, searchClick: false });
// console log the result data
console.log(this.props.results);
}
}
The console log always executes first because searchResults is async. What you need to do is returning a promise inside searchResults:
const searchResults = (keywords) => {
// fetch with the keywords and return the promise
// or any promise that resolves after your search is completed
return fetch(...);
};
componentDidUpdate(prevProps, prevState) {
if (
this.state.searchClick &&
prevState.searchClick !== this.state.searchClick
) {
// send get request to fetch data
this.props.searchResults(this.state.keyword).then(() => {
//Update searchClick state
this.setState({ ...this.state, searchClick: false });
// console log the result data
console.log(this.props.results);
});
}
}
I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )
How we can call react set state from Sync callback function for example
MyClass.getAynchFunction(inputParam, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
console.log(JSON.stringify(data))
}
}
I want to set variable from 'data' object in react component state.
It's a bit of a guess because you're not specifying what exactly doesn't work, but I'm guessing you're getting something like this.setState is not a function.
That's because this isn't the React component inside your callback function. There's basically two ways to fix this
// Bind the current this to the callback function.
MyClass.getAyncFunction(
"input",
function(err, data) {
this.setState({ responseData: data });
}.bind(this)
);
// Rewrite it as an arrow function so the current this is automatically bound.
MyClass.getAyncFunction("inputParams", (err, data) => {
this.setState({ responseData: data });
});
I would go with the arrow function because it's cleaner.
I have a Post class which will dynamically display content from each post from the database depending on the params. My issue is that I want to use state, but the only time the match.params id value is available is during the render method - at which point I can't use set state because of infinite loops. As a work around I'm using local variables and only setting them in render if match.params is not null. But I ideally want to use state.
Is there a way to grab that param before the render method so i can use set state?
UPDATED TO SHOW CODE:
This bit is here is where I check to see the param and match it with the post from db:
render() {
if(this.state.props.match) {
post = this.props.data.posts.find(post => post.id == this.props.match.params.id );
}
}
Because match.params is only available at this stage, I can't do it when the component first loads as it would be null.
In that post object I have a comments object, and again I would like to assign but can't. So what I'm having to do is assign it to state when I make a POST request (thus a method and not render):
postComment = (e, postId, postComments) => {
e.preventDefault();
let data = {
comment: this.state.comment,
post_id: postId
}
if(this.state.comment != "") {
axios.post('http://127.0.0.1:8000/api/post_comments', data)
.then(response => {
// handle success
console.log(response);
postComments.unshift(response.data);
this.setState({
comments: postComments
});
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
This means that to display the comments, I then have to compare state with the comments object in post. If state is bigger than the other, i.e. a new value has been entered, map that one, else map the other otherwise existing comments won't get displayed:
{
this.state.comments > post.comments ?
this.state.comments.map(comment =>
<p key={comment.id}>{comment.comment}</p>
)
:
post.comments.map(comment =>
<p key={comment.id}>{comment.comment}</p>
)
}
It just feels a bit messy and working with one array in state would be cleaner.
this.state.props.match:
{path: "/blog/:id(\d+)", url: "/blog/2", isExact: true, params: {…}}
isExact: true
params: {id: "2"}
path: "/blog/:id(\d+)"
url: "/blog/2"}
So when clicking on post 2 (blog/2), the above will print, when clicking on 3, id would be 3 and so on
ok, so i had something similar to your problem. In general if your state depends on props, you can try to use getDerivedStateFromProps.
static getDerivedStateFromProps(props, state) {
post = props.data.posts.find(post => post.id == props.match.params.id );
if (post !== state.post) {
//add your magic here
return {
//return a new state....this is like calling setState
};
}
return null; // do nothing
}
I have a parent component that renders a list of children pulled in from an API (which functions correctly). Each child has an option to delete itself. When a child deletes itself, I cannot get the parent to re-render. I have read about 50 answers on here related to this topic and tried all of them and nothing seems to be working. I am missing something and stuck.
The component has redux wired in to it, but I have tried the code with and without redux wired up. I have also tried this.forceUpdate() in the callback, which also does not work (I've commented it out in the example code below).
class Parent extends React.Component {
constructor(props) {
super(props)
this.refresh = this.refresh.bind(this)
this.state = {
refresh: false,
}
}
componentDidMount(){
this.getChildren()
}
refresh = () => {
console.log("State: ", this.state)
this.setState({ refresh: !this.state.refresh })
// this.forceUpdate();
console.log("new state: ", this.state)
}
getChildren = () => {
axios.get(
config.api_url + `/api/children?page=${this.state.page}`,
{headers: {token: ls('token')}
}).then(resp => {
this.setState({
children: this.state.children.concat(resp.data.children)
)}
})
}
render(){
return (
<div>
{_.map(this.state.children, (chidlren,i) =>
<Children
key={i}
children={children}
refresh={() => this.refresh()}
/>
)}
</div>
)
}
}
And then in my Children component, which works perfectly fine, and the delete button successfully deletes the record from the database, I have the following excerpts:
deleteChild = (e) => {
e.preventDefault();
axios.delete(
config.api_url + `/api/children/${this.state.child.id}`,
{headers: {token: ls('token')}}
).then(resp => {
console.log("The response is: ", resp);
})
this.props.refresh();
}
render() {
return(
<button class="btn" onClick={this.deleteChild}>Delete</button>
)}
}
I am sure I am missing something simple or basic, but I can't find it.
Your parent render method depends only on this.state.children which is not changing in your delete event. Either pass in the child id to your this.props.refresh method like this.props.refresh(this.state.child.id) and update this.state.children inside the refresh method or call the get children method again once a delete happens
Code for delete method in child
this.props.refresh(this.state.child.id)
Code for parent refresh method
refresh = (childIdToBeDeleted) => {
console.log("State: ", this.state)
this.setState({ refresh: !this.state.refresh })
// this.forceUpdate();
console.log("new state: ", this.state)
//New code
this.setState({children: this.state.children.filter(child => child.id !== childIdToBeDeleted);
}
Few notes about the code. First removing from db and then reloading might be slow and not the best solution. Maybe consider adding remove() function which can be passed to the children component to update state more quickly.
Second if you want to call setState that depends on previous state it is better to use the callback method like this (but i think you need something else see below)
this.setState((prevState,prevProps) =>
{children: prevState.children.concat(resp.data.children)})
Lastly and what i think the issue is. You are not actually calling getChildren from refresh method so the state is not updated and if you want gonna reload the whole state from db you shouldn't concat but just set it like this
.then(resp => {
this.setState({children: resp.data.children})
}
Hope it helps.
Edit:
As mentioned in the comments the call to refresh from children should be in promise then