React/Redux chaining async thunk actions at component level - javascript

What is the recommended way to chain dependent asynchronous redux thunk actions at the component level?
My use case is a flow where I need to first make an api call to retrieve a user object, then grab all blog posts for that user. The catch is that the second call to grab all blog posts is dependent on the first calls return value (user id).
My component:
export default class UserDetail extends React.Component
{
componentDidMount() {
this.props.getUser()
}
}
this.props.getUser() returns a user object which I map to props:
const mapStateToProps = (state) => {
return {
user: state.user
}
}
I need to call this.props.getBlogPostsForUser(USER_ID) after this.props.getUser() has completed. What is the recommended best practice to chain actions in this way?

You can chain thunks
const getUser = username => dispatch => request(username)
.then(res => dispatch({ type: GET_USER })
.catch(err => dispatch({ type: GET_USER_ERR }));
const getBlogPostsForUser = userId => dispatch => request(userId)
.then(res => dispatch({ type: GET_BLOGS }))
.catch(err => dispatch({ type: GET_BLOGS_ERR }));
const getUserAndPosts = username => (dispatch, getState) => dispatch(getUser(username))
.then(() => {
const user = getState().user;
return dispatch(getBlogPostsForUser(user.id));
});
Or you can combine them to one dispatch but then they are tied together
const getUserAndPosts = (username) => dispatch => request(username)
.then((userData) => {
dispatch(setUser(userData));
return request(user.id)
.then(blogs => dispatch(setBlog(blogs)));
});

You have to identify the new user response coming in componentDidUpdate lifecycle method to call another dependent call. Like this
export default class UserDetail extends React.Component {
componentDidMount() {
this.props.getUser();
}
componentDidUpdate(prevProps) {
const { user, getBlogPostsForUser } = this.props;
const { user: prevUser } = prevProps;
if (prevUser !== user) {
const { USER_ID } = user; // derive USER_ID from user object. I dont know path. you can accordingly change
getBlogPostsForUser(USER_ID);
}
}
}
This should work. Feedbacks are welcome

Related

Redux action doesn't dispatch after page refresh

I have an issue with redux and probably useEffect(I am not sure where my mistake is). I am trying to get information from PokeAPI and store information in the redux state. The problem is that the information about pokemons don't include pokemon types(fire, water, etc.), to solve this I am sending requests to fetch those types from a different endpoint and I want to include these types of specific pokemon to redux state.
1-redux state without types of pokemons
2-redux state with types of pokemons
My goal is to have a state like in the second picture with types. But when I refresh the page, I only acquire the first picture(actions aren't dispatching). When I change something in my code and save it, I get types as well. I suspect that my problem is in the useEffect, but I couldn't find a solution without creating some nasty loops.
Note: Page parameter in fetchData coming from PokeAPI, it basically returns 15 pokemon for every page.(For now I am just experimenting on the first page)
This is my first question in stackoverflow, I already searched for similar questions but those were dealing with different aspects, so I decided to ask myself.
PokemonList.js --> this is where I need those types
import React, { useEffect } from 'react';
import { ListGroup, ListGroupItem } from "react-bootstrap";
import { useDispatch, useSelector } from 'react-redux';
import _ from "lodash";
import { GetPokemonList, GetSpecificPokemon } from '../redux/actions/PokemonAction';
import { Button } from 'react-bootstrap';
const PokemonList = () => {
const dispatch = useDispatch();
const pokemonList = useSelector(state => state.PokemonList);
useEffect(() => {
const fetchData = (page = 1) => {
dispatch(GetPokemonList(page));
}
fetchData();
}, [dispatch]);
useEffect(() => {
const fetchTypes = () => {
pokemonList.data.forEach(pokemon => {
dispatch(GetSpecificPokemon(pokemon.name));
});
}
fetchTypes();
}, [dispatch]);
const showData = () => {
if (!_.isEmpty(pokemonList.data)) {
return (
<div className="pokemon-list-wrapper">
{pokemonList.data.map((pokemon, index) => {
return (
<div className="pokemon-list-element" key={index}>
<ListGroup>
<ListGroupItem action href={`/pokemon/${pokemon.name}`} variant="success">{pokemon.name}
<Button style={{ float: "right" }}>Test</Button>
</ListGroupItem>
</ListGroup>
</div>
)
})}
</div>
)
}
if (pokemonList.loading) {
return <p>Loading...</p>
}
if (pokemonList.errorMessage !== "") {
return <p>{pokemonList.errorMessage}</p>
}
};
return (
<div>
{showData()}
</div>
)
};
export default PokemonList;
PokemonAction.js
import axios from "axios"
export const GetPokemonList = (page) => async (dispatch) => {
try {
dispatch({
type: "POKEMON_LIST_LOADING"
});
const perPage = 15;
const offset = (page * perPage) - perPage;
const res = await axios.get(`https://pokeapi.co/api/v2/pokemon?limit=${perPage}&offset=${offset}`);
dispatch({
type: "POKEMON_LIST_SUCCESS",
payload: res.data
});
} catch (e) {
dispatch({
type: "POKEMON_LIST_FAIL"
});
}
}
export const GetSpecificPokemon = (name) => async (dispatch) => {
try {
const res = await axios.get(`https://pokeapi.co/api/v2/pokemon/${name}`);
dispatch({
type: "SPECIFIC_POKEMON_SUCCESS",
payload: res.data
});
} catch (e) {
dispatch({
type: "SPECIFIC_POKEMON_FAIL"
});
}
}
PokemonListReducer.js
const initialState = {
data: [],
loading: false,
errorMessage: "",
count: 0
};
const PokemonListReducer = (state = initialState, action) => {
switch (action.type) {
case "POKEMON_LIST_LOADING":
return {
...state,
loading: true,
errorMessage: ""
};
case "POKEMON_LIST_FAIL":
return {
...state,
loading: false,
errorMessage: "unable to get pokemon"
};
case "POKEMON_LIST_SUCCESS":
return {
...state,
loading: false,
data: action.payload.results,
errorMessage: "",
count: action.payload.count
};
case "SPECIFIC_POKEMON_SUCCESS":
const typesMap = action.payload.types.map((type) => {
return type.type.name;
})
return {
...state,
data: state.data.map((pokemon) => pokemon.name === action.payload.name
? {...pokemon, types: typesMap}
: pokemon
),
loading: false,
errorMessage: ""
}
case "SPECIFIC_POKEMON_FAIL":
return {
...state,
loading: false,
errorMessage: "unable to get pokemon"
};
default:
return state
}
}
export default PokemonListReducer;
This is happening because your second useEffect does not wait for your first useEffect to finish and because of that the pokemon list is empty. On code change, since the state already has the pokemon list pre-filled, the second useEffect finds the list and does it's thing. You have to guarantee that the second action is caller right after the first one in order for this to work properly. One way to do this is to dispatch the GetSpecificPokemon action for each pokemon before finishing the GetPokemonList action. Something like this should work:
export const GetPokemonList = (page) => async (dispatch) => {
try {
dispatch({
type: "POKEMON_LIST_LOADING"
});
const perPage = 15;
const offset = (page * perPage) - perPage;
const res = await axios.get(`https://pokeapi.co/api/v2/pokemon?limit=${perPage}&offset=${offset}`);
dispatch({
type: "POKEMON_LIST_SUCCESS",
payload: res.data
});
res.data.result.forEach(pokemon => {
dispatch(GetSpecificPokemon(pokemon.name));
});
} catch (e) {
dispatch({
type: "POKEMON_LIST_FAIL"
});
}
}
Note that you won't be needing the second useEffect if you are doing this. You might also have to change displaying/not displaying the loader part yourself.
Another way is to add pokemonList as the second object in the useEffect's array parameter. I haven't tested it yet but this should work. For example:
useEffect(() => {
const fetchTypes = () => {
pokemonList.data.forEach(pokemon => {
dispatch(GetSpecificPokemon(pokemon.name));
});
}
fetchTypes();
}, [dispatch, pokemonList]);
This will call the useEffect whenever there is a change in pokemonList. In your implementation, useEffect is only called once since the value of dispatch never really changes after that. Adding pokemonList to the array results in the useEffect being called when there is a change in pokemonList also. Use this approach if you want the GetPokemonList action to always be separate from GetSpecificPokemon action i.e there are cases when both are not called together. If both are always called together then the first approach is cleaner.
That being said, these implementations actually result in a lot of network calls. The best way is to avoid the second call if possible (change your UI accordingly?) since you do not have any control over the API. If you do have control over the API you could include the extra data in the first request's response.
Edit: Here is the batch logic
const p = pokemonList.map(({ name }) =>
axios.get(`https://pokeapi.co/api/v2/pokemon/${name}`)
);
const res = await Promise.all(p);
const data = res.map((r) => ({
...r.data,
types: r.data.types.map((type) => type.type.name) // the logic you were using for types
}));
dispatch({
type: "SPECIFIC_POKEMON_SUCCESS",
payload: data
});
And then update the state in the reducer like
case "SPECIFIC_POKEMON_SUCCESS":
return {
...state,
data: action.payload,
loading: false,
errorMessage: ""
};

Trying to provide createContext with a default value object which contains functions.Is there any correct way to do so?

Here is my context creator in which i made an initial state for the context but i also have to pass the functions that i want to use by useContext in the project.I passed the functions this way.can anybody tell me a better method to do so. P.S this method works fine but i think itll create problems for big projects
import { createContext } from "react"
import { initialState } from "./Initstate"
import { IInitState } from "./alert/IInitState"
export interface IGithubContext {
State: IInitState,
searchusers: (login: string) => void,
clearUsers: () => void,
getuser: (login: string) => void,
getuserrepos: (login: string) => void
}
const istate: IGithubContext = {
State: initialState,
clearUsers: () => null,
getuser: () => null,
getuserrepos: () => null,
searchusers: () => null
}
const GithubContext = createContext<IGithubContext>(istate)
export default GithubContext
Here is my provider which contains functions that i am trying to pass in the value={{}} as you can see
import GithubReducer, { Action } from "./GithubReducer"
import GithubContext from "./GithubContext"
import { SET_LOADING, CLEAR_USERS, USER_LOADING, GET_USER, SEARCH_USERS, GET_REPOS } from "./Types"
import { IInitState } from "./alert/IInitState"
import { initialState } from "./Initstate"
const GithubState = (props: any) => {
const [state, dispatch] = useReducer<React.Reducer<IInitState, Action>>(GithubReducer, initialState)
const searchusers = async (search: string) => {
setloading()
const data = await fetch(`https://api.github.com/search/users?q=${search}`)
const items = await data.json()
dispatch({ type: SEARCH_USERS, payload: items.items })
}
const getuser = async (login: string) => {
userloading()
const data = await fetch(`https://api.github.com/users/${login}`)
const items = await data.json()
dispatch({ type: GET_USER, payload: items })
}
const getuserrepos = async (login: string) => {
const data = await fetch(`https://api.github.com/users/${login}/repos?per_page=5&sort=created:asc`)
const items = await data.json()
dispatch({ type: GET_REPOS, payload: items })
}
const clearUsers = () => dispatch({ type: CLEAR_USERS })
const setloading = () => dispatch({ type: SET_LOADING })
const userloading = () => dispatch({ type: USER_LOADING })
return (
<GithubContext.Provider value={{
State: {
users: state.users,
user: state.user,
loading: state.loading,
userloading: state.userloading,
repos: state.repos,
alert: state.alert,
},
searchusers,
clearUsers,
getuser,
getuserrepos
}}>
{props.children}
</GithubContext.Provider>
)
}
export default GithubState
The values for the function in the context will default to the value provided when either the Provider doesn't enclose the components that need the context or when a component tries to access the values from the context without having access to it(not children of the Provider).
The current implementation of the default values for the functions for example, getuser: () => null just fail silently when some component calls the function getuser and has no access to the context provider. So yes this will cause some issues.
An alternative approach, throw an error inside the default values for the functions so that when ComponentA which is not a child of the Provider invokes the function getuser or searchusers, instead of failing silently the function will throw an error. With this approach at least you will know that some component which doesn't have access to the context tried to access some value from it.
const istate: IGithubContext = {
State: initialState,
clearUsers: () => { throw new Error('GithubContext not avaliable') },
getuser: () => { throw new Error('GithubContext not avaliable') },
/*other values*/
}

redux thunk fetch api action and reducer

So decided to use redux-thunk and I have a problem to write a function in my actions and reducer. Actually function looks like this:
async getData() {
if (this.props.amount === isNaN) {
return;
} else {
try {
await fetch(
`https://api.exchangeratesapi.io/latest?base=${this.props.base}`,
)
.then(res => res.json())
.then(data => {
const date = data.date;
const result = (data.rates[this.props.convertTo] * this.props.amount).toFixed(4);
this.setState({
result,
date,
});
}, 3000);
} catch (e) {
console.log('error', e);
}
}
}
Also I already have action types
export const FETCH_DATA_BEGIN = 'FETCH_DATA_BEGIN';
export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
export const FETCH_DATA_FAIL = 'FETCH_DATA_FAIL';
and actions like this
export const fetchDataBegin = () => {
return {
type: actionTypes.FETCH_DATA_BEGIN,
};
};
export const fetchDataSuccess = data => {
return {
type: actionTypes.FETCH_DATA_SUCCESS,
data: data,
};
};
export const fetchDataFail = error => {
return {
type: actionTypes.FETCH_DATA_FAIL,
error: error,
};
};
And then comes the hard part for me where I don't know how to get the same result from function async getData(). I already have just this in my action :
export async function fetchData() {
return async dispatch => {
return await fetch(`https://api.exchangeratesapi.io/latest?base=${this.props.base}`)
.then(res => res.json())
.then(data => {
// <------------------- WHAT NEXT?
}
};
export function fetchData() {
return dispatch => {
fetch(`https://api.exchangeratesapi.io/latest?base=${this.props.base}`)
.then(res => res.json())
.then(data => dispatch(fetchDataSuccess(data)), e => dispatch(fetchDataFail(e)))
}
};
Now this code:
const date = data.date;
const result = (data.rates[this.props.convertTo] * this.props.amount).toFixed(4);
this.setState({
result,
date,
});
goes into your reducer
if(action.type === FETCH_DATA_SUCCESS) {
const date = action.data.date;
const rates = action.data.rates;
return { ...state, rates, date };
}
Now you can use the redux state in your component and make the rest of the calculations there (ones that need this.props).
To dispatch the fetchData action now, you do this.props.dispatch(fetchData()) in your react-redux connected component.
EDIT
Here's how you use the state in the component.
I'm assuming you have created the redux store. something like:
const store = createStore(rootReducer,applyMiddleware(thunk));
Now, you can use the react-redux library's connect function to connect the redux state to your component.
function mapStateToProps(state, ownProps) {
return {
date: state.date,
result: (state.rates[ownProps.convertTo] * ownProps.amount).toFixed(4);
}
}
function mapDispatchToProps(dispatch) {
return {
fetchData: () => dispatch(fetchData())
}
}
export default connect(mapStateToProps,mapDispatchToProps)(YourComponent)
You can use this Higher Order Component in your DOM now and pass the appropriate props to it:
import ConnectedComponent from "./pathTo/ConnectedComponent";
...
return <View><ConnectedComponent convertTo={...} amount={...} /></View>;
And, also inside YourComponent you can now read this.props.date and this.props.result and use them wherever you need to.
You might want to look at selectors in the future to memoize the state and reduce the performance cost of redux.

How to transfer data from variable to redux storage?

I have a getPostById method that makes an asynchronous request and returns the post we clicked on. How to create a variable in redux 'postArticle' and write in the data that came from https://jsonplaceholder.typicode.com/posts/$ {id}?
getPostById = async(id: any) => {
const myResponse = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
const myJson = await myResponse.json();
const IWantToRedux = myJson.body
}
my actions
export const GET_ID = 'GET_ID'
export const getPostById = (changedBody: any) => ({
type: GET_ID,
payload: changedBody,
});
const initialState = {
background: "blue",
changedBody: 'hello',
}
my reducer
export function pageReducer(state = initialState, action: any) {
switch (action.type) {
case GET_ID:
return {...state, getPostByIdAction: action.payload};
default:
return state
}
}
Ok, I get it. You're using middleware, well, since it runs before your Redux action, you must return dispatch and then dispatch it, like this:
getPostById = async(id: any) => {
const myResponse = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
const myJson = await myResponse.json();
const IWantToRedux = myJson.body
return dispatch => {
dispatch({type: GET_ID, payload: IWantToRedux});
}
}
I'm considering that this asynchronous action is being triggered in the redux actions (in your component, you must dispatch this action, not the type/payload);
Let me know if that worked for you!

Converting functions from pure react to redux react

In pure react, I have written a function that I call in componentDidMount ():
getTasks = (userId, query, statusTask, pageNumber) => {
let check = {};
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(res => {
check = res.data;
if (res.data) {
this.setState({
checkRunning: res.data,
checkRunningId: res.data.id
});
this.utilizeTimes(res.data.task_id);
}
})
.catch(error => {
console.log(error);
})
.then(() => {
const params = {
sort: 'name'
};
if (query) {
params['filter[qwp]'] = query;
if (this.state.tasks[0]) {
this.setState({
selectedId: this.state.tasks[0].id,
selectedTabId: this.state.tasks[0].id
});
}
}
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(res => {
if (res.status === 200 && res.data) {
this.setState({
tasks: res.data,
lengthArrayTasks: parseInt(res.headers['x-pagination-total-count'])
});
if (!check && res.data && res.data[0]) {
this.setState({
selectedTabId: res.data[0].id,
});
this.load(res.data[0].id);
}
let myArrayTasks = [];
myArrayTasks = res.data;
let findObject = myArrayTasks.find(task => task.id === this.state.runningTimerTask.id);
if (
!findObject &&
this.state.runningTimerTask &&
this.state.runningTimerTask.id &&
this.state.query === ''
) {
this.setState({
tasks: [this.state.runningTimerTask, ...myArrayTasks]
});
}
}
})
.catch(error => {
console.log(error);
});
});
};
I am trying to rewrite it to redux, but with poor results. First it makes one request / api / v1 / beta / $ {userId}, writes the answer in the variable check. check passes to the nextthen. In the next then carries out the request '/ api / v1 // tasks' Can somebody help me? I am asking for some tips. Is this somehow complicated?
So far, I've managed to create something like this:
store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
actions
export const RUNNING_TIMER = 'RUNNING_TIMER';
export const GET_TASKS = 'GET_TASKS';
export const FETCH_FAILURE = 'FETCH_FAILURE';
export const runningTimer = (userId, query, statusTask, pageNumber) => dispatch => {
console.log(userId);
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
})
.then(() => {
const params = {
sort: 'name'
};
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(({ data }) => {
dispatch({
type: GET_TASKS,
payload: data
});
})
.catch(error => {
console.log(error);
});
});
};
reducer
import { RUNNING_TIMER, GET_TASKS } from '../actions';
const isRunningTimer = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case RUNNING_TIMER:
return {
checkRunningTimer: payload,
checkRunningTimerId: payload && payload.id ? payload.id : null
};
break;
case GET_TASKS:
return {
tasks: payload,
lengthArrayTasks: parseInt(action.headers['x-pagination-total-count'])
};
default:
return state;
}
};
const rootReducer = combineReducers({ isRunningTimer });
export default rootReducer;
App
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
componentDidMount() {
this.props.runningTimer();
}
render() {
return (
<div>
</div>
);
}
}
const mapStateToProps = state => {
const { isRunningTimer } = state;
return {
isRunningTimer
};
};
const mapDispatchToProps = dispatch => ({
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Number 1 Consider your state design.
I find it useful to consider what the state object would look like at a given point in time.
Here is an example of initialState used in an application of mine.
const initialState = {
grocers: null,
coords: {
latitude: 37.785,
longitude: -122.406
}
};
This is injected at the createStore.
Breaking down your application state object/properties, should assist you in making your actions simpler as well.
Number 2
Consider breaking down your actions.
My thoughts, decouple the action code, at the .then at the second .then .(Consider saving the results somewhere in a user: object)
.then(response => {
const data = response.data.user;
setUsers(data);})
.catch(error => {
console.log('There has been a problem with your fetch operation: ' + error.message);
})
function setUsers(data){
dispatch({
type: FETCH_USERS,
payload: data
});
}
This refers to the S in SOLID design principles. Single Responsibility Principle.
https://devopedia.org/solid-design-principles
Number 3
Consider this, if the 'getUser' info fetch fails.
Having the process/response separated will allow the application to be debugged more cleanly. In example, the user api failed or the getTask api failed, etc.
More resources on redux.
https://redux.js.org/introduction/learning-resources#thinking-in-redux
Extending previous answer from #Cullen, this is what I did:
Since you already have a action to GET_TODOS, just make the action creator for runningTimer to do one and only one thing - make API call to /api/v1/beta/<userId> and dispatch respective actions.
export const runningTimer = (
userId,
query,
statusTask,
pageNumber
) => dispatch => {
return axios({
url: `/api/v1/beta/${userId}`,
method: "GET"
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
});
};
Update props of your app component to read store data.
...
const mapStateToProps = state => {
const { isRunningTimer, todos, todo } = state;
return {
todos,
todo,
isRunningTimer,
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
getTodo: id => dispatch(getTodo(id)),
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer(userId)),
});
...
Update the implementation of componentDidMount to dispatch isRunningTimer -
componentDidMount() {
...
// call with userId 1
this.props.runningTimer(1).then(() => {
console.log(this.props);
// additional params for getTasks
const params = {
sort: 'name'
};
// another call for getTodos with names sorted
this.props.getTodos(params);
});
...
Note: You need to update your getTodos action to take in an optional params arguments (which is initialized to empty object if not passed).
Hope this helps you.
Live sandbox for this is present here - https://stackblitz.com/edit/react-redux-more-actions
Check out React-boilerplate. Great boilerplate for react and redux. They use redux-saga and redux-hooks as well.

Categories

Resources