I'm working on a todo app and everything works fine when it first loads in. However, when I add a new todo and the store updates, I get the unique key warning, when the key is defined in the array components:
render() {
const todoList = this.props.todos.map(todo => {
return <Todo todo={todo} key={todo._id}/>
})
return (
<div className={styles.todoContainer}>
{todoList}
</div>
);
}
Todo Component:
return (
<div className={styles.todo}>
<h2 className={styles.todoText}>{props.todo.name}</h2>
</div>
);
Adding todo:
//actions.js
export function addTodo(todo){
let config = {
headers: {
token: localStorage.getItem('token')
}
};
return function(dispatch){
return axios.post('http://localhost:8082/api/todos/create', todo, config)
.then(msg => {
dispatch({type: ADD_TODO, payload: todo})
})
.catch(err => console.log(err));
}
}
//reducer.js
case ADD_TODO:
const data = [...state.data];
data.push(action.payload);
return {
...state,
data: data
};
Is this a problem I should worry about fixing, or is it a bug? Thanks!
Related
I am trying hard to remove an array. But it is not work. After delete my array length and data remain same.
This is my frontend react web page
renderAdmin = (id) => {
if(this.props.auth.user.userType==='normal') return(<td></td>);
return (
<React.Fragment>
<td>
<button onClick={() => this.props.changeStatus(id)}
className="ui button" >Change Status</button>
</td>
<td>
<button onClick={() => this.props.deleteUser(id)}
className="ui button" >Delete User</button>
</td>
</React.Fragment>
);
}
Here i am taking id of my array.
Now this is my redux action creator
export const deleteUser = (id) => (dispatch,getState) => {
const user = JSON.parse(localStorage.getItem('user'));
axios.delete(`/api/users/remove/${id}`, {
headers: {
'Authorization': `${user.token}`
}
}).then((res) => {
console.log(res.data);
dispatch({
type: 'DELETE_USER',
payload: res.data
})
dispatch({
type: 'CREATE_ERROR',
payload: 'User Delete'
})
})
.catch((error) => {
console.error(error)
})
}
Here first i remove it from backend server. That works fine. Then i send res.data to my reducer payload. DELETE_USER not working well. but CREATE_ERROR works fine.
This is my reducer
export default function (state = [] , action) {
// console.log(action)
switch(action.type) {
case 'ALL_USER' : return action.payload;
case 'EDIT_USER' :
return state.map(user=>{
if(user._id === action.payload._id){
return action.payload;
}
else {
return user;
}
});
case 'DELETE_USER' :
return state.filter(user=>
user !== action.payload
)
default : return state;
}
}
After pressing delete button, data remove from server but redux store always remain same. Please help me.
Thank You.
It might be due to the objects not being truly equal try filtering on the id instead:
return state.filter(user=>
user._id !== action.payload._id
)
Or check for deep equality.
I want to implement an action which gets item by id, so I've created fetchItemAction(), as follows:
export const fetchItemAction = () => (dispatch) => {
dispatch({
type: FETCH_ITEM_REQUEST,
});
return axios.get(`${url}/notes/5d4724cd62087b0e141f75a4`)
.then(({ data }) => {
console.log(data);
dispatch({
type: FETCH_ITEM_SUCCESS,
data,
});
})
.catch((error) => {
dispatch({
type: FETCH_ITEM_FAILURE,
});
});
};
Then, I try to set item field in State in my reducer:
const initialState = {
isAuthenticated: false,
user: {},
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ITEM_REQUEST:
return {
...state,
isLoading: true,
};
case FETCH_ITEM_SUCCESS:
return {
...state,
item: action.data,
isLoading: false,
};
}
};
Then, I try to get those data in Details component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchItemAction } from 'actions/actions';
class Details extends Component {
componentDidMount() {
const { fetchItem } = this.props;
fetchItem();
}
render() {
const { item, isLoading } = this.props;
return (
<>
{console.log(item)}
{/* <p>{item.title}</p> */}
</>
);
}
}
const mapStateToProps = ({ item, isLoading }) => ({ item, isLoading });
const mapDispatchToProps = dispatch => ({
fetchItem: () => dispatch(fetchItemAction()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Details);
As a result, I'm getting following data in console:
Apart from two undefinded the result looks good because there is correct response from my backend.
But, when I try to uncomment <p>item.title</p> line in Details.js, the app crash:
TypeError: Cannot read property 'title' of undefined
I also implemented correctly fetchItemsAction(), addItemAction() and deleteItemAction() which are very similar but I have no idea what is wrong in fetchItemAction().
This is an asynchronous issue. componentDidMount is called when the component is mounted. Then, you're calling fetch. So on your first render, item is undefined. Once the data is returned, the render is triggered again with item data.
So, just check if item is defined:
render() {
const { item, isLoading } = this.props;
return (
<>
{console.log(item)}
{item && <p>{item.title}</p>}
</>
);
}
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})
})
}}
I'm making an application where I have to grab certain data from the Github API. I need to grab the name, url, language and latest tag. Because the latest tag is in a separate url, I need to make another fetch call there to grab that data.
I'm running into a certain amount of errors.
1st being the typeError cannot read property 'name' of undefined. I'm sure this is from the fetch call to the tag url where there isn't any data. I'm not really sure how to check if it's undefined. I've tried calling checking to see if the typeof data is undefined and so on but still get the error.
2nd problem being my tag url data doesn't show up with the other data. I'm sure I'm chaining the data wrong because when I click the add button it shows up.
Here is my code:
import React, { Component } from 'react'
import './App.css'
class App extends Component {
state = {
searchTerm: '',
repos: [],
favourites: []
}
handleChange = e => {
const { searchTerm } = this.state
this.setState({ searchTerm: e.target.value })
if (searchTerm.split('').length - 1 === 0) {
this.setState({ repos: [] })
}
}
findRepos = () => {
const { searchTerm } = this.state
// First api call here
fetch(`https://api.github.com/search/repositories?q=${searchTerm}&per_page=10&access_token=${process.env.REACT_APP_TOKEN}
`)
.then(res => res.json())
.then(data => {
const repos = data.items.map(item => {
const { id, full_name, html_url, language } = item
const obj = {
id,
full_name,
html_url,
language,
isFavourite: false
}
// Second api call here. I need the data from map to get the tags for the correct repo
fetch(`https://api.github.com/repos/${full_name}/tags`)
.then(res => res.json())
.then(data => {
obj.latest_tag = data[0].name
})
.catch(err => console.log(err))
return obj
})
this.setState({ repos })
})
.catch(err => console.log(err))
}
render() {
const { searchTerm, repos, favourites } = this.state
return (
<div className="App">
<h1>My Github Favorites</h1>
<input
type="text"
placeholder="search for a repo..."
value={searchTerm}
onChange={e => this.handleChange(e)}
onKeyPress={e => e.key === 'Enter' && this.findRepos()}
/>
<button
type="submit"
onClick={this.findRepos}>
Search
</button>
<div className="category-container">
<div className="labels">
<h5>Name</h5>
<h5>Language</h5>
<h5>Latest Tag</h5>
</div>
// Here I list the data
{repos.map(repo => (
<div key={repo.id}>
<a href={repo.html_url}>{repo.full_name}</a>
<p>{repo.language}</p>
{repo.latest_tag ? <p>{repo.latest_tag}</p> : <p>-</p>}
<button onClick={() => this.addToFavs(repo)}>Add</button>
</div>
))}
<h1>Favourites</h1>
{favourites.map(repo => (
<div key={repo.id}>
<a href={repo.html_url}>{repo.full_name}</a>
<p>{repo.language}</p>
<p>{repo.latest_tag}</p>
<button>Remove</button>
</div>
))}
</div>
</div>
)
}
}
export default App
If you use Promise.all(), you could rewrite your code like the following.
findRepos = () => {
const { searchTerm } = this.state;
// First api call here
const first = fetch(
`https://api.github.com/search/repositories?q=${searchTerm}&per_page=10&access_token=${
process.env.REACT_APP_TOKEN
}`
);
// Second api call here. I need the data from map to get the tags for the correct repo
const second = fetch(`https://api.github.com/repos/${full_name}/tags`);
Promise.all([first, second])
.then((res) => Promise.all(res.map(r => r.json())))
.then([data1, data2] => {
data1.then((firstData) => {
/*Do something you want for first.*/
});
data2.then((secondData) => {
/*Do something you want for second.*/
});
})
.catch((err) => console.log(err));
};
Hope this works for you.
I need to render a component after data is fetched. If try to load data instantly, component gets rendered but no data is show.
class App extends React.Component {
//typical construct
getGames = () => {
fetch(Url, {})
.then(data => data.json())
.then(data => {
this.setState({ links: data });
})
.catch(e => console.log(e));
};
componentDidMount() {
this.getGames();
}
render() {
return (
<div className="App">
<Game gameId={this.state.links[0].id} /> //need to render this part
after data is received.
</div>
);
}
}
You could keep an additional piece of state called e.g. isLoading, and render null until your network request has finished.
Example
class App extends React.Component {
state = { links: [], isLoading: true };
getGames = () => {
fetch(Url, {})
.then(data => data.json())
.then(data => {
this.setState({ links: data, isLoading: false });
})
.catch(e => console.log(e));
};
componentDidMount() {
this.getGames();
}
render() {
const { links, isLoading } = this.state;
if (isLoading) {
return null;
}
return (
<div className="App">
<Game gameId={links[0].id} />
</div>
);
}
}
You can do like this using short circuit.
{
this.state.links && <Game gameId={this.state.links[0].id} />
}
Can we use the pattern of "Render-as-you-fetch" to solve the problem.
Using a flag to check whether loading is complete doesn't look like a clean solution..