Testing fetch() method inside React component - javascript

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.

Related

Best practise to handle with responses and incoming props

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.

React - Promise - "this" is undefined

I'm sure this is a beginner question, but actually I can't figure out, why this is undefined.
But let's start from the beginning. I'm using react with react-redux to load data. In this special case I first want to load all positions of a project and then load the verfication files. That are 2 queries, so I want to be sure, that the positions are loaded completely before start loading the verifications. So I thought - oohh, let's use a promise here.
It starts with the method loadVerifications(event). In this method, the method loadProjectPositions(event) is being called. I can see that the project positions are loaded correctly.
When the positions are loaded it should load then the verification files (.then(()...).
Actually in this .then() method, this is undefined, but why?
class Kundenkommunikation extends Component {
constructor(p) {
super(p);
this.state = {
};
this.loadProjectPositions = this.loadProjectPositions.bind(this);
this.loadVerifications = this.loadVerifications.bind(this);
}
loadProjectPositions(event) {
return new Promise((resolve,reject) => {
this.props.getProjektPositionenByProjektIdForModulId(13, event.value);
resolve(this)
})
}
loadVerifications(event) {
this.loadProjectPositions(event)
.then(() => {
this.props.projektpositionen && Object.values(this.props.projektpositionen).map((position) => {
if(position.nachweis != null) {
this.props.createBericht(position.nachweis.id,'SherpaVerbinder');
}
});
})
}
UPDATE:
Even when I'm bind loadVerifications in the constructor, it doesn't work. this stays undefined.
You should use bind(this) method for loadVerifications to get a component's context from the external function or
Another is arrow functions loadVerifications= (event) => {...}
Ok, thanks for all hints and tipps. Lately it turned out, that Chrome did not show the data for whatever reason, but the data was available. I could find out by simply add an console.log(this.props) into the code, then it showed me all props and I was able to see that this is not undefined.
But the real issue was then, that the data was not loaded at this time. So I needed to add a then into the method loadProjectPositions to be sure, that the data is returned before resolving the Promise.
loadProjectPositions(event) {
return new Promise((resolve,reject) => {
this.props.getProjektPositionenByProjektIdForModulId(13, event.value)
.then(() => {
resolve(this)
});
})
}
After adding this, all data was loaded correctly at that time and then all worked.

ReactJS: multiple setStates happening asynchronously, state not updated

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.

ReactJS - REST API query inside .map function?

How do I generate repetitive mark-up elements in a React component, that also require a REST API query for each of them? Initially I had the calls completing in a for-each loop within the componentDidMount function. But that required calling setState inside the loop, in order to trigger a re-Render. The behavior was unstable, and apparently that technique is not a good idea.
So, now I'm trying to get the queries to run inside the .map function of my render (see below). But that's producing this error.
Is there a way to return the element value instead of a promise? (Probably more of a JS question than a React one...)
Or am I going about this the wrong way altogether?
return (<div>
{this.props.currentProject.Tasks.map((task => {
return ProjectsService.getTaskBaselines(this.props.currentProject.PWAGuid, task.TaskId, this.props.context).then((baseline: any): any => {
return (<div>{task.TaskName}</div>);
})
}))}
</div>);
It seems like you want to query all APIs and then handle the response once all are settled.
Try this:
const promises = this.props.currentProject.Tasks.map((task => {
return new Promise(function(resolve, reject) {
resolve(task);
});
Promise.all(promises).then((data) => {
this.setState({
// act on all of your data
});
});
Then render this.state in your render() function.

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