how to map nested array within a mapped array in reducer - javascript

TLDR:How would i better map an array within a initalState prop
The following code im trying to attempt is to retrieve all postIds from the like array, and compare post.id to like.postId
However
like.postId is undefined.
post.id is available on the console log, the difference between posts, and likes is that posts are mapped on the client side, and likes are not. So im trying to do the mapping in the reducer because i would need to get amount of like counts stored for each post, and map it to its respected post.id.
The reason why i would need to set the values within the likes initialState is because i would need to use this following action to upvote posts
case ADD_LIKE:
// console.log(action.id) // renders post id which is 2
// console.log(state.posts) // logs posts array
// console.log(state.posts)
return {
...state,
likes: state.likes + 1
};
if i do something like this like[0].postId it will only get the values for that like.id only. I need to be able to get all of likes from all posts, and compare like.postId to post.id and then set the value.
And retrieve the count like
{this.props.likes}
just to get an idea what the array looks like.
This is example posts array, and within the Posts array, you have the likes array.
Here is how im calling posts
export const GetPosts = () => {
return (dispatch, getState) => {
return Axios.get('/api/posts/myPosts')
.then( (res) => {
const data = res.data
const likes = res.data // gets the first item within array, and shows likes.
const myLikes = likes.map( (post) => {
return post.Likes
})
console.log(myLikes)
dispatch({type: GET_POSTS, data, myLikes})
})
}
}
reducer
export default (state = initialState, action) => {
switch (action.type) {
case GET_POSTS:
console.log(action.data[0].Likes.length)
return {
...state,
posts: action.data, // maps posts fine
// set likes to but it only gets the first post, when it should get all posts
likes: action.data.map( (post) => {
action.myLikes.map( (like) => {
// if(post.id === like.postId){
console.log(like) // renders an array of likes for all posts
console.log(like.postId) // renders undefined,
// }
})
})
}
}
this is how its being mapped
PostList.js
render(){
const {posts} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={post.id} style={Styles.myPaper}>
{/* {...post} prevents us from writing all of the properties out */}
<PostItem
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
)
}
GetPosts action is called within this component
class Posts extends Component {
state = {
posts: [],
loading: true,
isEditing: false,
}
async componentWillMount(){
await this.props.GetPosts();
this.setState({ loading: false })
const reduxPosts = this.props.myPosts;
const ourPosts = reduxPosts
console.log(reduxPosts); // shows posts line 35
}
render() {
const {loading} = this.state;
const { myPosts} = this.props
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList posts={myPosts}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
myPosts: state.post.posts
})
const mapDispatchToProps = (dispatch, state) => ({
GetPosts: () => dispatch( GetPosts())
});
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Posts));

