Handle async actions that do not store result in state - javascript

I stumbled upon a requirement on a section of a vanilla JS webapp that requires a single JSON "definitions" object to render. Definitions are loaded via an HTTP request at the very beginning, read, parsed and handed down to another layer of the app. The object itself never changes throughout its life cycle.
I'm now trying to model this scenario in ReactJS, using Redux + redux-thunk. I created a thunk/async action the fetches the JSON object, extracts what it needs and ends up updating the state with that -
but it does not store the object itself in the state. This seems like the right, logical approach since, as mentioned, the definitions are never modified in any way. I'd argue it's simply not state, in a strict sense.
However, by taking that decision I ended up struggling while implementing the actual React.Component. Almost every single example I've seen out there in the wild for async cases like this one:
Defines a thunk action that fires some API call.
Stores whatever they got back (or after some alterations) in a state property.
Maps that property to this.props in the Component with mapStateToProps and connect.
In my case, I don't really have a state property to bind to. So I ended up returning the definitions object in my async action and using the component's local state to get what I needed.
class ContainerComponent extends React.Component {
state = { definitions: {} };
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchDefinitions())
.then((definitions) => this.setState({ definitions }));
}
render() {
return (<PresentationalComponent definitions={this.state.definitions} />);
}
}
export default connect()(ContainerComponent);
Not saying that this.setState should be avoided, but this looks an awful lot like what I had before even introducing Redux: an API call returning a promise - only with a lot more meddling indirections.
componentDidMount() {
const { dispatch } = this.props;
fetch(`${API_URL}/definitions`)
.then((res) => res.json())
.then((definitions) => this.setState({ definitions }));
}
So, how should I go about this? Is there any particular thing I am missing here? Any pattern I should be following? Perhaps, avoiding Redux entirely for this matter?

You are right in that having a component state isn't necessarily a bad thing, but I believe you are confused on where to store that data once the API call is made.
You mention that it is not necessarily state, but I would argue otherwise. Prior to making the API call, your application does not have that data. You may have certain UX/UI indications at the start up of your application that, for example could indicate on if the data is being fetched: definitions.all.isFetching.
In your componentDidMount, dispatching the action to fetch the data is correct. Once the action is fired, and the success response is received, your reducer should save the definitions to your redux store like
import { assign, get } from 'lodash';
const all = (
state = { isFetching: false, data: [] },
action,
) => {
switch (action.type) {
case types.LIST_DEFINITIONS:
return assign({}, state, { isFetching: true });
case types.LIST_DEFINITIONS_SUCCESS:
return assign({}, state, get(action, 'result.data'), { isFetching: false });
default: return state;
}
};
Then in your component, you would connect your redux store
function mapStateToProps(state){
return {
definitions: state.definitions.all.data
};
}
export default connect(mapStateToProps, { listDefinitions })(ContainerComponent);
Also note I moved the action out in my example and am placing it into the connect with mapDispatchToProps shorthand.

Related

How can I fetch backend data in child component and then retrieve an object from store?

