How to pass argument to function in reactjs? - reactjs

How can I send sport_id form getSport to getEvents to show each sports events?
Can I put getSport function to other component, call and use it in this component?
events json:
[
{
"id": "912653",
"time": "1536471082",
"time_status": "1",
"league": {
"id": "900",
"name": "Hong Kong 2nd Division",
"cc": "hk"
},
"home": {
"id": "13767",
"name": "Yau Tsim Mong",
"image_id": "193606",
"cc": "hk"
},
"away": {
"id": "63770",
"name": "Tuen Mun SA",
"image_id": "56045",
"cc": "hk"
},
"timer": {
"tm": 74,
"ts": 25,
"tt": "1",
"ta": 0
},
"scores": {}
}
]
sports json:
[
{
"id": 8,
"name": "Rugby Union",
"is_active": null,
"slug": "rugby-union"
}
]
Here is my code:
import React, { Component } from "react";
import axios from "axios";
import moment from "moment";
export default class Feutred extends Component {
state = {
sports: [],
events: [],
isLoading: true,
errors: null
};
getSports() {
axios
.get("/api/v1/sports.json")
.then(response =>
response.data.map(sport => ({
id: sport.id,
name: sport.name,
slug: sport.slug
}))
)
.then(sports => {
this.setState({
sports,
isLoading: false
});
})
.catch(error => this.setState({ error, isLoading: false }));
}
getEvents() {
axios
.get("/api/v1/events?sport_id=${sport_id}")
.then(response =>
response.data.map(event => ({
id: event.id,
time: event.time,
league: event.league,
time_status: event.time_status,
homeTeam: event.home,
awayTeam: event.away
}))
)
.then(events => {
this.setState({
events,
isLoading: false
});
})
.catch(error => this.setState({ error, isLoading: false }));
}
componentDidMount() {
this.getSports();
(this.interval = setInterval(
() => this.getEvents({ time: Date.now() }),
12000
));
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { sports, isLoading } = this.state;
return (
<React.Fragment>
{!isLoading ? (
sports.map(sport => {
const { id, name } = sport;
return (
<div key={sport.id}>
<div className="text">
<p className="meta">
<span className="matchinfo">
<span className="block">time</span>
<span className="block">timestatus</span>
</span>
</p>
<h3>
home-team vs aya tream
</h3>
<p className="league">
<a className="watchlive" href="">
<span className="icon" />
<span>Watch live</span>
</a>
<span>{sport.name} - league cc - league name</span>
</p>
</div>
</div>
);
})
) : (
<p>Loading...</p>
)}
</React.Fragment>
);
}
}