When you use Array.map() it will create a new array with the results of calling a provided function on every element in the calling array, so you will get an array of arrays (array of each post's likes array) in order to solve your issue and get an array of likes you need to use the reducer function as follows:
export const GetPosts = () => {
return (dispatch, getState) => {
return Axios.get('/api/posts/myPosts')
.then( (res) => {
const data = res.data
const likes = res.data // gets the first item within array, and shows likes.
const myLikes = likes.reduce( (acc,post) => {
return acc.concat(post.Likes)
},[])
console.log(myLikes)
dispatch({type: GET_POSTS, data, myLikes})
})
}}

Related

How to delete a specific item from localstorage in react redux

How can I remove a specific item (by id) from localstorage using react (redux - persist)? handleSubmit is working fine, but handleDelete, is not. I have this:
handleSubmit = event => {
event.preventDefault();
this.props.addWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
};
handleDelete = (event, id) => {
this.props.deleteWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
}
const mapStateToProps = state => ({
allWeather: state.allWeather
});
const mapDispatchToProps = dispatch =>
bindActionCreators(WeatherActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(WeatherList);
And button in form to call handleDelete:
<form onSubmit={this.handleDelete}><button type="submit" id="add" onClick={this.handleDelete}>Remove City</button></form>
My localstorage:
allWeather: "[{\"id\":0.5927975642362653,\"city\":\"Toronto\"},{\"id\":0.8124764603718682,\"city\":\"Fortaleza\"},{\"id\":0.9699736666575081,\"city\":\"Porto\"},{\"id\":0.852871998478355,\"city\":\"Tokio\"},{\"id\":0.8854642571682461,\"city\":\"New York\"}]"
My reducer:
export default function allWeather(state = [], action) {
switch (action.type) {
case "ADD_WEATHER":
return [...state, { id: Math.random(), city: action.payload.city }];
case "DELETE_ITEM":
return [...state, state.weatherCity.filter((event, id) => id !== action.payload.id)];
default:
return state;
}
}
And actions:
export const deleteWeather = id => ({
type: "DELETE_ITEM",
payload: { id }
});
I appreciate any help.
Your problem is that you are using the spread operator, which copies the content of the current state first. Then you are adding the items that were returned from the filter method. So you aren't deleting but adding. To delete from an array use the filter method only, without the spread operator like that:
return state.filter( (city) => city.id !== action.payload.id )
Also the state is an array, not an object, so this is invalid state.weatherCity.

Redux: dispatching an action multiple times results to too many api requests

Using an api for anime called Jikan, I'm trying to display promo thumbnails of new anime shows.
I'm using two api calls, one to get the new anime shows:
export const get_new_anime = () =>
`${base_url}search/anime?q&order_by=score&status=airing&sort=desc`;
and one for getting the videos (containing promos) of anime by getting its id.
export const get_news = (anime_id) => `${base_url}anime/${anime_id}/videos`;
In my home page, here I am mapping the shows, returning a component for each anime:
<Promos>
{new.map((anime, index) => (
<Anime key={anime.mal_id} index={index}></Anime>))}
</Promos>
And for each Anime component, I have a useEffect which uses useDispatch for every new id
const Anime = ({ id, index }) => {
const dispatch = useDispatch();
const loadDetailHandler = () => {
// eslint-disable-line react-hooks/exhaustive-deps
dispatch(loadDetail(id));
useEffect(() => {
loadDetailHandler(id);
}, [id]); // eslint-disable-line react-hooks/exhaustive-deps
const promo = useSelector((state) => state.detail.promo);
const isLoading = useSelector((state) => state.detail.isLoading);
return (
<PromoBox
style={
!isLoading
? { backgroundImage: `url("${promo[index][0].image_url}")` }
: null
}
></PromoBox>);
};
Here is how my promoReducer looks like:
const initState = {
promo: [],
isLoading: true,
};
const promoReducer = (state = initState, action) => {
switch (action.type) {
case "LOADING_PROMO":
return {
...state,
isLoading: true,
};
case "GET_DETAIL":
return {
...state,
promo: [...state.promo, action.payload.promo],
isLoading: false,
};
default:
return { ...state };
}
};
export default promoReducer;
and here is the promoAction:
export const loadPromo = (id) => async (dispatch) => {
dispatch({
type: "LOADING_PROMO",
});
const promoData = await axios.get(get_promos(id));
dispatch({
type: "GET_DETAIL",
payload: {
promo: promoData.data.promo,
},
});
};
While it does return the promo data as the action is dispatched, the problem is that in some instances of dispatching, no data is returned. Here is a screenshot from redux devtools to show what I mean:
and I was trying to get the promos of all the new anime, in which I was expecting to get 50 results of promo data. In devtools, you can see I only got 9 of them. This is followed by an error 429 (too many requests):
How can I resolve this issue? And is there a better way to do this, because this seems like bad practice:
Well it seems that you're limited by the api itself and it's threshold for the number of request per unit of time. There should probably be a request that allows you to pass multiple anime ids to get request in order to avoid requesting details for each anime individually.

React hooks, component is reading old data from Redux

My component is set to display a list of books, as card thumbnails. Each item from the list of books is generated by this component.
Each Card has a favorites icon, when clicking it adds the book to favoriteTitles array. By pressing again on the favorites icon it removes it from the list.
const Card = ({ title, history }) => {
const dispatch = useDispatch();
const { favoriteTitles } = useSelector(({ titles }) => titles);
const { id, name, thumbnail } = title;
const [favorite, setFavorite] = useState(favoriteTitles?.some(item => item.titleId === title.id));
const handleFavoriteClick = () => {
const isFavorite = favoriteTitles?.some(item => item.titleId === title.id);
if (isFavorite) {
dispatch(removeFavoriteTitle(title));
setFavorite(false);
} else {
dispatch(addFavoriteTitle(title));
setFavorite(true);
}
};
return (
<CardContainer>
<Thumbnail thumbnail={thumbnail} />
{name}
<FavesIcon isActive={favorite} onClick={handleFavoriteClick} />
</CardContainer>
);
};
The issue with this component is when you press once on FavesIcon to add, and if you changed your mind and want to remove it and press right away again, the favoritesTitles array still has the old value.
Let's suppose our current favorites list looks like this:
const favoritesTitles = [{titleId: 'book-1'}];
After pressing on favorites icon, the list in Redux gets updated:
const favoritesTitles = [{titleId: 'book-1'}, {titleId: 'book-2'}];
And if I press again to remove it, the favoritesTitles array inside the component is still the old array with 1 item in it. But if I look in Redux the list updated and correct.
How component should get the updated Redux value?
Update
I have specific endpoints for each action, where I add or remove from favorites:
GET: /users/{userId}/favorites - response list eg [{titleId: 'book-1'}, {titleId: 'book-2'}]
POST: /users/me/favorites/{titleId} - empty response
DELETE: /users/me/favorites/{titleId} - empty response
For each action when I add or remove items, on success request I dispatch the GET action. Bellow are my actions:
export const getFavoriteTitles = userId =>
apiDefaultAction({
url: GET_FAVORITE_TITLES_URL(userId),
onSuccess: data => {
return {
type: 'GET_FAVORITE_TITLES_SUCCESS',
payload: data,
};
},
});
export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
return dispatch(
apiDefaultAction({
method: 'POST',
url: SET_FAVORITE_TITLES_URL,
data: {
titleId: id,
},
onSuccess: () => {
dispatch(getFavoriteTitles(userId));
return { type: 'SET_FAVORITE_TITLE_SUCCESS' };
},
})
);
};
My reducers are pretty straight forward, I'm not mutating any arrays. Since only GET request is returning the list of array, I don't do any mutating in my reducers:
case 'GET_FAVORITE_TITLES_SUCCESS':
return {
...state,
favoriteTitles: action.payload,
};
case 'SET_FAVORITE_TITLE_SUCCESS':
return state;
case 'DELETE_FAVORITE_TITLE_SUCCESS':
return state;
It seems that by the time you click FavesIcon second time after adding to favourites, GET: /users/{userId}/favorites request is still pending and favoriteTitles list is not updated yet. That's why the component still contains an old value.
You need to update favoriteTitles list right away after triggering addFavoriteTitle or removeFavoriteTitle actions, without waiting GET_FAVORITE_TITLES_SUCCESS action to be dispatched. This pattern is called 'Optimistic UI':
export const toggleFavorite = itemId => {
return {
type: 'TOGGLE_FAVORITE',
payload: { itemId },
};
}
export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
dispatch(toggleFavorite(id));
return dispatch(
...
);
};
export const removeFavoriteTitle = (userId, id) => (dispatch, getState) => {
dispatch(toggleFavorite(id));
return dispatch(
...
);
};
And your reducer can look something like this:
case 'TOGGLE_FAVORITE':
return {
...state,
favoriteTitles: state.favoriteTitles.map(item => item.titleId).includes(action.payload.itemId)
? state.favoriteTitles.filter(item => item.titleId !== action.payload.itemId)
: [...state.favoriteTitles, { titleId: action.payload.itemId }],
};
UPD. Please, check out a minimal working sandbox example

Looping through an object in react

Learning react
Trying to loop through an object from an API call that returns a json object and display it but struggling to implement it
This is the component that should render it
export default class ProfilePage extends Component {
constructor() {
super();
this.state = { data: '' };
}
mapObject(object, callback) {
return Object.keys(object).map(function (key) {
return callback(key, object[key]);
})
}
async componentDidMount() {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const json = await response.json();
// console.log(json)
this.setState({ data: json });
}
render() {
const data = this.state.data
console.log(data)
return (
<div className="row">
{Object.values(data).map(data => {
<div key={key}>
{data[key]}
</div>
})
}
Woerkkk please
</div>
);
}
}
All I'm getting is a blank screen.
in the console i get the error 'key' is not defined no-undef
You are missing a return statement in your map for your render method.
Edit: Key is not returned from Object.values
Either reconfigure with a return statement like so:
{Object.keys(data).map(key => {
return (<div key={key}>
{data[key]}
</div>);
})
Or alternatively you can implicitly return from arrow function using brackets
{Object.keys(data).map(key => (
<div key={key}>
{data[key]}
</div>)
))
Using Object.values(myObj) you can get all object values as a array. So, with this array, you can iterate over the array and show your items, like this:
{Object.values(myObj).map(value => <p>{value}</p>)}
Don't forget use key prop when iterating.
You can use useState and useEffect to fetch the object data
const App = () => {
const [objData, setObjData] = useState({});
const [objItems, setObjItems] = useState([]);
const fetchObj = async () => {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const data = await response.json();
setObjData(data);
setObjItems(data.items);
}
useEffect(() => {
fetchObj()
},[]);
return(
<div>
<h1> Order Id :{objData.order_id}</h1>
// or any other objData keys
<h1>Items : </h1>
<ul>
{
objItems.map((i, idx) => {
return(
<li key={idx}>Name : {i.name} , Category: {i.category}, Price: {i.price}, Currency: {i.currency}</li>
)
})
}
</ul>
</div>
)
}
export default App;

How to implement buttons that receive different data from the API depending on which button is pressed on Redux?

I currently have a code that displays Api data on the page. This is a list :
API has this structure:
{"hij":{
"low":[{......},{.....}],
"all":[{....},{......}]}
}
How to implement such buttons on Redux? Can you help me a little bit? I watched a few dozen videos on YouTube, read many articles and no examples of how to make buttons which to take different data from API....
On React I implement it this way(App.js):
class App extends React.Component {
state = {
day: 1,
data: [],
filteredData: [],
search: "",
shift: "low"
};
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(`...`);
const data = (await response.json()).body;
this.setState({data, shift: Object.keys(data)[0]}, this.filter);
};
loadDay = day => {
this.setState({ day }, this.fetchData);
};
updateSearch = e => {
this.setState({search: e.target.value});
};
filter = () => {
this.setState(({ search, data, shift }) => {
const s = search.toLowerCase();
return {
filteredData: data[shift].filter(n =>
n.term.toLowerCase().includes(s)
)
};
});
};
onClick = ({target: { dataset: { shift }}}) => {
this.setState(() => ({ shift }), this.filter);
};
render() {
const { search, shift, data, filteredData } = this.state;
return (
<div>
<TableSearch value={search}
onChange={this.updateSearch}
onSearch={this.filter}/>
{days.map((day, i) => (
<button key={day}
onClick={() => this.loadDay(i)}
className={i === this.state.day ? "active" : ""}>{day}</button>
))}
<br />
{Object.keys(data).map(n => (
<button data-shift={n}
onClick={this.onClick}
className={n === shift ? "active" : ""}>{n} shift</button>
))}
<TableData data={filteredData} />
</div>
);
}
}
There are a number of things to change to make this a redux compatible application, the least of your concerns would be buttons. So instead of answering the button part directly, here's an annotated refactor of your application to Redux:
import { createStore, applyMiddleware } from 'redux';
import { connect, Provider } from 'react-redux';
import thunk from 'redux-thunk';
// Take out common functionality into seperate functions, such as
// running a search here.
function searchFilter (search, data) {
return data.filter(n => n.term.toLowerCase().includes(search));
}
// This is your reducer function which updates the global redux state,
// depending on which action you dispatch:
// see https://redux.js.org/basics/reducers
function reducer (state = {}, action) {
state = { ...state }
switch (action.type) {
case 'SET_SEARCH':
state.search = action.search.toLowerCase();
break;
case 'RUN_FILTER':
state.shift = action.shift || state.shift;
state.search = action.search || state.search;
state.filteredData = searchFilter(state.search, state.data[state.shift]);
break;
case 'LOAD_DATA_START':
state.day = action.day;
break;
case 'LOAD_DATA_END':
state.data = action.data;
state.shift = Object.keys(data)[0];
state.filteredData = searchFilter(state.search, state.data[state.shift]);
break;
}
return state;
}
// This is your store object which contains an initial state, and a reducer
// that will be used for dispatched actions.
// see https://redux.js.org/basics/store
//
// Redux-thunk is used as middleware to support async data fetching which you will
// also need to read up on, although you don't really need to know how it
// works at first.
// see https://github.com/reduxjs/redux-thunk
const store = createStore(
reducer,
{
day: 1,
data: [],
filteredData: [],
search: "",
shift: "departure"
},
applyMiddleware(thunk)
);
// This is a "thunk" called fetch data, again you can read more
// about thunks in the redux-thunk docs
// see https://github.com/reduxjs/redux-thunk
function fetchData (day) {
return async (dispatch) => {
dispatch({ type: 'LOAD_DATA_START', day });
const response = await fetch(`https://api.iev.aero/api/flights/${days[this.state.day]}`);
const data = (await response.json()).body;
dispatch({ type: 'LOAD_DATA_END', data });
}
}
const days = ["23-08-2019", "24-08-2019", "25-08-2019"];
// Stripped down component, it does not handle any of its own state
// all state is passed to it through the redux connect HOC.
class Root extends React.Component {
componentDidMount() {
this.props.onFetchData(this.props.day);
}
render() {
const { search, shift, data, filteredData, onFilter, onSetSearch, onFetchData } = this.props;
return (
<div>
<TableSearch value={search}
onChange={(e) => onSetSearch(e.target.value)}
onSearch={() => onFilter()} />
{days.map((day, i) => (
<button key={day}
onClick={() => onFetchData(day)}
className={i === day ? "active" : ""}>{day}</button>
))}
<br />
{Object.keys(data).map(n => (
<button data-shift={n}
onClick={(e) => onFilter({ shift: e.target.dataset.shift })}
className={n === shift ? "active" : ""}>{n} shift</button>
))}
<TableData data={filteredData} />
</div>
);
}
}
// This is the "connected" version of the component, which is
// your component wrapped in a connect HOC. When the reducer function
// is run, the two functions below will be executed and your component
// inside will re render.
//
// You can read more about this one in react-redux
// https://react-redux.js.org/
const ConnectedRoot = connect(
(state) => state,
(dispatch) => ({
onFilter: (args) => dispatch({ type: 'RUN_FILTER', ...args }),
onSetSearch: (search) => dispatch({ type: 'SET_SEARCH', search }),
onFetchData: (day) => dispatch(fetchData(day))
})
);
// This is your top level component that you would call in ReactDOM.render
// The Provider component is part of react-redux, and you can read about it
// there, but in most cases it is sufficient to drop it at the very top level
// of your application.
// https://react-redux.js.org/
const App = () => (
<Provider store={store}>
<ConnectedRoot />
</Provider>
);

Categories