My parent component <Room/> build children components <RoomSensor/>, when parent build these children I also send to the <RoomSensor/> uuid, by this uuid I fetch sensor data from a backend.
Store is array of objects.
// Parent <Room/>
return props.sensors.map((uuid, index) => {
return <RoomSensor key={index} uuid={uuid}/>
})
// Children <RoomSensor/>
const RoomSensor = props => {
useEffect(() => {
console.log("useEffect")
props.fetchSensor(props.uuid)
}, [props.uuid])
console.log(props.sensor)
return (
<div className={"col-auto"}>
<small><b>{props.sensor.value}</b></small>
</div>
)
}
let mapStateToProps = (state, props) => {
return {
sensor: filterSensor(state, props.uuid)
}
}
let mapDispatchToProps = {
fetchSensor,
}
export default connect(mapStateToProps, mapDispatchToProps)(RoomSensor)
// Selectors
export const getSensor = (state, uuid) => {
return _.filter(state.sensors, ["uuid", uuid])[0]
}
export const filterSensor = createSelector(
getSensor,
(sensor) => sensor
)
And I cannot understand two things:
When I do refresh I get.
TypeError: Cannot read property 'uuid' of undefined
I understand that there is no data in the state yet, that's why such an error occurs. Is it possible not to render the component until the data comes from the server?
If I comment <small><b>{props.sensor.value}</b></small> no errors occur, data appears in the store, then I uncomment this line and voila everything works. But in the console I see too many component rerende. What am I doing wrong? Is there something wrong with the selector?
In general, I want each sensor component to render independently of the others.
The following is based on a few assumptions derived from the shared code and output:
Currently, there's a hard-coded list of 4 sensor UUIDs.
createSelector is from the reselect package.
_ references an import of the lodash package.
"Is it possible not to render the component until the data comes from the server?"
The short answer to this is yes. There're several approaches to achieve this, so you'll want to evaluate what fits best with the structure of the app. Here're 2 approaches for consideration:
Retrieve the list of sensors from the server. Initialize the store with an empty list and populate it upon getting data back from the server.
In getSensor, return a default value if the uuid isn't in the list.
Either way, I'd recommend adding default state to the store. This will help reduce the amount of code required to handle edge cases.
Here's a rough example of what the new reducer and selector for (1) might look like:
export const storeReducer = (state, action) => {
let nextState = state;
if (!state) {
// State is uninitialized, so give it a default value
nextState = {
sensors: [],
};
}
switch (action.type) {
case 'RECEIVE_SENSORS':
// We received sensor data, so update the value in the store
nextState = {
...nextState,
sensors: action.sensors,
};
break;
default:
break;
}
return nextState;
};
export const getSensors(state) {
return state.sensors;
}
The action upon receiving the data from the server, could look something like:
dispatch({
sensors,
type: 'RECEIVE_SENSORS',
})
"...in the console I see too many component rerende[rs]"
Without additional context, it's hard to say exactly why the re-renders are happening, but the most likely cause is that each call to props.fetchSensor(props.uuid) changes the data in the store (e.g. if it's overwriting the data).
From the console output you shared, we see that there're 16 re-renders, which would happen because:
Each of the 4 instances of RoomSensor calls fetchSensor
This results in 4 updates to the store's state
Each update to the store's state causes React to evaluate each instance of RoomSensor for re-render
Hence, 4 state updates x 4 components evaluated = 16 re-renders
React is pretty efficient and if your component returns the same value as the previous run, it knows not to update the DOM. So, the performance impact probably isn't actually that significant.
That said, if the above theory is correct and you want to reduce the number of re-renders, one way to do it would be to check whether the data you get back from the server is the same as what's already in the store and, if so, skip updating the store.
For example, fetchSensor might be updated with something like:
const existingData = getSensor(getState(), uuid);
const newData = fetch(...);
// Only update the store if there's new data or there's a change
if (!existingData || !_.isEqual(newData, existingData)) {
dispatch(...);
}
This would require updating getSensor to return a falsey value (e.g. null) if the uuid isn't found in the list of sensors.
One additional tip
In Room, RoomSensor is rendered with its key based on the item's index in the array. Since uuid should be unique, you can use that as the key instead (i.e. <RoomSensor key={uuid} uuid={uuid} />). This would allow React to base updates to RoomSensor on just the uuid instead of also considering the order of the list.

simplify redux with generic action & reducer