Just destructure it - load sports in one component then render some <EventsLoadingComponent /> passing sport id as prop ...
HINT: Use if(isLoading) return <p>Loading...</p> in render before 'main return' - no need to use ternary operator in return JSX.
UPDATE:
render() {
const { sports, isLoading } = this.state;
if(isLoading) return <p>Loading...</p>
return (
<React.Fragment>
{sports.map(sport => <EventsLoadingComponent sport={sport}/>}
</React.Fragment>
);
}
Move getEvents into <EventsLoadingComponent/> - you'll be fething for events related to this.props.sport.id and render them. This way each of them can be separately updated.
Remember to use key in the topmost html element.
UPDATE #2:
can you please give your code comparison with my code ?
Your code - linear, procedural, 'flat template-driven', forcing async to be sync, all-in-one-component ... while html is a (flatten view of) tree structure.
React thinking (generally, not my code only) - more OO, building tree of objects closer related to data and view structure, giving them own responsibility (data handling, view). Easier to read, expand (destructure further details to components - even one-liners), suitable to decorating, easy to manage ... and reuse.
Often object in structure renders only passed children (or nothing) only providing functionality. Available level of complexity is greater, communication within this structure is far easier (and less dependent) than (it could be done) in html.

Something like this:
getEvents({ id }) {
axios
.get(`/api/v1/events?sport_id=${id}`)
...
}
componentDidMount() {
this.getSports()
.then(() => {
return Promise
.all(this.state.sports.map(this.getEvents))
});
...
}
Note:
You need to refine the way you save the data because you need to know which events are for which sport.

Related

Unable to read the json object stored in React state

import React from 'react';
import axios from 'axios';
class Quiz extends React.Component {
constructor(props) {
super(props);
this.state = {
showInstruction: true,
questionIndex: 0,
isLoading: true,
questions: ''
};
}
proceedHandler = () => {
this.setState({
showInstruction: false
})
}
handleQuestion = (event) => {
event.preventDefault();
console.log('show next question');
}
componentDidMount() {
console.log("After mount! Let's load data from API...");
axios({
method: "GET",
url: "/apis/questions"
}).then(response => {
console.log(response.data);
this.setState({ questions: response.data });
this.setState({ isLoading: false });
});
}
render() {
if (this.state.showInstruction) {
return (
<div>
<h1>Welcome to Quiz</h1>
<p>Quiz instructions goes here</p>
<button type="button" onClick={this.proceedHandler}>Proceed</button>
</div>
)
}
const { isLoading, questions } = this.state;
console.log(this.state['questions'][0]);
console.log(questions[0]);
if (isLoading) {
return <div className="App">Loading...</div>;
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<div onChange={this.onChangeValue}>
{/* {questions[0]} */}
</div>
<button onClick={this.handleQuestion}>Next</button>
</form>
</div>
)
}
}
export default Quiz;
My sample API content looks like the below. Right now making the api call to local file which is stored inside Public folder. Path is public/apis/questions.
[
{
id: 0,
question: `What is the capital of Nigeria?`,
options: [`New Delhi`, `Abuja`, `Aba`, `Onisha`],
answer: `Abuja`
},
{
id: 1,
question: `What is the capital of India?`,
options: [`Punjab`, `Awka`, `Owerri`, `Enugu`],
answer: `New Delhi`
}
]
I am building a quiz app and above is my code. I try to fetch the questions from api and render them one by one based on state. I am using axios to fetch the data inside componentDidMount and I can see the this.state.questions is updated with the questions array. But when I do questions[0] or this.state.questions[0], it always returns [. Any help would be greatly appreciated as I am fairly new the react development.
The issue is from my API data. I missed to wrap the keys with double quotes.
Updating the data from api call resolved my issue. So the sample api data will look like the below.
[
{
"id": 0,
"question": "What is the capital of Nigeria?",
"options": [
"New Delhi",
"Abuja",
"Aba",
"Onisha"
],
"answer": "Abuja"
},
{
"id": 1,
"question": "What is the capital of India?",
"options": [
"Punjab",
"Awka",
"Owerri",
"Enugu"
],
"answer": "New Delhi"
}
]

How to update array object within reducer

TLDR: How to update array object within the reducer
I would need some help understanding how to update the like count value of my post data once the action has been fired, and possibly a working logic.
Posts are being fetched from an action, being passed and mapped as a posts prop. Ideally it should make a new likes object on upvote
A user is able to click like, and on the backend its adds a like. Which is good.
The front end needs to upvote the current value to plus +1, however the current logic is not working.
Getting this error with current logic
there seem to be an error TypeError: Invalid attempt to spread
non-iterable instance
console.log(index) renders the like count for whatever post the user clicked on.
for example like
20
I would not be able to use state, i would need to do this in redux.
https://i.stack.imgur.com/1N0Nh.png <- idea of what the front end looks like
Here is the Posts Structure
{
"id": 5,
"title": "React Interview Questiossssnsdd",
"post_content": "ssss",
"username": "blueowl",
"createdAt": "2019-04-26T09:38:10.324Z",
"updatedAt": "2019-04-26T18:55:39.319Z",
"userId": 1,
"Likes": [
{
"id": 131,
"like": true,
"createdAt": "2019-04-26T12:20:58.251Z",
"updatedAt": "2019-04-26T12:20:58.251Z",
"userId": 1,
"postId": 5
},
{
"id": 152,
"like": true,
"createdAt": "2019-04-26T14:01:13.347Z",
"updatedAt": "2019-04-26T14:01:13.347Z",
"userId": 1,
"postId": 5
},
{
"id": 153,
"like": true,
"createdAt": "2019-04-26T14:01:46.739Z",
"updatedAt": "2019-04-26T14:01:46.739Z",
"userId": 1,
"postId": 5
},...
Example Likes Structure
[
{
"id": 182,
"like": true,
"createdAt": "2019-04-27T11:05:05.612Z",
"updatedAt": "2019-04-27T11:05:05.612Z",
"userId": 1,
"postId": 5
},
{
"id": 178,
"like": true,
"createdAt": "2019-04-27T10:44:49.311Z",
"updatedAt": "2019-04-27T10:44:49.311Z",
"userId": 1,
"postId": 5
},
{
"id": 179,
"like": true,
"createdAt": "2019-04-27T10:45:27.380Z",
"updatedAt": "2019-04-27T10:45:27.380Z",
"userId": 1,
"postId": 5
},
{
"id": 180,
"like": true,
"createdAt": "2019-04-27T10:46:44.260Z",
"updatedAt": "2019-04-27T10:46:44.260Z",
"userId": 1,
"postId": 5
},
reducer
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:[],
someLike:[],
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_POSTS:
console.log(action.data)
return {
...state,
posts: action.data, // maps posts fine,
}
case ADD_LIKE:
console.log(action.id) // renders post id
// console.log(state.posts) // logs posts array
console.log(state.posts)
const index = state.posts.find((post) => post.id === action.id).Likes.length
console.log(index); // gets likes length for the corresponding id to whatever post that has been clickd
// renders 5 or 3 (their is currently 2 posts)
// honestly don't what im doing below this line of code but should make a new like object
return [
{
Likes: [
...state.posts.find((post) => post.id === action.id).Likes.length + 1,
action.newLikeObject
]
}
]
show update count below here
myLikes={post.Likes.length} // right here
render(){
const {posts} = this.props; // from reducer
return (
<div>
{posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem
myLikes={post.Likes.length} // right here
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
);
}
}
extra info
actions.js
export const postLike = (id) => {
return (dispatch) => {
// console.log(userId);
return Axios.post('/api/posts/like', {
postId: id
}).then( (like) => {
dispatch({type: ADD_LIKE, id})
// console.log('you have liked this', like)
}).catch( (err)=> {
console.log('there seem to be an error', err);
})
}
}
Edit
console.log(newState)
{
"post": [],
"postError": null,
"posts": [
{
"id": 5,
"title": "React Interview Questiossssnsdd",
"post_content": "ssss",
"username": "EliHood",
"createdAt": "2019-04-26T09:38:10.324Z",
"updatedAt": "2019-04-26T18:55:39.319Z",
"userId": 1,
"Likes": [
{
"id": 219,
"like": true,
"createdAt": "2019-04-27T15:54:03.841Z",
"updatedAt": "2019-04-27T15:54:03.841Z",
"userId": 1,
"postId": 5
},
{
"id": 189,
"like": true,
"createdAt": "2019-04-27T11:11:07.558Z",
"updatedAt": "2019-04-27T11:11:07.558Z",
"userId": 1,
"postId": 5
},
{
"id": 190,
"like": true,
"createdAt": "2019-04-27T11:12:09.599Z",
"updatedAt": "2019-04-27T11:12:09.599Z",
"userId": 1,
"postId": 5
},
....,
"isEditing": false,
"isEditingId": null,
"likes": [
77,
24
],
"someLike": [],
"postId": null
}
Like Component
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCoffee, faAdjust } from '#fortawesome/free-solid-svg-icons';
import {connect} from 'react-redux';
import { postLike} from '../actions/';
class Like extends Component{
constructor(props){
super(props);
this.state = {
likes: null,
heart: false
}
}
// passes post id thats stored in PostItem.js
clickLike = (id) => {
this.props.postLike(id);
// toggles between css class
this.setState({
heart: !this.state.heart
})
}
render(){
return(
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<i style={{ marginRight: '140px'}} className={this.state.heart ? 'fa fa-heart':'fa fa-heart-o' }>
<span style={{ marginLeft: '6px'}}>
<a href="#" onClick={() =>this.clickLike(this.props.like)}>Like</a>
</span>
{/* gets the like counts */}
<span style={{ marginLeft: '7px'}} >{this.props.likes} </span>
</i>
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(Like);
Like component being passed here as <Like like={id} likes={myLikes} />
PostItem.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import Editable from './Editable';
import {connect} from 'react-redux';
import {UpdatePost, postLike, getCount} from '../actions/';
import Like from './Like';
import Axios from '../Axios';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
button:{
marginRight:'30px'
}
}
class PostItem extends Component{
constructor(props){
super(props);
this.state = {
disabled: false,
myId: 0,
likes:0
}
}
componentWillMount(){
}
onUpdate = (id, title) => () => {
// we need the id so expres knows what post to update, and the title being that only editing the title.
if(this.props.myTitle !== null){
const creds = {
id, title
}
this.props.UpdatePost(creds);
}
}
render(){
const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, Likes, clickLike, myLikes} = this.props
return(
<div>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={myTitle ? myTitle : title} editChange={editChange}/>
): (
<div>
{title}
</div>
)}
</Typography>
<Typography component={'span'} variant={'body2'}>
{post_content}
<h5>by: {username} </h5>
{/* component span cancels out the cant be a decedent of error */}
<Typography component={'span'} variant={'body2'} color="textSecondary">{moment(createdAt).calendar()}</Typography>
{/* gets like counts */}
<Like like={id} likes={myLikes} />
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm(id)}>
Edit
</Button>
):(
// pass id, and myTitle which as we remember myTitle is the new value when updating the title
<div>
<Button
disabled={myTitle.length <= 3}
variant="outlined"
onClick={this.onUpdate(id, myTitle)}>
Update
</Button>
<Button
variant="outlined"
style={{marginLeft: '0.7%'}}
onClick={editForm(null)}>
Close
</Button>
</div>
)}
{!isEditing && (
<Button
style={{marginLeft: '0.7%'}}
variant="outlined"
color="primary"
type="submit"
onClick={removePost(id)}>
Remove
</Button>
)}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(PostItem);
Posts.js (Master parent)
import React, { Component } from 'react';
import PostList from './PostList';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
loading: true,
isEditing: false,
// likes:[]
}
async componentWillMount(){
await this.props.GetPosts();
const thesePosts = await this.props.myPosts
const myPosts2 = await thesePosts
// const filtered = myPosts2.map((post) => post.Likes )
// const likesCount = filtered.map( (like) => like.length)
this.setState({
posts: myPosts2,
loading:false
})
}
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={this.state.posts}/>
</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));
PostList.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost, postLike, UpdatePost,EditChange, GetPosts, getCount, DisableButton} from '../actions/';
import PostItem from './PostItem';
import _ from 'lodash';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
title: '',
loading:true,
posts:[],
}
}
componentWillMount(){
this.props.GetPosts();
const ourPosts = this.props.myPosts
this.setState({
posts: ourPosts,
loading:false
})
console.log(this.state.posts)
}
componentWillReceiveProps(nextProps) {
const hasNewLike = false;
if(this.state.posts && this.state.posts.length) {
for(let index=0; index < nextProps.myPosts.length; index++) {
if(nextProps.myPosts[index].Likes.length !=
this.state.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if(hasNewLike) {
this.setState({posts: nextProps.myPosts}); // here we are updating the posts state if redux state has updated value of likes
}
console.log(nextProps.myPosts)
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({
title: e.target.value
})
}
formEditing = (id) => ()=> {;
this.props.EditChange(id);
}
render(){
// const {posts, ourLikes, likes} = this.props;
// console.log(posts)
// console.log(this.props.ourLikes);
return (
<div>
{this.state.posts.map(post => (
<Paper key={post.id} style={Styles.myPaper}>
<PostItem
myLikes={post.Likes.length} // right here
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
);
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
myPosts: state.post.posts,
// ourLikes: state.post.likes // reducer likes
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
postLike: (id) => dispatch( postLike(id)),
GetPosts: () => dispatch( GetPosts()),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
The error seems be occurring due to the code below
...state.posts.find((post) => post.id === action.id).Likes.length + 1
so here, we are finding the length of likes whose result will be a number and then we are trying to spread a number type variable, but spread operator (...) works for iterables like object, array.
From what I understand we want to update the likes array in posts collection.
case ADD_LIKE:
const newState = {...state}; // here I am trying to shallow copy the existing state
newState.posts.find(post => post.id == action.id).Likes.push(action.newLikeObject); // here we are trying to append the new like object to already existing **likes** array in the **posts** which should now make the count increase by 1
return newState;
if we want to use spread operator to update the array, we can use as below:
case ADD_LIKE:
const newState = {...state}; // here I am trying to shallow copy the existing state;
const existingLikesOfPost = newState.posts.find(post => post.id == action.id).Likes;
newState.posts.find(post => post.id == action.id).Likes = [...existingLikesOfPost, action.newLikeObject]; // using this approach I got some code duplication so I suggested the first approach of using **push** method of array.
return newState;
In Posts.js we can add another lifecycle method, like below:
componentWillReceiveProps(nextProps) {
const hasNewLike = false;
if(this.state.posts && this.state.posts.length) {
for(let index=0; index < nextProps.myPosts.length; index++) {
if(nextProps.myPosts[index].Likes.length !=
this.state.posts[index].Likes.length) {
hasNewLike = true;
}
}
}
if(hasNewLike) {
this.setState({posts: nextProps.myPosts}); // here we are updating the posts state if redux state has updated value of likes
}
}
edited above solution to use componentWillrecieveProps instead of getDerivedStateFromProps
You're currently trying to spread an integer with the following line:
...state.posts.find((post) => post.id === action.id).Likes.length + 1,
(you shouldn't try and modify an array's length property directly like this, if that's what you were trying to do)
Modifying deeply nested objects like this is pretty annoying without a library like ramda, but I think you're looking for something like this in your return statement:
// copy your state's posts
const newPosts = [...state.posts]
// Find the post you're adding a like to
const idx = newPosts.findIndex((post) => post.id === action.id)
const postToReplace = newPosts[idx]
// Replace that post with a copy...
newPosts[idx] = {
...postToReplace,
// ... with the Likes object also copied, with the new Like appended.
Likes: [
...postToReplace.Likes,
action.newLikeObject
]
}
return {
...state,
posts: newPosts
}
Basically, you need to drill down into your object and start replacing the elements that you're affecting in an immutable way.

Reactjs not updating the new value of product_number

Reactjs not updating the new value of product_number. I knew that similar question has been asked but am having hard time trying to resolve this.
The Reactjs code below displays provisions records from the arrays.
Now I need to update and replace the value the product_number from 001 to 006.
To this effect, I have added an update button which fetch the product_number from the Axios Call.
My problem is that product_number is not updated with 006 when the button is clicked.
Here is the json response of Axios Call for product_number updates
product_number.json
[{"product_number":"006"}]
Here is the code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import axios from 'axios';
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: false
};
}
componentDidMount() {
this.setState({
data: [
{"provision_id":"1","provision":"Milk","category":[{"category_id":"1","category_price":"100 USD","product":[{"product_id":"1","product_number":"001"}] }]}
],
});
}
// Get and update New Product number of Milk
handleNewProductNumber(prod_id) {
alert(prod_id);
const prod_data = {
prod_id: prod_id};
axios
.get("http://localhost/provision/product_number.json", { prod_data })
.then(response => {
const newData = this.state.data.map(store => {
//if (product.product_id !== prod_id) return product;
return {
...store,
product: store.product.map(
product => {
if (product.product_id !== prod_id) return product
return { ...product, product_number: response.data[0].product_number }
}
)
};
});
this.setState(state => ({
data: newData
}));
console.log(response.data[0].category_price);
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<span>
<label>
<ul>
{this.state.data.map((store) => {
return (
<div key={store.provision_id}>
<div><h1>Provision Store</h1> <br />
<b> Product: </b>{store.provision}
</div>
{store.category && store.category.map((cat) => {
return (
<div key={cat.category_id}>
<div><b>Prices:</b> {cat.category_price}
</div>
{cat.product && cat.product.map((prod) => <div key={prod.product_id}>
<b>Product Number:</b> #{prod.product_number}
<br />
<input
type="button"
value="Get & Update New Product Number"
onClick={() => this.handleNewProductNumber(prod.product_id)}
/>
</div>)}
</div>
)
})}
</div>
)
})}
</ul>
</label>
</span>
);
}
}
Updated Section using map function
return {
...store,
category: store.category.map(
product: store.product.map(
product => {
if (product.product_id !== prod_id) return product
return { ...product, product_number: response.data[0].product_number }
})
})
};
The problem is the same of the other question, you have an array of object, with inside another array of objects, in your state:
data: [
{
"provision_id": "1",
"provision": "Milk",
"category": [
{
"category_id": "1",
"category_price": "100 USD",
"product": [
{
"product_id": "1",
"product_number": "001"
}
]
}
]
}
]
To update the inner level, you have to traverse all the state tree:
return {
...store,
category: [{
...store.category,
product: [{
...store.category[0].product,
product_number: response.data[0].product_number
}]
}]
};
Edit after... well, your edit
Your updated piece of code isn't valid syntax:
return {
...store,
category: store.category.map(
product: store.product.map(
product => {
if (product.product_id !== prod_id) return product
return { ...product, product_number: response.data[0].product_number }
}
)
})
};
The first store.category.map call takes a function which will be called with a single category as an argument.
You have to spread the category prior to shadow the product property:
return {
...store,
category: store.category.map(
category => ({
...category,
product: category.product.map(
product => {
if (product.product_id !== prod_id) return product
return { ...product, product_number: response.data[0].product_number }
}
)
})
)
};

Displaying Data from One Component in Another

I'm learning react at the moment and I'm trying to have two components interact with each other. The hierarchy is as follows:
App
--SearchForm
--Results
There's a data object that will be filtered through a string I enter in the SearchForm component. The filtered result should be displayed in the Results component.
My logic was to have all the functions needed in the App component, and pass the data to the individual components.
I want to be able to display the filtered data in the results component.
Can anyone help me with this please?
Please find the App.js file's code below, as well as a sample of the object I'm using.
App.js
import React, { Component } from "react";
import styled from "styled-components";
import Header from "./Header";
import SearchForm from "./SearchForm";
import Results from "./Results";
import Map from "./Map";
const Outer = styled.div`
text-align:center;
`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
query: "",
data: [],
refinedData: [],
};
// this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange = (event) => {
this.setState({
query: event.target.value,
});
}
getData = async () => {
const response = await fetch("http://localhost:4200/bookings");
const json = await response.json();
this.setState({
data: json,
})
console.log(this.state.data);
}
filterData = () => {
const filtered = this.state.data.filter(element => {
return element.toLowerCase().includes(this.state.query.toLowerCase());
});
this.setState({
refinedData: filtered,
});
console.log(this.state.refinedData);
}
componentDidMount() {
this.getData();
}
render() {
return (
<Outer>
<Header/>
<SearchForm triggeredUpdate={this.handleSearchChange}/>
<Results searchQuery={this.state.filterData}/>
<Map/>
</Outer>
);
}
}
export default App;
Object
[
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
]
This is a simple logic actually in React. You want to show filtered results in your Results component, then you pass the filtered state to it. You can trigger the search with a button, then maybe the suitable place for this can be Search component. For this, you will pass your filterData method to it as a prop as you think.
I said a few times "it is an array not object" in my comments since the last data you show in your question says Object as bold but it is an array :) So, I got confused but you are doing it right.
You should filter your data with a prop in your object. As you think again, like user.name, car.license_late etc. You need a target here.
Here is a simple working example:
class App extends React.Component {
state = {
query: "",
data: [
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
],
refinedData: [],
};
handleSearchChange = event => this.setState({
query: event.target.value,
});
filterData = () => {
const { data, query } = this.state;
const filtered = !query ? [] : data.filter(element =>
element.car.licence_plate.toLowerCase().includes(this.state.query.toLowerCase())
);
this.setState({
refinedData: filtered,
});
}
render() {
return (
<div>
<SearchForm filterData={this.filterData} triggeredUpdate={this.handleSearchChange} />
<Results refinedData={this.state.refinedData} />
</div>
);
}
}
const Results = props => (
<div>
{
props.refinedData.map( el =>
<div key={el.id}>
<p>ID: {el.id}</p>
<p>User name: {el.user.name}</p>
</div>
)
}
</div>
)
const SearchForm = props => (
<div>
<input onChange={props.triggeredUpdate} />
<br />
<button onClick={props.filterData}>Search</button>
</div>
)
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Update after discussion on chat
You can do the search without a button while you typing. We don't have filterData method anymore since we moved the filter logic into handleSearchChange method. Also, we don't need any query state right now.
filterData array created with a ternary operator. If there is no search value we are returning an empty array since we don't want to list all of our data if there is not any search. By the way, I've updated my previous solution according to that, too. It was returning all the data if we hit the Search button with an empty input.
class App extends React.Component {
state = {
data: [
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
],
refinedData: [],
};
handleSearchChange = event => {
const { value: query } = event.target;
this.setState(prevState => {
const filteredData = !query ? [] : prevState.data.filter(element =>
element.car.licence_plate.toLowerCase().includes(query.toLowerCase())
);
return {
refinedData: filteredData
};
});
}
render() {
return (
<div>
<SearchForm triggeredUpdate={this.handleSearchChange} />
<Results refinedData={this.state.refinedData} />
</div>
);
}
}
const Results = props => (
<div>
{
props.refinedData.map(el =>
<div key={el.id}>
<p>ID: {el.id}</p>
<p>User name: {el.user.name}</p>
</div>
)
}
</div>
)
const SearchForm = props => (
<div>
<input onChange={props.triggeredUpdate} />
</div>
)
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Uncaught TypeError - issue mapping over json object in React

