with redux, we uses actions to handle with crud operations. But I stuck at some points. If we send async requests inside of component. We can easly handle with response. But when we send request through actions, we dont know what happened. Is request send successfully ? it took how much amount of time ? What kind of response is returned ? we don't know that
I will clarify question with samples..
lets update a post.
onClick () {
postsApi.post(this.state.post) | we know how much time
.then(res => res.data) | has took to execute
.then(res => { | request
console.log(res) // we have the response
})
.catch(err => console.log(error))
}
But if we use actions
onClick () {
this.props.updatePost(this.state.post) // we know nothing what will happen
}
or handling with incoming props. lets say I have fetchPost() action to retrieve post
componentDidMount(){
this.props.fetchPost()
}
render method and componentDidUpdate will run as well. It's cool. But what if I want to update my state by incoming props ? I can't do this operation inside of componentDidUpdate method. it causes infinity loop.
If I use componentWillUpdate method, well, things works fine but I'm getting this warning.
Warning: componentWillReceiveProps has been renamed, and is not
recommended for use. Move data fetching code or side effects to
componentDidUpdate. If you're updating state whenever props change,
refactor your code to use memoization techniques or move it to static
getDerivedStateFromProps
I can't use componentDidUpdate method for infinty loop. Neither getDerivedStateFromProps method because it's run everytime when state change.
Should I continue to use componentWillMethod ? Otherwise what should I use and why (why componentWillMethod is unsafe ?)
If I understand correcty, what you would like to do is to safely change your local state only when your e.g. updatePost was successful.
If indeed that is your case, you can pass a callback function on your updatePost and call this as long as your update was succefull.
successfulUpdate() {
// do your thing
this.setState( ... );
}
onClick () {
this.props.updatePost(this.state.post, this.successfulUpdate) // we know nothing what will happen
}
UPDATE:
You can also keep in mind that if your action returns a promise, then you can just use the then method:
onClick () {
this.props.updatePost(this.state.post).then(this.onFulfilled, this.onRejected)
}
I think we can use redux-thunk in this cases. What if we dispatch an async function instead of dispatch an action object?
"Neither getDerivedStateFromProps method because it's run everytime when state change." - does it matter? You can avoid setting state with every getDerivedStateFromProps call by using a simple condition inside.
Example:
static getDerivedStateFromProps(props, state) {
if (props.post !== state.post) { // or anything else
return {
post: props.post,
};
}
return null;
};
An infinite loop will not occur.
Here is my way for such cases. We can redux-thunk for asynchronous calls such as api call. What if we define the action that returns promise? Please check the code below.
actions.js
export const GetTodoList = dispatch => {
return Axios.get('SOME_URL').then(res => {
// dispatch some actions
// return result
return res.data;
});
}
TodoList.js
onClick = async () => {
const { GetTodoList } = this.props;
try {
const data = await GetTodoList();
// handler for success
this.setState({
success: true,
data
});
} catch {
// handler for failure
this.setState({
success: fail,
data: null
});
}
}
const mapStateToProps = state => ({
GetTodoList
});
So we can use actions like API(which returns promise) thanks to redux-thunk.
Let me know your opinion.
Related
I have a question for React/Redux.
I am making a web application. And it works perfectly in the way I want it to work without any problems. Even though I don't understand it. The part I don't understand is this part.
This is my code.
const myApps = () => {
let myInfo = {};
const form = [];
form.push(<div>what</div>);
myInfo = form.reduce((prev, element, index) => {
prev[index] = {isTrue : false};
return prev;
}, {});
}
useEffect(() => {
uploadToRedux(myInfo); // this is redux dispatch. I upload to redux "only" here.
}, []);
myInfo[0].isTrue = true;
if (myInfo[0].isTrue) console.log('Correct!');
else console.log('Incorrect!');
I'm sending myInfo to Redux only if it's componentDidMount by using useEffect. By the way, When I switch myInfo[0].isTrue = true, the state in the redux changes simultaneously. Even though I didn't send a dispatch!
Of course, this is what I want, but is there any change in the state in redux even if I didn't send the dispatch? How could this be?
Notice that you are passing a function to useEffect() as callback.
() => {
uploadToRedux(myInfo); // this is redux dispatch. I upload to redux "only" here.
}
useEffect() will execute immediately but the anonymous function (callback) will be scheduled to be executed by JS runtime (i.e. ready to be executed but in fact not executed yet).
By the time the callback is about to execute and update values in redux, JS runtime will already have executed the lines after useEffect() i.e. the following lines of code will be executed before redux update:
myInfo[0].isTrue = true;
if (myInfo[0].isTrue) console.log('Correct!');
else console.log('Incorrect!');
So when JS runtime will execute following line:
uploadToRedux(myInfo);
Value of myInfo[0].isTrue will already be updated and same will go to redux.
The phenomena is called Callback Hell. To know more about it, read this: http://callbackhell.com/
To be sure, log the value of myInfo[0].isTrue before redux update
useEffect(() => {
console.log("myInfo[0].isTrue", myInfo[0].isTrue);
uploadToRedux(myInfo); // this is redux dispatch. I upload to redux "only" here.
}, []);
As I know, you need to dispatch yourself when you request a server (API) to return data to you. So it needs to be async .but when it's not async you just pass the data to store and it will dispatch itself.
So whenever your data comes back after a delay it's an async request and you need dispatch data when you received data. (if you don't you may get an error because redux dispatch an undefined data)
I hope it be helpful to you.
here's a brief overview of my component OneRoadmap: in async componentDidMount, I am first calling "await this.props.getRoadmaps", which is an action in my Redux, this action will send a get request to my api which will retrieve items from my database, then, from those items retrieved, we send a dispatch to the Reducer, calling GET_ROADMAPS, this is done like
export const getRoadmaps = () => dispatch => {
dispatch(setRoadmapsLoading());
axios
.get("/api/roadmaps")
.then(res => dispatch({ type: GET_ROADMAPS, payload: res.data }));
};
and my component looks like:
async componentDidMount() {
await this.props.getRoadmaps();
var location1 = this.props.location.pathname;
var n = location1.slice(9);
var current_roadmaps = this.props.roadmap.roadmaps;
this.displayRoadmap = current_roadmaps.filter(
eachRoadmap => eachRoadmap._id == n
);
// now we have a roadmap
this.setState({
treeData: this.displayRoadmap[0].roadmap[0],
loading: false
});
}
GET_ROADMAPS will update my Redux state.
The problem appears to be: await this.props.getRoadmaps() will only wait until getRoadmaps() sends the dispatch to GET_ROADMAPS, which means it doesn't wait until GET_ROADMAPS update the redux state, this means few lines later, when I do this.props.roadmap.roadmaps, it is going to be undefined because this.props.roadmap.roadmaps is probably called before the GET_ROADMAPS finish updating my redux state.
Please guide me if there are any ways to solve to problem :) and please correct me if the problem is not what I think it is.
P.S. I referenced https://www.robinwieruch.de/react-fetching-data, this info on there will usually work but it appears to be that since I have an extra dispatch to my redux store, the dispatch updates the store after I call this.props.roadmap.roadmaps, which means I get undefined and can't use that variable in my rendering
It seems you’re not returning in your action creator. You have to explicitly return when you create a block (use curly braces).
Return your axios call and it should work properly.
Facing an issue where state is not updated, before function (filterAndSort) located in render is fired. I have added a console.log statement within the function, and only one of the states gets updated. My code seems logical to me, as I have set an if condition where function is fired only after setState occurs
Full error message:
Warning: Can't call setState (or forceUpdate) on an unmounted
component. This is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asynchronous tasks
in the componentWillUnmount method.
However, i'm suspecting that because i'm having multiple setStates happening asynchronously.
I'm thinking that perhaps, i need to re-write the componendDidMount to setState for all variables only after all axios requests are fetched.
Multiple Axios Requests Into ReactJS State
Another solution I think would be to having the returned results of the function stored as a state instead of a variable, then add a componentDidUpdate.
componentDidUpdate(prevProps, prevState) {
if (this.state.value > prevState.value) {
this.filterAndSort();
}
}
Component
class Results extends Component {
constructor(){
super()
this.state = {
results: [],
races: [],
arr = []
isLoading: true
};
}
componentDidMount(){
const oneRequest = axios.get(URL_ONE)
.then(response =>
response.data.data.map(result => ({...
))
)
.then(results => this.setState({results, isLoading: false}))
const twoRequest = axios.get(URL_TWO)
.then(response =>
response.data.data.map(race => ({...}))
)
.then(races => this.setDefault(races))
}
setDefault = (races) => {
........
this.setState({arr, races, isLoading:false})
}
filterAndSort = (races, results) => {
console.log(races, results)
.......
}
render() {
const{races, results} = this.state
if (isLoading == true) {
return (
<div>
<p>Loading...</p>
</div>
)
} else {
return (
<div>
<BarChart
qualData={this.filterAndSort(races, results)}
raceData={this.filterAndSort(races, results)}
width="1200"
height="500" />
</div>
);
}
}
}
export default Results;
Well, there are a number of things that come to my mind.
First, you set the isLoading to false only when you have one of two pieces of data, whichever comes first, thus the render method will at some point call your function with either empty races or results.
Another thing, you are asynchronously calling setState. By the time the request is finished your component may not exist any more thus it will try to update a non existing component and fail with that error.
For the first issue, one possible solution is to have two isLoading-variables for both results and races.
For the second issue (setState called on an unmountedComponent), its a bit more complicated, because you need to somehow cancel the request. I recommend reading more on this, the general advice is to move your data out of the components using a library like redux. If you google cancel promise on unmount you will find discussion on this. You can also deal with it using an "isMounted" variable, which will work as an ugly patch.
So, as soon as request one (or two) is completed, setState is called, the component is then re-rendered. The isLoading is now true so filterAndSort is called with results (or races) but not both because the second request is still pending.
Finally, in your render method your isLoading needs to be first defined (i assume its ok in your code but not in the question) and the is True comparison can be better put as
if (isLoading) { instead of if (isLoading == True) {
You're right. When making multiple requests its always best practice to wait for them all to resolve before moving forward. For that you can use the Promise library which is built into ES6. Also, for fetching of data its best practice, so far as i've seen, to do it in componentWillMount(). I'll also add that the context of this changes when inside an async function. So in your componentWillMount():
let promiseArr = [axios.get(URL_ONE), axios.get(URL_TWO)];
let _this = this;
Promise.all(promiseArr)
.then((resp) => {
// Call _this.setState() here. Response from URL_ONE
// will be available in resp.data[0] and URL_TWO in resp.data[1]
}).catch((err) => {
console.log(err);
})
Also, in your constructor:
constructor(){
super()
this.state = {
results: [],
races: [],
arr = []
isLoading: true
};
this.setDefault = this.setDefault.bind(this);
this.filterAndSort = this.filterAndSort.bind(this);
}
Using .bind to make sure that the context of this is referrering to the current instance of the class when calling those methods.
I think after doing those things that error will go away. Hope that helps.
I have a function in my ReactJS app that sends an axios POST request to the server to delete a certain element from my database.
Basically, I have a list and I can delete certain items from it, however, React only displays the changes made by deleting an element after refreshing the page.
Here is the delete function that I use:
handleDelete (event) {
var id = event.target.id;
axios.get('todos/delete?id='+id)
.then(function(response) {
});
this.componentDidMount();
}
componentDidMount() fetches data from my database and stores it in the state.
I found that, if I call componentDidMount within the function, that it displays the changes right away, however, I kind of feel like that's a rather unprofessional way of doing what I'm trying to achieve.
Therefore my questions are:
Is it considered bad practice to call a lifecycle method within another function?
Is there a better way to get the page to display the changes right away?
Well you shouldn't really do that.
componentDidMount is just a lifecycle method of your component. What you want is this structure:
fetchData () { ... };
handleDelete (event) {
var id = event.target.id;
axios.get('todos/delete?id='+id)
.then(function(response) {
});
this.fetchData();
}
componentDidMount() {
this.fetchData();
}
This is a simplified example but you get the point.
NOTE: in the handleDelete function if you want the fetchData call to happen after the axios call your should but the code inside the then
axios.get('todos/delete?id='+id)
.then(() => {
this.fetchData();
});
Answer 1- No it is recommended to call lifecycle method in any function.
Answer 2- better recommednded way , call all server function in ComponentDidMount
for More refrence check here
You can do your operation in this way :-
componentDidMount(){
// do some get opration to get data from server
getFunction();
}
handleDelete (event) {
var id = event.target.id;
axios.get('todos/delete?id='+id)
.then(function(response) {
// get operation here in response
});
}
Indeed it's a bad practice. You can either use React lifecycle to update your state or directly within the handleDelete function.
handleDelete = ({ target }) => {
const { id } = target;
axios.get('todos/delete?id=='+id')
.then(response => {
const todos = this.state.todos;
todos.splice(indexOfTheItem,1);
this.setState({ todos });
})
I have an App component that is responsible for rendering child input components, it is also responsible for handling fetch requests to the Twitch API via a method called channelSearch. I have tried to adhere to suggested best practices outlined here for working with ajax/fetch with React.
The method is passed down through props and called via a callback.
Note the fetch method is actually isomorphic-fetch.
channelSearch (searchReq, baseUrl="https://api.twitch.tv/kraken/channels/") {
fetch(baseUrl + searchReq)
.then(response => {
return response.json();
})
.then(json => {
this.setState({newChannel:json});
})
.then( () => {
if (!("error" in this.state.newChannel) && this.channelChecker(this.state.newChannel._id, this.state.channelList) ) {
this.setState(
{channelList: this.state.channelList.concat([this.state.newChannel])}
);
}
})
.catch(error => {
return error;
});
}
I am currently trying to write a test for the channelSearch method. I am currently using enzyme and jsdom to mount the entire <App> component in a DOM. Find the child node with the callback, simulate a click (which should fire the callback) and check to see if the state of the component has been changed. However, this does not seem to work.
I have also tried calling the method directly, however, I run into problems with this.state being undefined.
test('channel search method should change newChannel state', t => {
const wrapper = mount(React.createElement(App));
wrapper.find('input').get(0).value = "test";
console.log(wrapper.find('input').get(0).value);
wrapper.find('input').simulate("change");
wrapper.find('button').simulate("click");
console.log(wrapper.state(["newChannel"]));
});
I am really lost, I am not sure if the method itself is poorly written or I am not using the correct tools for the job. Any guidance will be greatly appreciated.
Update #1:
I included nock as recommended in comments, test now looks like this:
test('channel search method should change newChannel state', t => {
// Test object setup
var twitch = nock('https://api.twitch.tv')
.log(console.log)
.get('/kraken/channels/test')
.reply(200, {
_id: '001',
name: 'test',
game: 'testGame'
});
function checker() {
if(twitch.isDone()) {
console.log("Done!");
console.log(wrapper.state(["newChannel"]));
}
else {
checker();
}
}
const wrapper = mount(React.createElement(App));
wrapper.find('input').get(0).value = "test";
wrapper.find('input').simulate("change");
wrapper.find('button').simulate("click");
checker();
});
This still does not seem to change the state of the component.
fetch is asynchronous but you're testing synchronously, you need to either mock fetch with a synchronous mock or make the test asynchronous.
nock may work for you here.
I suggest you create a sample of your test using plnkr.
I agree with Tom that you're testing synchronously. It would of course be helpful to show off your actual component code (all of the relevant portions, like what calls channelSearch, or at the least describe it by saying e.g. "channelSearch is called by componentDidMount()". You said:
I run into problems with this.state being undefined.
This is because this.setState() is asynchronous. This is for performance reasons, so that React can batch changes.
I suspect you'll need to change your code that is currently:
.then(json => {
this.setState({newChannel:json});
})
to:
.then(json => {
return new Promise(function(resolve, reject) {
this.setState({newChannel:json}, resolve);
})
})
Note that your checker() method won't work. It's looping, but twitch.isDone() will never be true because it never has a chance to run. Javascript is single threaded, so your checker code will run continuously, not allowing anything else in between.
If you set up the plnkr, I'll take a look.
Refactor out the fetch code from the component then pass it it to the component as a callback function in the properties.
export class Channel extends React.Component {
componentDidMount() {
this.props.searchFunction().then(data => this.setState(data));
}
render() {
return <div>{this.state}</div>;
}
}
Uage:
function channelSearch(name) {
return fetch(`https://api.twitch.tv/kraken/search/channels?query=${name}`);
}
<Channel searchFunction={channelSearch} />
Now you can test the API functionality independently of the component.