In React-Redux project, people usually create multiple actions & reducers for each connected component. However, this creates a lot of code for simple data updates.
Is it a good practice to use a single generic action & reducer to encapsulate all data changes, in order to simplify and fasten app development.
What would be the disadvantages or performance loss using this method. Because I see no significant tradeoff, and it makes development much easier, and we can put all of them in a single file! Example of such architecture:
// Say we're in user.js, User page
// state
var initialState = {};
// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// define component
var User = React.createClass({
render: function(){
// Here's the magic...
// We can just call the generic setState() to update any data.
// No need to create separate dispatchers and reducers,
// thus greatly simplifying and fasten app development.
return [
<div onClick={() => setState({ someField: 1 })}/>,
<div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
<div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
]
}
});
// register component for data update
function mapStateToProps(state){
return { ...state.user };
}
export default connect(mapStateToProps)(User);
Edit
So the typical Redux architecture suggests creating:
Centralized files with all the actions
Centralized files with all the reducers
Question is, why a 2-step process? Here's another architectural suggestion:
Create 1 set of files containing all the setXField() that handle all the data changes. And other components simply use them to trigger changes. Easy. Example:
/** UserAPI.js
* Containing all methods for User.
* Other components can just call them.
*/
// state
var initialState = {};
// generic action
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// API that we export
let UserAPI = {};
// set user name
UserAPI.setName = function(name){
$.post('/user/name', { name }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ name });
});
};
// set user picture URL
UserAPI.setPicture = function(url){
$.post('/user/picture', { url }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ url });
});
};
// logout, clear user
UserAPI.logout = function(){
$.post('/logout', {}, function(){
setState(initialState);
});
};
// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods,
// like some helper methods unrelated to Redux, or Ajax getters.
// Now you have everything related to User available in a single file!
// It becomes much easier to read through and understand.
// Finally, you can export a single UserAPI object, so other
// components only need to import it once.
export default UserAPI
Please read through the comments in the code section above.
Now instead of having a bunch of actions/dispatchers/reducers. You have 1 file encapsulating everything needed for the User concept. Why is it a bad practice? IMO, it makes programmer's life much easier, and other programmers can just read through the file from top to bottom to understand the business logic, they don't need to switch back and forth between action/reducer files. Heck, even redux-thunk isn't needed! And you can even test the functions one by one as well. So testability is not lost.
Firstly, instead of calling store.dispatch in your action creator, it should return an object (action) instead, which simplifies testing and enables server rendering.
const setState = (obj) => ({
type: 'SET_USER',
data: obj
})
onClick={() => this.props.setState(...)}
// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)
You should also use ES6 class instead of React.createClass.
Back to the topic, a more specialised action creator would be something like:
const setSomeField = value => ({
type: 'SET_SOME_FIELD',
value,
});
...
case 'SET_SOME_FIELD':
return { ...state, someField: action.value };
Advantages of this approach over your generic one
1. Higher reusability
If someField is set in multiple places, it's cleaner to call setSomeField(someValue) than setState({ someField: someValue })}.
2. Higher testability
You can easily test setSomeField to make sure it's correctly altering only the related state.
With the generic setState, you could test for setState({ someField: someValue })} too, but there's no direct guarantee that all your code will call it correctly.
Eg. someone in your team might make a typo and call setState({ someFeild: someValue })} instead.
Conclusion
The disadvantages are not exactly significant, so it's perfectly fine to use the generic action creator to reduce the number of specialised action creators if you believe it's worth the trade-off for your project.
EDIT
Regarding your suggestion to put reducers and actions in the same file: generally it's preferred to keep them in separate files for modularity; this is a general principle that is not unique to React.
You can however put related reducer and action files in the same folder, which might be better/worse depending on your project requirements. See this and this for some background.
You would also need to export userReducer for your root reducer, unless you are using multiple stores which is generally not recommended.
I mostly use redux to cache API responses mostly, here are few cases where i thought it is limited.
1) What if i'm calling different API's which has the same KEY but goes to a different Object?
2) How can I take care if the data is a stream from a socket ? Do i need to iterate the object to get the type(as the type will be in the header and response in the payload) or ask my backend resource to send it with a certain schema.
3) This also fails for api's if we are using some third party vendor where we have no control of the output we get.
It's always good to have control on what data going where.In apps which are very big something like a network monitoring application we might end up overwriting the data if we have same KEY and JavaScript being loosed typed may end this to a lot weird way this only works for few cases where we have complete control on the data which is very few some thing like this application.
Okay i'm just gonna write my own answer:
when using redux ask yourself these two questions:
Do I need access to the data across multiple components?
Are those components on a different node tree? What I mean is it isn't a child component.
If your answer is yes then use redux for these data as you can easily pass those data to your components via connect() API which in term makes them containers.
At times if you find yourself the need to pass data to a parent component, then you need to reconsider where your state lives. There is a thing called Lifting the State Up.
If your data only matters to your component, then you should only use setState to keep your scope tight. Example:
class MyComponent extends Component {
constructor() {
super()
this.state={ name: 'anonymous' }
}
render() {
const { name } = this.state
return (<div>
My name is { name }.
<button onClick={()=>this.setState({ name: 'John Doe' })}>show name</button>
</div>)
}
}
Also remember to maintain unidirectional data flow of data. Don't just connect a component to redux store if in the first place the data is already accessible by its parent component like this:
<ChildComponent yourdata={yourdata} />
If you need to change a parent's state from a child just pass the context of a function to the logic of your child component. Example:
In parent component
updateName(name) {
this.setState({ name })
}
render() {
return(<div><ChildComponent onChange={::this.updateName} /></div>)
}
In child component
<button onClick={()=>this.props.onChange('John Doe')}
Here is a good article about this.
Just practice and everything will start to make sense once you know how to properly abstract your app to separate concerns. On these matter composition vs ihhertitance and thinking in react are a very good read.
I started writing a package to make it easier and more generic. Also to improve performance. It's still in its early stages (38% coverage). Here's a little snippet (if you can use new ES6 features) however there is also alternatives.
import { create_store } from 'redux';
import { create_reducer, redup } from 'redux-decorator';
class State {
#redup("Todos", "AddTodo", [])
addTodo(state, action) {
return [...state, { id: 2 }];
}
#redup("Todos", "RemoveTodo", [])
removeTodo(state, action) {
console.log("running remove todo");
const copy = [...state];
copy.splice(action.index, 1);
return copy;
}
}
const store = createStore(create_reducer(new State()));
You can also even nest your state:
class Note{
#redup("Notes","AddNote",[])
addNote(state,action){
//Code to add a note
}
}
class State{
aConstant = 1
#redup("Todos","AddTodo",[])
addTodo(state,action){
//Code to add a todo
}
note = new Note();
}
// create store...
//Adds a note
store.dispatch({
type:'AddNote'
})
//Log notes
console.log(store.getState().note.Notes)
Lots of documentation available on NPM. As always, feel free to contribute!
A key decision to be made when designing React/Redux programs is where to put business logic (it has to go somewhere!).
It could go in the React components, in the action creators, in the reducers, or a combination of those. Whether the generic action/reducer combination is sensible depends on where the business logic goes.
If the React components do the majority of the business logic, then the action creators and reducers can be very lightweight, and could be put into a single file as you suggest, without any problems, except making the React components more complex.
The reason that most React/Redux projects seem to have a lot of files for action creators and reducers because some of the business logic is put in there, and so would result in a very bloated file, if the generic method was used.
Personally, I prefer to have very simple reducers and simple components, and have a large number of actions to abstract away complexity like requesting data from a web service into the action creators, but the "right" way depends on the project at hand.
A quick note: As mentioned in https://stackoverflow.com/a/50646935, the object should be returned from setState. This is because some asynchronous processing may need to happen before store.dispatch is called.
An example of reducing boilerplate is below. Here, a generic reducer is used, which reduces code needed, but is only possible the logic is handled elsewhere so that actions are made as simple as possible.
import ActionType from "../actionsEnum.jsx";
const reducer = (state = {
// Initial state ...
}, action) => {
var actionsAllowed = Object.keys(ActionType).map(key => {
return ActionType[key];
});
if (actionsAllowed.includes(action.type) && action.type !== ActionType.NOP) {
return makeNewState(state, action.state);
} else {
return state;
}
}
const makeNewState = (oldState, partialState) => {
var newState = Object.assign({}, oldState);
const values = Object.values(partialState);
Object.keys(partialState).forEach((key, ind) => {
newState[key] = values[ind];
});
return newState;
};
export default reducer;
tldr It is a design decision to be made early on in development because it affects how a large portion of the program is structured.
Performance wise not much. But from a design perspective quite a few. By having multiple reducers you can have separation of concerns - each module only concerned with themselves. By having action creators you add a layer of indirection -allowing you to make changes more easily. In the end it still depends, if you don't need these features a generic solution helps reduce code.
First of all, some terminology:
action: a message that we want to dispatch to all reducers. It can be anything. Usually it's a simple Javascript object like const someAction = {type: 'SOME_ACTION', payload: [1, 2, 3]}
action type: a constant used by the action creators to build an action, and by the reducers to understand which action they have just received. You use them to avoid typing 'SOME_ACTION' both in the action creators and in the reducers. You define an action type like const SOME_ACTION = 'SOME_ACTION' so you can import it in the action creators and in the reducers.
action creator: a function that creates an action and dispatches it to the reducers.
reducer: a function that receives all actions dispatched to the store, and it's responsible for updating the state for that redux store (you might have multiple stores if your application is complex).
Now, to the question.
I think that a generic action creator is not a great idea.
Your application might need to use the following action creators:
fetchData()
fetchUser(id)
fetchCity(lat, lon)
Implementing the logic of dealing with a different number of arguments in a single action creator doesn't sound right to me.
I think it's much better to have many small functions because they have different responsibilities. For instance, fetchUser should not have anything to do with fetchCity.
I start out by creating a module for all of my action types and action creators. If my application grows, I might separate the action creators into different modules (e.g. actions/user.js, actions/cities.js), but I think that having separate module/s for action types is a bit overkill.
As for the reducers, I think that a single reducer is a viable option if you don't have to deal with too many actions.
A reducer receives all the actions dispatched by the action creators. Then, by looking at the action.type, it creates a new state of the store. Since you have to deal with all the incoming actions anyway, I find it nice to have all the logic in one place. This of course starts to be difficult if your application grows (e.g. a switch/case to handle 20 different actions is not very maintainable).
You can start with a single reducer, the move to several reducers and combine them in a root reducer with the combineReducer function.