I have a react component which is pulling in user data, and should be able to display one of the values from the JSON object. However, I'm getting the following Uncaught TypeError: this.state.reasons.map is not a function error. I believe its because it is expecting an array vs an object, but not quite sure how to check or convert as needed. It should be able to map over the object and render the value of subscription.current_period_end
This is the JSON object:
{
"displayName": "username",
"email": "user#email.com",
"entitled": true,
"id": "23456789",
"subscription": {
"canceled_at": 1508519952,
"current_period_end": 1524765490,
"is_premium": true,
"is_renewing": false,
"plan_type": "Annual",
"saved": true,
"status": "Active"
},
"country": "us",
"language": "en"
}
React Component
class CancelConfirm extends React.Component {
constructor(props) {
super(props)
this.state = {
reasons: []
}
this.processData = this.processData.bind(this)
}
componentDidMount() {
this.fetchContent(this.processData)
}
fetchContent(cb) {
superagent
.get('/api/user')
.then(cb)
}
processData(data) {
this.setState({
reasons: data.body
})
}
render(props) {
const content = this.props.config.contentStrings
const reason = this.state.reasons.map((reason, i) => {
return (
<p key={i}>{reason.subscription.current_period_end}</p>
)
})
console.log(reason)
return (
<div className = 'confirm' >
<p className = 'confirm-subpara' >{reason}</p>
</div>
)
}
}
export default CancelConfirm
Your this.state.reasons is response object from JSON, object doesn't have map function it's only for iterator values such as new Array() if you are trying to show reason you would simply do
Currently I don't know why you are trying to iterate over object when it has no array array data, maybe I got your question wrong so please specify why you even did map instead of simple object reference.
var API_RESPONSE = {
"body": {
"displayName": "username",
"email": "user#email.com",
"entitled": true,
"id": "23456789",
"subscription": {
"canceled_at": 1508519952,
"current_period_end": 1524765490,
"is_premium": true,
"is_renewing": false,
"plan_type": "Annual",
"saved": true,
"status": "Active"
},
"country": "us",
"language": "en"
}
}
class CancelConfirm extends React.Component {
constructor(props) {
super(props)
this.state = {
reasons: false
}
this.processData = this.processData.bind(this)
}
componentDidMount() {
this.fetchContent(this.processData)
}
fetchContent(cb) {
setTimeout(() => {
cb(API_RESPONSE)
}, 2000)
}
processData(data) {
this.setState({
reasons: data.body
})
}
render(props) {
if (!this.state.reasons) return (<i>Loading ....</i>)
const reason = <p>{this.state.reasons.subscription.current_period_end}</p>
return (
<div className = 'confirm' >
<p className = 'confirm-subpara' >{reason}</p>
</div>
)
}
}
ReactDOM.render(<CancelConfirm />, document.getElementById('react'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="react"></div>
You can't map() over an Object. As such it would be better to do something like this:
render(props) {
const content = this.props.config.contentStrings
const { reasons } = this.state;
const keys = Object.keys(reasons);
return (
keys.map((k, i) => {
return <p key={i}>{reasons[k].subscription.current_period_end}</p>
})
)
}

Resources