multiple dispatches with callback in react-hook? - javascript

how to add loading state to true in 'addItem' (reducer case), and set the loading state to false?
But I'm stuck using react-hook
https://codesandbox.io/s/new-water-vczdp
I did it here: https://codesandbox.io/s/react-todolist-3dko3 with older react version.
What to do here?
case "addItem": {
//fakeHttp();
const { toAddItem, items } = state;
const nextId = +items[items.length - 1].id + 1;
return {
items: [...items, { id: nextId, name: toAddItem }],
loading: false
};
}

You don't have to call async functions in your reducer. You have to use other hooks for this. In your case, you should use useCallback hook:
function App() {
const [{ items, toAddItem, loading }, dispatch] = useReducer(
reducer,
initialState
);
const addItem = useCallback(() => {
async function fakeHttp() {
dispatch({ type: "loading" });
await delay(500);
dispatch({ type: "addItem"})
}
fakeHttp();
}, []);
return (
...
<AddList
handleInput={e =>
dispatch({ type: "handleInput", value: e.target.value })
}
toAddItem={toAddItem}
addItem={addItem}
loading={loading}
/>
)
}
I make some changes in your sandbox example. You could see a working example here:
https://codesandbox.io/embed/stupefied-currying-l39il

Related

Initialising a function returns it's name

const [state, setState] = React.useState({ default: () => callApiHere(), defaultTab: " });
useEffect(() => {
console.log(state, 'current state values')
}, [state])
The problem here is whenever I do a setState the Api call isn't happening properly. It returns me the function name callApiHere itself in the console.log
Make api calls in useEffects or event handlers
const [state, setState] = React.useState({
default: {}, // set default value here
defaultTab: ""
});
useEffect(() => {
let ignore = false;
const fetchData = async () => {
const response = await callApiHere();
// skip execution in invalid scope
if (ignore) { return; }
setState(s => ({ ...s, default: response }))
}
fetchData()
return () => {
ignore = true;
}
}, [])
when using state use chaining ?. or ?? to handle missing data accordingly
{ state?.default?.property ?
<div>...</div>
: null
}
You can check the beta docs about useEffect life cycles
Hope it helps

Apply loading state to the items that are clicked in an array - React

I'm trying to set loader only for the item that is clicked. Currently, the problem I'm facing is, it applies loading state to all the items in the array.
I created a working example using CodeSandbox. Could anyone please help?
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const cars = [{ name: "Benz" }, { name: "Jeep" }, { name: "BMW" }];
const handleClick = () => {
// API Call
dispatch({ type: "SET_LOADING", loading: true });
// Loading stops once response is recieved.
};
return (
<div className="App">
{cars.map(({ name }) => (
<button onClick={handleClick}>
{state.loading ? "Loading..." : name}
</button>
))}
</div>
);
}
Instead of having a boolean ,you could use the name of the car for the loading property & then checking the value of loading property should suffice.I am assuming names are unique,otherwise add an id property & check with it.
import { useReducer } from "react";
import "./styles.css";
const initialState = {
loading: false
};
const reducer = (state, action) => {
switch (action.type) {
case "SET_LOADING":
return { ...state, loading: action.loading };
default:
return state;
}
};
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const cars = [{ name: "Benz" }, { name: "Jeep" }, { name: "BMW" }];
const handleClick = (e,name) => {
// API Call
dispatch({ type: "SET_LOADING", loading: name });
// Loading stops once response is recieved.
// Assing loading = '' once response is received
};
return (
<div className="App">
{cars.map(({ name }) => (
<button onClick={(e)=>{handleClick(e,name)}}>
{state.loading===name ? "Loading..." : name}
</button>
))}
</div>
);
}

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: ""
};

REACT, Help me understand prevState

What is the difference between defining this counterHandler like this
counterHandler = () => {
this.setState(() => {
return { times: this.state.times + 1 }
});
}
And this?
counterHandler = () => {
this.setState((prevState) => {
return { times: prevState.times + 1 }
});
}
Does the state from a component always gets passed to setState automatically?
If you only use One data inside state, you dont need to make callback of prevState,
but if your state more than one, you need to callback of prevstate because this will make your other and previous data will not be lost.
for example
const [state, setState] = useState({
loading: false,
data: []
})
const handleLoading = () => {
setState({
loading: true
})
}
const handleData = () => {
setState({
data: [a,b,c] // you will lost your loading = true
})}
const handleData = () => {
setState((prevState) => {
...prevState,
data: [a,b,c] // you still have loading = true
})
}

Redux returning undefined promise

I am stuck trying to figure out Redux. Currently all my "painting" prop is a promise with undefined value:
In page component:
componentDidMount = () => {
this.props.setPaintingToProps(paintingId);
}
...
const mapStateToProps = state => {
return {
painting: state
};
};
const mapDispatchToProps = dispatch => {
return {
setPaintingToProps: paintingId => {
dispatch({ type: "SET_PAINTING", id: paintingId });
}
};
};
And In reducer:
case "SET_PAINTING":
paintingService.getDetails(action.id).then(data=>{
return {...state,
...data}
})
break;
The reducer method runs and the data is correct, but in state it is PromiseĀ {<resolved>: undefined}.
Thank you in advance, if there is any more info needed to solve this please ask.
You should use middleware for async call for example redux-thunk to create async action and then dispatch your data
export const setPaintingToProps = (paintingId) =>
(dispatch) => {
paintingService.getDetails(paintingId).then(data => {
dispatch({ type: "SET_PAINTING", payload: data });
})
};
const mapDispatchToProps = dispatch => {
return {
setPaintingToProps: paintingId => dispatch(setPaintingToProps(paintingId));
}
};
};
case "SET_PAINTING":
return {...state,
action.payload}
})
break;
let me know if it works for you

Categories

Resources