Redux mapStateToProps returns undefined

Redux is successfully storing and updating state. The reducers are seemingly working correctly. I'm able to use this.props.dispatch. However, when it actually comes to detailing that information (i.e. this.props.array I always seem to get undefined.
Reducer:
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":
state.array = action.array
return state;
default:
return state;
}
}
State-aware component:
constructor(props) {
super(props);
this.state = {
array: array
}
}
--
self.props.dispatch({
type: 'UPDATE_ARRAY',
array: array
})
--
const mapStateToProps = (state) => {
return {
messages: state.messages,
array: state.array
};
};
export default connect(mapStateToProps)(Component);
This only seems to be able to save state btw when I define an empty array. This doesn't seem right, I thought the intention of Redux was a self-contained store? Updating a variable seems to defeat the purpose a bit.
Would appreciate any help.
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":state={
...state,
array:action.array
}
return state;
default:
return state;
}
}
you should always update your state immutably,instead of mutating the current application state ,you should create another object and return that.State should be immutable ,only way to change the state is to create a new one.This helps to improve the performance of the application.
I am not sure if you application has more than one reducer or not, if it has, than you must be using combine reducer method .So to access state.array in mapsStateToProps is like this
const mapStateToProps = (state) => {
return {
messages: state.{reducer_name}.message,
array: state.{reducer_name}.array
};
};
in place of 'reducer_name' you have to specify the reducers_name which you have define in combine reducer
And last mapStateToProps return array ,in props not in component state.
which you can access in this way {this.props.array},you cant set component state in componentDidMount and in componentWillRecieveProps (in case of aysnc action).
Your component will receive array as a field in its props field. Your code assumes it's in the state field. So instead of:
this.state = {
array: array
}
you would just access this.props.array wherever in your code you need to use the array. You don't need to put it in the local state at all. Usually, you would use it in the render function, like in this example:
render()
{
return <div>The array contains {this.props.array.length} items.</div>
}
I wonder if you're confusing local state with the Redux store's state? Local state is what you get/set when you access this.state in your component code. Every component can have its own state object that it can read from and write to.
The Redux store's state is what's passed in to mapStateToProps. It's usually the entire state object of all the combined reducers in your top-level reducer (though if you only have one reducer function and are not using combineReducers, then the store state is identical to that single reducer's state).
I suggest choosing more descriptive variable names, so that your code will be more readable. It's hard to understand what your intentions are for your code with such generic names. For example, you could name your reducer something that indicates what it's for, like bookListReducer, and name the array you want to store and retrieve for what will go inside it, like books. Naming both your reducer and all your variables array makes it harder to read your code. This will help anyone who reads your code in the future - including, most importantly, you!, as well as future Stack Overflow readers of your future questions (and perhaps this one if you edit it).
I am not sure the following is your issues, but hope these will help:
export default function array(state = {}, {type, array}) {
switch (type) {
case "UPDATE_ARRAY":
return {...state, array};
default:
return state;
}
}
Your reducer should be pure, which you had is mutating the state.
constructor(props) {
super(props);
this.state = {
array: array // what is array?
}
}
Above constructor is not right. You should be able to access the array from this.props.array as your mapStateToProps
Do a console.log(this.props) in your render function or ComponentWillReceiveProps, see if you can something :)

