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!
Related
im dispaching my id to the productsSlice , and from there im call to getProductById() that in my productService, the productService communicate with async-storage-service and i think the problem comes from here.
i want to get the product from the id i sent.
i dont have an error in the console.
//ProductDetails.jsx
export function ProductDetails() {
let [loading, setLoading] = useState(true);
const dispatch = useDispatch()
// let [color, setColor] = useState("#ffffff");
const { id } = useParams();
console.log(id);
const [product, setProduct] = useState(null);
useEffect(() => {
dispatch(getProduct(id))
}, [id, product]);
//productsSlice.js
const productsSlice = createSlice({
name: 'products',
initialState,
reducers: {
getProduct: (state, action) => {
productsService.getProductById(action.payload)
}
},
//products-service.js
async function query() {
const response = await axios.get('https://dummyjson.com/products')
const products = await response.data
localStorage.setItem(PRODUCTS_KEY, JSON.stringify(products.products))
return products
}
async function getProductById(id) {
return storageService.get(PRODUCTS_KEY, id)
}
//async-storage-service.js
export const storageService = {
query,
get,
post,
put,
remove,
postMany
}
function query(entityType) {
var entities = JSON.parse(localStorage.getItem(entityType)) || []
return Promise.resolve(entities);
}
function get(entityType, entityId) {
return query(entityType)
.then(entities => entities.find(entity => entity.id === entityId))
}
Redux reducer functions are to be considered pure, synchronous functions. Your getProduct action is calling asynchronous code and it doesn't update any state.
Convert getProduct to an asynchronous action and specify an extraReducers reducer function to handle the fulfilled Promise returned by it.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
const getProduct = createAsyncThunk(
"/products/getProduct",
async (id, thunkAPI) => {
return productsService.getProductById(id);
},
);
const productsSlice = createSlice({
name: 'products',
initialState,
reducers: {
...
},
extraReducers: builder => {
builder
.addCase(getProduct.fulfilled, (state, action) => {
state.product = action.payload; // * Note
});
},
};
*Note: You didn't include what initialState is so we've no way of knowing what state should actually be updated, but it's just a normal Redux state update from here. Tweak this part to fit your actual use case.
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: ""
};
I am migrating my component from a class component to a functional component using hooks. I need to access the states with useSelector by triggering an action when the state mounts. Below is what I have thus far. What am I doing wrong? Also when I log users to the console I get the whole initial state ie { isUpdated: false, users: {}}; instead of just users
reducers.js
const initialState = {
isUpdated: false,
users: {},
};
const generateUsersObject = array => array.reduce((obj, item) => {
const { id } = item;
obj[id] = item;
return obj;
}, {});
export default (state = { ...initialState }, action) => {
switch (action.type) {
case UPDATE_USERS_LIST: {
return {
...state,
users: generateUsersObject(dataSource),
};
}
//...
default:
return state;
}
};
action.js
export const updateUsersList = () => ({
type: UPDATE_USERS_LIST,
});
the component hooks I am using
const users = useSelector(state => state.users);
const isUpdated = useSelector(state => state.isUpdated);
const dispatch = useDispatch();
useEffect(() => {
const { updateUsersList } = actions;
dispatch(updateUsersList());
}, []);
first, it will be easier to help if the index/store etc will be copied as well. (did u used thunk?)
second, your action miss "dispatch" magic word -
export const updateUsersList = () =>
return (dispatch, getState) => dispatch({
type: UPDATE_USERS_LIST
});
it is highly suggested to wrap this code with { try } syntax and be able to catch an error if happened
third, and it might help with the console.log(users) error -
there is no need in { ... } at the reducer,
state = intialState
should be enough. this line it is just for the first run of the store.
and I don't understand where { dataSource } comes from.
I am trying to access the updated state from useReducer inside of an arrow function in a functional component. However, when I call the state, I'm only getting the initial state object.
The reducer function
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE':
return {
...state,
[action.progress.request]: action.progress
}
case 'REMOVE':
const { [action.progress.request]: value, ...rest } = state
return rest
default:
return state
}
}
The react component
const ProgressProvider = ({ children }: Props) => {
const [state, dispatch] = useReducer(reducer, {})
const start = (request) => {
console.log(state) // expected to see updated state, but instead see initial value
// ... do more things
}
const end = (request) => {
console.log(state)
// ...do more things
}
return (
<ProgressContext.Provider value={{ state, start, end }}>
{children}
</ProgressContext.Provider>
)
}
could be used in an api request like this:
const progress = useContext(ProgressContext)
const getData = async params => {
const url = '/my/endpoint'
progress.start(url)
try {
await axios.get(url, { params })
} catch (error) {
console.error(error)
} finally {
progress.end(request)
}
}
I expect in the start and end functions to be able to see an updated state, but I actually see the initial state {}
In order for state to change, you need to dispatch an action. This can either be done via a click of a button or something else entirely. You should update your start function to be along the lines of the following
const start = request => {
const action = {
type: 'UPDATE',
request
};
dispatch(action);
}
The dispatch(action) will cause an update to the state that will be available on render.
I'm trying to handle a form submission to show a loading component when the data fetch is occuring. I'd like to display the data when it's been loaded into my Redux store.
Right now, I've set up my component to use React hooks. While the data loads into my redux store successfully, I'm not sure how to "await" the result of the action being completed. Here's a simplified version of what my component looks like:
const DataPage = (props) => {
const [isLoading, setIsLoading] = useState(false);
const [isError, setError] = useState(false);
useEffect(() => { // Reset Filters when dataSource changes...
setError(false);
setIsLoading(false);
}, [dataSource]);
const handleSubmit = (e, { dataSource }) => {
e.preventDefault();
setError(false)
setIsLoading(true);
//// IDEALLY THIS IS WHERE THE FIX WOULD GO? TURN THIS INTO ASYNC/AWAIT?
props.fetchData({ dataSource, token: localStorage.JWT_TOKEN });
};
return (
<div className="dataPage">
<form className="dataPage__filters" onSubmit={(e) => handleSubmit(e, { dataSource })}>
<DataSelector dataSource={dataSource} setDataSource={setDataSource}/>
<button className="button">
Search
</button>
</form>
{isError && <div>Something went wrong...</div>}
{ isLoading ? ( <div>...Loading </div> ) : (
<div className="dataPage__table">
<DataTable /> // This is connected to my redux-store separately through 'connect'
</div>
)}
</div>
);
};
const mapDispatchToProps = (dispatch) => ({
fetchData: ({ dataSource, token }) => dispatch(startFetchData({ dataSource, token }))
});
export default connect(null, mapDispatchToProps)(DataPage);
The relevant actions (startFetchData, and setData) are located in another file, and look like this:
export const setData = (data) => ({
type: "SET_DATA",
data
});
export const startFetchData = ({ dataSource, filter, filterTarget, token }) => {
return (dispatch) => {
axios.get(`${'http://localhost:8081'}/api/${dataSource}`, { headers: { authorization: token }})
.then((res) => {
dispatch(setData(result));
});
}
};
I'd like to be able to do this without introducing any new dependencies if possible.
A note for those using TypeScript: If you want to await a promise returned by an action using useDispatch() you may see TypeScript complaining about an unnecessary await.
In this case make sure to add the correct typing (see ThunkDispatch) to useDispatch via generics.
Also with useEffect() with async-await syntax make sure to wrap your async code in another closure because useEffect() expects a void return value and Typescript otherwise complains about you returning a Promise.
const dispatch = useDispatch<ThunkDispatch<any, any, Action>>();
useEffect(() => {
(async () => {
const myResult = await dispatch(...);
const anotherResult = await dispatch(...);
// ...
})();
});
I recommend you to use redux-thunk middleware. It's really easy and useful library to able your action to be, instead of objects, functions (including async functions). I'll give you an example:
Store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
import api from './services/api';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
// With extra argument, in this case, my API):
applyMiddleware(thunk.withExtraArgument(api));
);
AuthDuck.js
Giving this duck (types, actions and reducers in the same file, see more here)
// ----------------------------------------------------------------
// Types
// ----------------------------------------------------------------
const Types = {
SIGN_IN_START: 'SIGN_IN_START',
SIGN_IN_SUCCESS: 'SIGN_IN_SUCCESS',
SIGN_IN_FAIL: 'SIGN_IN_FAIL'
};
// ----------------------------------------------------------------
// Actions
// ----------------------------------------------------------------
const signin = function (user) {
// LOOK HERE!
// Redux Thunk able you to return a function instead of an object.
return async function (dispatch, getState, api) {
try {
dispatch({ type: Types.SIGN_IN_START });
const token = await api.access.signin(user);
dispatch({ type: Types.SIGN_IN_SUCCESS, payload: token });
} catch (error) {
dispatch({ type: Types.SIGN_IN_FAIL, payload: error });
}
};
};
export const Actions = { signin };
// ----------------------------------------------------------------
// Reducers
// ----------------------------------------------------------------
export default function reducer(state, action) {
switch (action.type) {
case VeasyCalendarTypes.SIGN_IN_START:
return { ...state, isLoading: true };
case VeasyCalendarTypes.SIGN_IN_SUCCESS:
return { ...state, isLoading: false, token: action.payload };
case VeasyCalendarTypes.SIGN_IN_FAIL:
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
};
I hope to helped you, let me know if it worked for your case :)
Best regards