React + Redux, How to render not after each dispatch, but after several?

I am trying to make multiple changes to the store, but not render till all changes are done. I wanted to do this with redux-thunk.
Here is my action creator:
function addProp(name, value) {
return { type:'ADD_PROP', name, value }
}
function multiGeoChanges(...changes) {
// my goal here is to make multiple changes to geo, and make sure that react doesnt update the render till the end
return async function(dispatch, getState) {
for (let change of changes) {
dispatch(change);
await promiseTimeout(2000);
}
}
}
I dispatch my async action creator like this:
store.dispatch(multiGeoChanges(addProp(1, "val1"), addProp(2, "val2"), addProp(3, "val3")));
However this is causing react to render after each dispatch. I am new to redux-thunk, I never used async middleware, but I thought it could help me here.
#Kokovin Vladislav's answer is correct. To add some additional context:
Redux will notify all subscribers after every dispatch. To cut down on re-renders, either dispatch fewer times, or use one of several approaches for "batching" dispatches and notifications. For more info, see the Redux FAQ on update events: http://redux.js.org/docs/faq/Performance.html#performance-update-events .
I also recently wrote a couple of blog posts that relate to this topic. Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability discusses the pros and cons of using thunks, and summarizes several ways to handle batching of dispatches. Practical Redux Part 6: Connected Lists, Forms, and Performance describes several key aspects to be aware of regarding Redux performance.
Finally, there's several other libraries that can help with batching up store change notifications. See the Store#Store Change Subscriptions section of my Redux addons catalog for a list of relevant addons. In particular, you might be interested in https://github.com/manaflair/redux-batch , which will allow you to dispatch an array of actions with only a single notification event.
There are ways to achieve the goal:
Classic way:
usually:
Actions describe the fact that something happened, but don't specify how the application's state changes in response. This is the job of reducers.
That also means that actions are not setters.
Thus, you could describe what has happened and accumulate changes, and dispatch one action
something like:
const multipleAddProp = (changedProps) =>({
type:'MULTIPLE_ADD_PROP', changedProps
});
And then react on action in reducer:
const geo=(state,action)=>{
...
switch (action.type){
case 'MULTIPLE_ADD_PROP':
// apply new props
...
}
}
Another way When rerendering is critical :
then you can consider to limit components, which could be rerendered on state change.
For example you can use shouldComponentUpdate to check whether component
should be rendered or not.
Also you could use reselect, in order to not rerender connected components
after calculating derived data...
Non standard way:
redux-batched-action
It works something like transaction.
In this example, the subscribers would be notified once:
import { batchActions } from 'redux-batched-actions';
const multiGeoChanges=(...arrayOfActions)=> dispatch => {
dispatch( batchActions(arrayOfActions) );
}
In react-redux 7.0.1+ batching is now built-in. Release notes of 7.0.1:
https://github.com/reduxjs/react-redux/releases/tag/v7.0.1
Batched Updates
React has an unstable_batchedUpdates API that it uses to group
together multiple updates from the same event loop tick. The React
team encouraged us to use this, and we've updated our internal Redux
subscription handling to leverage this API. This should also help
improve performance, by cutting down on the number of distinct renders
caused by a Redux store update.
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment());
dispatch(increment());
})
}
}
By design when the state, which is held by the store, changes the view should render.
You can avoid this by updating the state once.
If you are using promises you can use Promise.all to wait for all the promises to resolve and then dispatch a new action to the store with the calculated result. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Something like this:
Promise.all([p1, p2, p3, p4, p5]).then(changes => {
dispatch(changes)
}, err => {
// deal with error
});
Of course you'll need an action that will deal with many props, something like addManyProps this should update the state once, resulting in one render.
redux-batched-actions
Batching action creator and associated higher order reducer for redux that enables batching subscriber notifications for an array of actions.
Coming to this a bit late, but I think this is a much nicer solution, which enables you to add meta.batch to actions you would like to batch together into a single react update. As a bonus this approach works with asynchronous actions.
import raf from 'raf'
import { batchedSubscribe } from 'redux-batched-subscribe'
let notify = null
let rafId = null
const shouldBatch = action => action?.meta?.batch
export const batchedSubscribeEnhancer = batchedSubscribe(freshNotify => (notify = freshNotify))
export const batchedSubscribeMiddleware = () => next => action => {
const resolved = next(action)
if (notify && rafId === null && !shouldBatch(action)) {
notify()
} else if (!rafId) {
rafId = raf(() => {
rafId = null
notify()
})
}
return resolved
}
Then connect up to your store
mport { applyMiddleware, compose, createStore } from 'redux'
import { batchedSubscribeMiddleware, batchedSubscribeEnhancer } from './batching'
const store = createStore(
reducer,
intialState,
compose(
batchedSubscribeEnhancer,
applyMiddleware(batchedSubscribeMiddleware)
)
)

what is right way to do API call in react js?

I have recently moved from Angular to ReactJs. I am using jQuery for API calls. I have an API which returns a random user list that is to be printed in a list.
I am not sure how to write my API calls. What is best practice for this?
I tried the following but I am not getting any output. I am open to implementing alternative API libraries if necessary.
Below is my code:
import React from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
UserList(){
return $.getJSON('https://randomuser.me/api/')
.then(function(data) {
return data.results;
});
}
render() {
this.UserList().then(function(res){
this.state = {person: res};
});
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">
{this.state.person.map((item, i) =>{
return(
<h1>{item.name.first}</h1>
<span>{item.cell}, {item.email}</span>
)
})}
<div>
</div>
)
}
}
In this case, you can do ajax call inside componentDidMount, and then update state
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
render() {
const persons = this.state.person.map((item, i) => (
<div>
<h1>{ item.name.first }</h1>
<span>{ item.cell }, { item.email }</span>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ persons }</div>
</div>
);
}
}
You may want to check out the Flux Architecture. I also recommend checking out React-Redux Implementation. Put your api calls in your actions. It is much more cleaner than putting it all in the component.
Actions are sort of helper methods that you can call to change your application state or do api calls.
Use fetch method inside componentDidMount to update state:
componentDidMount(){
fetch('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
This discussion has been for a while and #Alexander T.'s answer provided a good guide to follow for newer of React like me. And I'm going to share some additional know-how about calling the same API multiple times to refresh the component, I think it's probably a common question for beginners.
componentWillReceiveProps(nextProps), from official documentation :
If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.
We could conclude that here is the place we handle props from the parent component, have API calls, and update the state.
Base on #Alexander T.'s example:
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
//For our first load.
this.UserList(this.props.group); //maybe something like "groupOne"
}
componentWillReceiveProps(nextProps) {
// Assuming parameter comes from url.
// let group = window.location.toString().split("/")[*indexParameterLocated*];
// this.UserList(group);
// Assuming parameter comes from props that from parent component.
let group = nextProps.group; // Maybe something like "groupTwo"
this.UserList(group);
}
UserList(group) {
$.getJSON('https://randomuser.me/api/' + group)
.then(({ results }) => this.setState({ person: results }));
}
render() {
return (...)
}
}
Update
componentWillReceiveProps() will be deprecated.
Here are only some methods (all of them in Doc) in the life cycle I think that they are related to deploying API in the general cases:
By referring to the diagram above:
Deploy API in componentDidMount()
The proper scenario to have API call here is that the content (from the response of API) of this component will be static, componentDidMount() only fire once while the component is mounting, even new props are passed from the parent component or have actions to lead re-rendering.
The component do check difference to re-render but not re-mount.
Quote from doc:
If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.
Deploy API in static getDerivedStateFromProps(nextProps, prevState)
We should notice that there are two kinds of component updating, setState() in current component would not trigger this method but re-rendering or new props from parent component would.
We could find out this method also fires while mounting.
This is a proper place to deploy API if we want to use the current component as a template, and the new parameters to make API calls are props coming from parent component.
We receive a different response from API and return a new state here to change the content of this component.
For example:
We have a dropdown list for different Cars in the parent component, this component needs to show the details of the selected one.
Deploy API in componentDidUpdate(prevProps, prevState)
Different from static getDerivedStateFromProps(), this method is invoked immediately after every rendering except the initial rendering. We could have API calling and render difference in one component.
Extend the previous example:
The component to show Car's details may contain a list of series of this car, if we want to check the 2013 production one, we may click or select or ... the list item to lead a first setState() to reflect this behavior (such as highlighting the list item) in this component, and in the following componentDidUpdate() we send our request with new parameters (state). After getting the response, we setState() again for rendering the different content of the Car details. To prevent the following componentDidUpdate() from causing the infinity loop, we need to compare the state by utilizing prevState at the beginning of this method to decide if we send the API and render the new content.
This method really could be utilized just like static getDerivedStateFromProps() with props, but need to handle the changes of props by utilizing prevProps. And we need to cooperate with componentDidMount() to handle the initial API call.
Quote from doc:
... This is also a good place to do network requests as long as you
compare the current props to previous props ...
I would like you to have a look at redux
http://redux.js.org/index.html
They have very well defined way of handling async calls ie API calls, and instead of using jQuery for API calls, I would like to recommend using fetch or request npm packages, fetch is currently supported by modern browsers, but a shim is also available for server side.
There is also this another amazing package superagent, which has alot many options when making an API request and its very easy to use.
You can also fetch data with hooks in your function components
full example with api call: https://codesandbox.io/s/jvvkoo8pq3
second example: https://jsfiddle.net/bradcypert/jhrt40yv/6/
const Repos = ({user}) => {
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://api.github.com/users/${user}/repos`);
setRepos(response.data);
}
fetchData();
}, []);
return (
<div>
{repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))
1) You can use Fetch API to fetch data from Endd Points:
Example fetching all Github repose for a user
/* Fetch GitHub Repos */
fetchData = () => {
//show progress bar
this.setState({ isLoading: true });
//fetch repos
fetch(`https://api.github.com/users/hiteshsahu/repos`)
.then(response => response.json())
.then(data => {
if (Array.isArray(data)) {
console.log(JSON.stringify(data));
this.setState({ repos: data ,
isLoading: false});
} else {
this.setState({ repos: [],
isLoading: false
});
}
});
};
2) Other Alternative is Axios
Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.
import axios from "axios";
/* Fetch GitHub Repos */
fetchDataWithAxios = () => {
//show progress bar
this.setState({ isLoading: true });
// fetch repos with axios
axios
.get(`https://api.github.com/users/hiteshsahu/repos`)
.then(result => {
console.log(result);
this.setState({
repos: result.data,
isLoading: false
});
})
.catch(error =>
this.setState({
error,
isLoading: false
})
);
}
Now you can choose to fetch data using any of this strategies in componentDidMount
class App extends React.Component {
state = {
repos: [],
isLoading: false
};
componentDidMount() {
this.fetchData ();
}
Meanwhile you can show progress bar while data is loading
{this.state.isLoading && <LinearProgress />}
Render function should be pure, it's mean that it only uses state and props to render, never try to modify the state in render, this usually causes ugly bugs and decreases performance significantly. It's also a good point if you separate data-fetching and render concerns in your React App. I recommend you read this article which explains this idea very well. https://medium.com/#learnreact/container-components-c0e67432e005#.sfydn87nm
This part from React v16 documentation will answer your question, read on about componentDidMount():
componentDidMount()
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().
As you see, componentDidMount is considered the best place and cycle to do the api call, also access the node, means by this time it's safe to do the call, update the view or whatever you could do when document is ready, if you are using jQuery, it should somehow remind you document.ready() function, where you could make sure everything is ready for whatever you want to do in your code...
As an addition/update to Oleksandr T.'s excellent answer:
If you use class components, backend calls should happen in componentDidMount.
If you use hooks instead, you should use the effect hook
For example:
import React, { useState, useEffect } from 'react';
useEffect(() => {
fetchDataFromBackend();
}, []);
// define fetchDataFromBackend() as usual, using Fetch API or similar;
// the result will typically be stored as component state
Further reading:
Using the Effect Hook in the official docs.
How to fetch data with React Hooks? by Robin Wieruch
A clean way is to make an asynchronous API call inside componentDidMount with try/catch function.
When we called an API, we receive a response. Then we apply JSON method on it, to convert the response into a JavaScript object. Then we take from that response object only his child object named "results" (data.results).
In the beginning we defined "userList" in state as an empty array. As soon as we make the API call and receive data from that API, we assign the "results" to userList using setState method.
Inside the render function we tell that userList will be coming from state. Since the userList is an array of objects we map through it, to display a picture, a name and a phone number of each object "user". To retrieve this information we use dot notation (e.g. user.phone).
NOTE: depending on your API, your response may look different. Console.log the whole "response" to see which variables you need from it, and then assign them in setState.
UserList.js
import React, { Component } from "react";
export default class UserList extends Component {
state = {
userList: [], // list is empty in the beginning
error: false
};
componentDidMount() {
this.getUserList(); // function call
}
getUserList = async () => {
try { //try to get data
const response = await fetch("https://randomuser.me/api/");
if (response.ok) { // ckeck if status code is 200
const data = await response.json();
this.setState({ userList: data.results});
} else { this.setState({ error: true }) }
} catch (e) { //code will jump here if there is a network problem
this.setState({ error: true });
}
};
render() {
const { userList, error } = this.state
return (
<div>
{userList.length > 0 && userList.map(user => (
<div key={user}>
<img src={user.picture.medium} alt="user"/>
<div>
<div>{user.name.first}{user.name.last}</div>
<div>{user.phone}</div>
<div>{user.email}</div>
</div>
</div>
))}
{error && <div>Sorry, can not display the data</div>}
</div>
)
}}
As best place and practice for external API calls is React Lifecycle method componentDidMount(), where after the execution of the API call you should update the local state to be triggered new render() method call, then the changes in the updated local state will be applied on the component view.
As other option for initial external data source call in React is pointed the constructor() method of the class. The constructor is the first method executed on initialization of the component object instance. You could see this approach in the documentation examples for Higher-Order Components.
The method componentWillMount() and UNSAFE_componentWillMount() should not be used for external API calls, because they are intended to be deprecated. Here you could see common reasons, why this method will be deprecated.
Anyway you must never use render() method or method directly called from render() as a point for external API call. If you do this your application will be blocked.
You must try "axios" library for API call.
Instead of direct using jQuery.
Thanks.
It would be great to use axios for the api request which supports cancellation, interceptors etc. Along with axios, l use react-redux for state management and redux-saga/redux-thunk for the side effects.

Categories

Resources