Upload image using react and rails - ruby-on-rails

I am trying to upload image using react into rails active storage.
My component is:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import User from './../../Assets/user.png';
import { addAvatar } from './../../actions/userAction';
class UploadAvatar extends Component {
state = {
image: null,
};
fileSelectHandler = (e) => {
this.setState({
image: e.target.files[0],
});
};
fileUploadHandler = () => {
if (this.state.image) {
console.log(this.state.image, this.props.userId);
const fd = new FormData();
fd.append('avatar', this.state.image, this.state.image.name);
this.props.addAvatar({ avatar: fd, userId: this.props.userId });
}
};
render() {
return (
<div className="avatar ">
<div className="avatar-content shadow-lg">
<div className="avatar-pic">
<img src={User} alt="userpic" />
</div>
<p>ADD PHOTO</p>
<input type="file" onChange={this.fileSelectHandler} />
<div className="avatar-foot">
<button type="button" className="skip">
SKIP
</button>
<button type="button" onClick={this.fileUploadHandler} className="submit">
SUBMIT
</button>
</div>
</div>
</div>
);
}
}
const mapStateToProps = store => ({
userId: store.userReducer.userId,
userEmail: store.userReducer.userEmail,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
addAvatar,
},
dispatch,
);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(UploadAvatar);
My ajax.js:
/* eslint-disable no-console no-param-reassign */
let CLIENT_URL = 'http://localhost:3000/api/v1';
function getDefaultOptions() {
return {
method: 'GET',
// credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
};
}
function buildParam(params, asJSON = true) {
if (asJSON) {
return JSON.stringify(params);
}
const fD = new FormData();
Object.keys(params).forEach((param) => {
fD.append(param, params[param]);
});
return fD;
}
function ajax(uri, options = {}) {
const defaultOptions = getDefaultOptions();
options.method = options.method ? options.method : defaultOptions.method;
if (!options.formType) {
options.headers = options.headers ? options.headers : defaultOptions.headers;
}
options.credentials = options.credentials ? options.credentials : defaultOptions.credentials;
if (options.body && !options.formType) {
options.body = buildParam(options.body);
}
uri = uri.startsWith('/') ? uri : `/${uri}`;
return fetch(`${CLIENT_URL}${uri}`, options)
.then(data => data.json())
.catch(errors => console.log(errors));
}
export default ajax;
But on rails side I am getting empty object in avatar. Don't know why?
Please have a look to screenshot for rails side.
But from postman if I am trying to upload it is working fine.
Is there any alternative way to upload image.

Related

I want to get my modified post, net ADD modified post on list (React - using react-redux)

// store.js
import { createStore, combineReducers } from "redux";
const INITIAL_STATE = [];
const postingReducer = (state, action) => {
if (action.type === "POST_SUCCESS") {
const newPost = {
title: action.payload.title,
content: action.payload.content,
};
return state.concat(newPost);
} else if (action.type === "POST_DELETE") {
return state.filter((item) => {
return item.title !== action.payload;
});
} else if (action.type === "POST_EDIT_SUCCESS") {
const modifiedPost = {
title: action.payload.title,
content: action.payload.content,
};
// console.log(modifiedPost)
// console.log(state.map((item,index)=>item[0]))
// console.log(state[0].title)
// state[0].title = modifiedPost.title
// state[0].content = modifiedPost.title
return state.concat(modifiedPost);
}
return INITIAL_STATE;
};
const store = createStore(
combineReducers({
posting: postingReducer,
})
);
export default store;
// EditForm.js
import { Link , useParams} from "react-router-dom";
import { useState } from "react";
import { useDispatch , useSelector } from "react-redux";
const EditForm = () => {
const dispatch = useDispatch();
const state = useSelector(state=>state.posting)
const params = useParams()
const [titleInput, setTitleInput] = useState(state[params.title].title);
const [contentInput, setContentInput] = useState(state[params.title].content);
const publishBtn = () => {
window.alert('Modified.')
return dispatch({
type: "POST_EDIT_SUCCESS",
payload: { title: titleInput, content: contentInput },
});
}
return (
<>
<form>
<div>
<label htmlFor="Title">Title</label>
<br />
<input
id="Title"
value={titleInput}
onChange={event=>{setTitleInput(event.target.value)}}
type="text"
/>
<br />
</div>
<br />
<div>
<label htmlFor="Content">Content</label>
<br />
<textarea
id="Content"
value={contentInput}
onChange={event=>{setContentInput(event.target.value)}}
type="text"
/>
<br />
</div>
<div>
<Link to="/">
<button onClick={publishBtn}>Modify</button>
</Link>
</div>
</form>
</>
);
};
export default EditForm
I want to get my modified post, net ADD modified post on list.
when i click Modify button in 'EditForm.js' I want to get my modified post, net ADD modified post on list.
in this situation, modified post added on the postlist.
I don't know how to fix "POST_EDIT_SUCCESS" return in 'store.js'
please help me!

React: Trying to set selection to drop down option

I setup a component that is basically a drop down and I am trying to figure out how to set it to where when I submit the form....its set on that one option. When I submit it now, it sends all the options to the backend instead of just the one I selected.
Here is my Category component
import React, { Component } from 'react'
class Categories extends Component{
handleCatChange = (event) => {
this.setState({category: event.target.value}) <------this should set the state to whatever is selected
}
render(){
let categories = this.props.category
let value = this.props.value
let optionItems = categories.map((cat,index) => {
return <option key={index} value={value}>{cat.category}</option>
})
return (
<div>
<select onchange={this.handleCatChange} value={this.props.category}>
{this.props.category ? optionItems : <p>Loading....</p>}
</select>
</div>
)
}
}
export default Categories
And here is RecipeInput Component with form
import React, { Component } from 'react'
import Categories from './Categories.js'
class RecipeInput extends Component{
constructor(props){
super(props)
this.state = {
category: [],
name:'',
ingredients: '',
chef_name: '',
origin: ''
}
}
componentDidMount(){
let initialCats = [];
const BASE_URL = `http://localhost:10524`
const CATEGORIES_URL =`${BASE_URL}/categories`
fetch(CATEGORIES_URL)
.then(resp => resp.json())
.then(data => {
initialCats = data.map((category) => {
return category
})
this.setState({
category: initialCats
})
});
}
handleSubmit = (event) =>{
event.preventDefault();
this.props.postRecipes(this.state)
this.setState({
name:'',
ingredients: '',
chef_name: '',
origin: ''
})
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<Categories category={this.state.category} value={this.state.category}/>
<div>
<label for='name'>Recipe Name:</label>
<input type='text' value={this.state.name} onChange={this.handleNameChange} />
</div>
<div>
<label for='name'>Country Origin:</label>
<input type='text' value={this.state.origin} onChange={this.handleOriginChange} />
</div>
<div>
<label for='name'>Chef Name:</label>
<input type='text' value={this.state.chef_name} onChange={this.handleChefChange} />
</div>
<div>
<label for='name'>Ingredients:</label>
<textarea value={this.state.ingredients} onChange={this.handleIngChange} />
</div>
<input value='submit' type='submit'/>
</form>
</div>
)
}
}
export default RecipeInput
And here is the error that is produced on submission(Its Rails btw)
I tired a few ways but haven't quite wrapped my head around using a component as a dropdown. What do I need to do?
Here is my backend code that creates the record on the api
def create
recipe = Recipe.create(recipe_params)
if recipe.save
render json: recipe
else
render json: { error: "Couldn't save" }
end
end
private
def recipe_params
params.permit(:category_id,:name,:ingredients,:chef_name,:origin,category_attribute:[:category])
end
Also my postRecipe function
export const postRecipes = (recipe)=>{
const BASE_URL = `http://localhost:10524`
const RECIPES_URL =`${BASE_URL}/recipes`
const config = {
method: "POST",
body:JSON.stringify(recipe),
headers: {
"Accept": "application/json",
"Content-type": "application/json"
}
}
//category field
return(dispatch)=>{
fetch(RECIPES_URL,config)
.then(response => response.json())
.then(resp => {
dispatch({
type: 'Add_Recipe',
payload:{
// category:resp.category,
name: resp.name,
ingredients: resp.ingredients,
chef_name: resp.chef_name,
origin: resp.origin,
categoryId: resp.categoryId
}
})
})
//.then(response => <Recipe />)
.catch((error) => console.log.error(error))
}
}
Code Edit due to change in question:
Access selectedValue while sending to the server
class Categories extends Component {
render() {
...
let optionItems = categories.map((cat, index) => {
return (
<option key={index} value={index}>
{cat.category}
</option>
);
});
...
}
}
class RecipeInput extends Component{
constructor(props){
super(props)
this.state = {
category: [],
name:'',
ingredients: '',
chef_name: '',
origin: ''
selectedValue: {}
}
}
handleSubmit(id){
this.setState({
selectedValue: this.state.category[id]
)}
}
...
}
You're passing onChange and value from Input Component but you're not using them in Categories Component.
Add onChange and value property to tag.
here is reference
import "./styles.css";
import React, { Component } from "react";
class Categories extends Component {
render() {
let categories = this.props.category;
let onChange = this.props.onChange;
let optionItems = categories.map((cat, index) => {
return (
<option key={index} value={cat.category}>
{cat.category}
</option>
);
});
return (
<div>
<select onChange={(e) => onChange(e.target.value)}>
{this.props.category.length ? optionItems : null}
</select>
</div>
);
}
}
export default function App() {
const onChange = (value) => {
console.log(value);
};
return (
<Categories
onChange={onChange}
category={[{ category: "1st" }, { category: "2nd" }]}
/>
);
}
I've updated the code.
If you need to try it online you can refer my Sandbox
https://codesandbox.io/s/stackoverflow-qno-65730813-j32ce

Cannot read property id of undefined, error found in the map function (react-rails)

I'm trying to make my submit button add on to the list of items, my seed data in rails gets rendered fine but when I try to add a new item when clicking submit, I get this error Cannot read property id of undefined which is found at the map function. The post request works fine, and only after I refresh the page, the item I add gets rendered on the page. Any help would be appreciated!
class TodoList extends React.Component {
render() {
const {todos} = this.props
var todoItems = todos.map(title => <TodoItem key={title.id} title={title}/>) //here
return (
<ListGroup className="my-2">
<h2 className="text-center">Items</h2>
{todoItems}
</ListGroup>
)
}
}
export default TodoList
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todos: [],
stuff: ''
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.addNewTodo = this.addNewTodo.bind(this)
}
handleChange = event => {
this.setState({
stuff:event.target.value
})
}
handleSubmit = event => {
event.preventDefault()
let body = JSON.stringify({todo: {item: this.state.stuff} })
fetch('http://localhost:3000/api/v1/todos', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: body,
})
.then(response => {response.json()})
.then(todo => {this.addNewTodo(todo)})
}
addNewTodo(todo){
this.setState({
todos: this.state.todos.concat(todo)
})
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-10 mx-auto mt-4">
<h1 className="text-center">Todo List</h1>
<TodoInput
stuff={this.state.stuff}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
/>
<TodoList
todos={this.state.todos}
/>
</div>
</div>
</div>
)
}
}
when your component is renders first "todos" won't have any value, it will be undefined. try below code
class TodoList extends React.Component {
render() {
const { todos } = this.props;
if (todos) {
return (
<ListGroup className="my-2">
<h2 className="text-center">Items</h2>
{todos.map(title => (
<TodoItem key={title.id} title={title} />
))}
</ListGroup>
);
}
return ""; } }
export default TodoList;

Click button not triggering "handleSubmit" function in React

I am building a react on rails app. I have a button on the page that user can indicate whether they want to join a meet up or not. Clicking "join" button should create a rsvp relation between the current user and an event, and the button will be switched to "Leave", if user then click on the "Leave" button, this relationship will be deleted from the rails backend. After messing around my react component, my "Join" button doesn't trigger the "onSubmit" function, and the "Leave" button seems to return an error saying "submission form cancelled because form is not connected". I'd appreciated a lot if any one can help me clean my logic.
import React from 'react'
class EventsIndexContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
active: props.rsvp
}
this.toggleButton = this.toggleButton.bind(this)
this.handleRsvpSubmit = this.handleRsvpSubmit.bind(this)
this.handleRsvpDelete = this.handleRsvpDelete.bind(this)
}
toggleButton() {
this.setState({active: !this.state.active})
}
handleRsvpSubmit(event) {
event.preventDefault()
let formPayLoad = {
user_id: this.props.current_user.id,
event_id: this.props.selectedId
}
this.props.addRsvp(formPayLoad)
}
handleRsvpDelete() {
fetch(`/api/v1/rsvps/${this.props.selectedId}`, {
method: 'DELETE'}
)
}
render() {
let button
let joinButton =
<form onSubmit={this.handleRsvpSubmit}>
<button type="button" onClick={() => (this.props.handleSelect(),
this.toggleButton())}>Join</button>
</form>
let leaveButton =
<button type="button" onClick={() => (this.toggleButton(),
this.handleRsvpDelete)}>Leave</button>
button = this.state.active? leaveButton : joinButton
return(
<div>
<h4>{this.props.location} - {this.props.meal_type} at
{this.props.time}</h4>
<p>{this.props.group.name}</p>
{button}
<button>See who is going</button>
</div>
)
}
}
export default EventsIndexContainer
This is the parent container:
import React from 'react'
import GroupIndexContainer from './GroupIndexContainer'
import EventsIndexContainer from './EventsIndexContainer'
class MainContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
groups: [],
current_user: null,
events: [],
rsvps: [],
selectedId: null
}
this.fetchGroups = this.fetchGroups.bind(this)
this.fetchEvents = this.fetchEvents.bind(this)
this.handleSelect = this.handleSelect.bind(this)
this.addRsvp = this.addRsvp.bind(this)
this.fetchRsvps = this.fetchRsvps.bind(this)
}
componentDidMount() {
fetch('api/v1/users.json', {
credentials: 'same-origin',
method: "GET",
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
this.setState ({current_user: data.user})
})
.then(this.fetchGroups())
.then(this.fetchEvents())
.then(this.fetchRsvps())
}
fetchGroups() {
fetch('/api/v1/groups', {
credentials: 'same-origin',
method: "GET",
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
this.setState({groups: data.groups})
})
}
fetchEvents() {
fetch('/api/v1/events', {
credentials: 'same-origin',
method: "GET",
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
this.setState({events: data})
})
}
fetchRsvps() {
fetch('/api/v1/rsvps', {
credentials: 'same-origin',
method: "GET",
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
this.setState({rsvps: data.rsvps})
})
}
handleSelect(id) {
this.setState({selectedId: id})
}
addRsvp(formPayLoad) {
fetch('/api/v1/rsvps', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json'},
body: JSON.stringify(formPayLoad)
})
}
render() {
let groups = this.state.groups.map((group) => {
return (
<GroupIndexContainer
key={group.id}
id={group.id}
name={group.name}
/>
)
})
let rsvp_ids = this.state.rsvps.map(rsvp => rsvp.event_id)
let events = this.state.events.map((event) => {
return(
<EventsIndexContainer
key={event.id}
id={event.id}
rsvp={rsvp_ids.some(rsvp_id => rsvp_id == event.id) ? true : false}
location={event.location}
meal_type={event.meal_type}
time={event.time}
group={event.group}
current_user={this.state.current_user}
user={event.user}
selectedId={this.state.selectedId}
addRsvp={this.addRsvp}
handleSelect={() => this.handleSelect(event.id)}
/>
)
})
return(
<div className="wrapper">
<div className="groups-index">
<h3>Your Groups:</h3>
{groups}
</div>
<div className="events-index">
<h3>What's happening today...</h3>
{events}
</div>
</div>
)
}
}
export default MainContainer
The button built before returning in the render function is either a form or a button. I would suggest to simply check the state of your component, avoid using the form (which is not inserted in the DOM, hence the "submission form cancelled because form is not connected" message).
Basically, your code will be much simpler if you use the onClick function of a button. You won't have to deal with the button types submit or button that will trigger onSubmit for the former or not for the latter as per : Difference between <input type='button' /> and <input type='submit' />
Also, using arrow functions in components properties is not a good practise, as well documented here : Why shouldn't JSX props use arrow functions or bind?
So I would suggest in a second time to change your onClick property to something like onClick={ this.handleLeave }, bind handleLeave in the constructor like you did for other functions, and handle the work there (and do the same for handleJoin).
I tried to rework a bit your code in the following snippet, hope this will help!
class EventsIndexContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
active: props.rsvp
}
this.toggleButton = this.toggleButton.bind(this)
this.handleRsvpSubmit = this.handleRsvpSubmit.bind(this)
this.handleRsvpDelete = this.handleRsvpDelete.bind(this)
// Stub
this.handleSelect = this.handleSelect.bind(this)
}
handleSelect() {
console.log("handleSelect called");
}
toggleButton() {
this.setState({active: !this.state.active})
}
// event argument removed here, wasn't used anyway
handleRsvpSubmit() {
console.log("handleRsvpSubmit called")
}
handleRsvpDelete() {
console.log("handleRsvpDelete called")
}
render() {
return(
<div>
<h4>Hello</h4>
<p>Group name</p>
{ this.state.active ?
<button type="button" onClick={() => (this.toggleButton(), this.handleRsvpDelete())}>Leave</button>
:
<button type="button" onClick={() =>(this.handleSelect(),this.toggleButton(), this.handleRsvpSubmit())}>Join</button>
}
<button>See who is going</button>
</div>
)
}
}
ReactDOM.render(
<EventsIndexContainer rsvp={ false } />,
document.getElementById('container')
);
<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="container">
<!-- This element's contents will be replaced with your component. -->
</div>

React App works perfectly on localhost, but errors out on Heroku

Very frustrating errors today. I've spent the entire day trying to debug my small application that works perfectly on my localhost, but errors out on heroku occasionally.
If I refresh the page several times, I can achieve the login. But it takes 2-3 refreshes.
The two errors I get when logging in a user are -
Uncaught TypeError: Cannot read property 'exercise_name' of undefined
And
Uncaught TypeError: Cannot read property '_currentElement' of null
Now I basically know what the issue is. I must not have my props when I initially try to map over them. One of the props is an array of exercises, with one of the keys as 'exercise_name.' I'm guessing it has to do with the speed I receive the from local host, compared to heroku's ajax calls.
Here is my issue,
I do not know which component this is coming from since, I use exercise_name in 4 components. Heroku has line numbers, but they are of no help since it doesn't point to anything in my application and I can't drop debuggers in heroku like I can on my machine here.
I've tried setting default props in mapStateToProps like so -
allExercises: state.entities.exercise || []
Did not work.
Ive tried wrapping things in conditionals in my components. Hasn't worked.
The following four components use exercise_name. Any direction would be greatly appreciated.
I understand the following is a lot of code. I would be completely content with an answer letting me know how to find which lines of code are producing these errors on heroku, or how to debug on heroku in general.
component 1
import React from 'react';
import { withRouter } from 'react-router-dom';
import SetResultContainer from '../setresult/create_setresult_container';
class ExerciseIndex extends React.Component {
constructor(props) {
super(props);
this.state = {
inputVal: '',
active: 'FIRST',
name: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({ inputVal: e.target.value })
}
componentDidMount() {
this.props.requestAllExercises();
}
handleClick(e) {
this.setState({ inputVal: e.currentTarget.attributes.value.value})
}
handleSubmit(e) {
let newActive = this.state.active === 'FIRST' ? 'SECOND' : null
let allExercises = this.props.allExercises;
let selected;
let name;
if (allExercises) {
allExercises.forEach(exercise => {
if (exercise.exercise_name === this.state.inputVal) {
selected = exercise,
name = exercise.exercise_name
}
})
e.preventDefault();
}
if (!name) {
this.setState({inputVal: 'Invalid Input, Please try Again'})
return 'Invalid Input'
}
this.props.requestExercise(selected)
this.setState({inputVal: '', active: newActive, name: name})
this.props.requestAllExercises();
}
render() {
let allExercises = this.props.allExercises || [{ exercise_name: '' }]
let match = allExercises.map((exercise) => {
if (this.state.inputVal === '') return [];
let matched = [];
if (this.state.inputVal.length > 0) {
for (var j = 0; j < this.state.inputVal.length; j++) {
matched = [];
if (exercise.exercise_name.slice(0, j + 1).toUpperCase() === this.state.inputVal.slice(0, j + 1).toUpperCase()) {
matched.push(<li onClick={this.handleClick}
value={exercise.exercise_name}
className="workout-auto-li"
key={exercise.id}>{exercise.exercise_name}</li>);
}
}
} else {
matched.push(<li onClick={this.handleClick}
value={exercise.exercise_name}
className="workout-auto-li"
key={exercise.id}>{exercise.exercise_name}</li>)
}
return matched;
});
return (
<div>
{this.props.allExercises ? (
<div>
{this.state.active === 'FIRST' ? (
<div className="exercise-main-div">
<div className="exercise-second-div">
<label className="exercise-label">
<h3>Add an Exercise for {this.props.liftname}</h3>
<input type="text" value={this.state.inputVal}
onChange={this.handleChange}
className="exercise-input"
/>
</label>
<ul className="exercise-ul">
{match}
</ul>
<button className="new-exercise-button"
onClick={this.handleSubmit}>Add Exercise</button>
</div>
</div>
) : this.state.active === 'SECOND' ? (
<SetResultContainer user={this.props.user}
exercises={this.props.exercises}
exercise={this.state.name}
liftname={this.props.liftname}/>
) : null }
</div>
) : null }
</div>
);
}
}
export default withRouter(ExerciseIndex);
component 2
import React from 'react';
import { withRouter } from 'react-router';
import values from 'lodash/values'
import { Pie } from 'react-chartjs-2';
class Leaderboard extends React.Component {
constructor(props) {
super(props)
this.state = { exercise: null }
this.handleUpdate = this.handleUpdate.bind(this)
}
componentDidMount() {
this.props.requestAllUsers();
this.props.requestAllExercises();
}
handleUpdate(property) {
return e => this.setState({ [property]: e.target.value });
}
render() {
const members = this.props.members.map(member => {
return <li className="members-list" key={member.id + 1}>{member.username}</li>
})
const memberId = {}
this.props.members.map(member => {
memberId[member.username] = member
})
const membersSetResults = {}
const membersLiftMaxes = {}
const completedMemberExercises = []
const completedExercises = {}
this.props.members.map(member => {
if (member.workouts) {
let workouts = values(member.workouts)
for (var i = 0; i < workouts.length; i++) {
let workoutResult = workouts[i].setresults
let results = values(workoutResult)
if (membersSetResults[member.username]) {
membersSetResults[member.username].unshift(results)
} else {
membersSetResults[member.username] = [results];
}
}
}
})
Object.keys(membersSetResults).map(member => {
let setResults = membersSetResults[member]
membersLiftMaxes[member] = {}
for (var i = 0; i < setResults.length; i++) {
let sets = setResults[i]
for (var j = 0; j < sets.length; j++) {
let currentExercise = this.props.allExercises[sets[j].exercise_id]
let exercise = currentExercise.exercise_name
if (completedMemberExercises.indexOf(exercise) < 0 && currentExercise.ex_type === 'lift') {
completedMemberExercises.push(exercise)
}
if (completedExercises[exercise]) {
completedExercises[exercise] += 1
} else if (!completedExercises[exercise]) {
completedExercises[exercise] = 1
}
if (currentExercise.ex_type === 'lift') {
if (membersLiftMaxes[member][exercise]) {
if(membersLiftMaxes[member][exercise] < sets[j].weight_lifted) {
membersLiftMaxes[member][exercise] = sets[j].weight_lifted
}
} else if (!membersLiftMaxes[member][exercise]) {
membersLiftMaxes[member][exercise] = sets[j].weight_lifted
}
}
}
}
})
const PieChart = {
datasets: [{
data: Object.values(completedExercises),
backgroundColor: [
'#2D4262',
'#363237',
'#73605B',
'#D09683',
'#F1F3CE',
'#1E656D',
'#00293C',
'#F0810F',
'#75B1A9',
],
}],
labels: Object.keys(completedExercises)
};
let exerciseDropdown = completedMemberExercises.map((exercise, idx) => {
return <option key={idx} value={exercise}>{exercise}</option>
})
let sorted = [];
const memberAndMax = {}
Object.keys(membersLiftMaxes).map(member => {
if (this.state.exercise) {
let exerciseMax = membersLiftMaxes[member][this.state.exercise]
if(!memberAndMax[this.state.exercise]){
memberAndMax[this.state.exercise] = []
memberAndMax[this.state.exercise].push([member, exerciseMax])
} else if (memberAndMax[this.state.exercise]) {
memberAndMax[this.state.exercise].push([member, exerciseMax])
}
memberAndMax[this.state.exercise].map(max => {
if (sorted.indexOf(max) < 0) {
if (max[1] > 0) {
sorted.push(max)
}
}
})
sorted.sort((a, b) => {
return a[1] - b[1]
})
}
})
let maxLis = sorted.reverse().map((user) => {
if (memberId[user[0]].id === this.props.cu.id) {
return <li className='userPresent' key={memberId[user[0]].id}>
<p className="members-list-p">{user[0]}</p>
<p className="members-list-p-two">{user[1]}</p></li>
} else {
return <li className='members-list' key={memberId[user[0]].id}>
<p className="members-list-p">{user[0]}</p>
<p className="members-list-p-two">{user[1]}</p></li>
}
})
return (
<div className='main-leaderboard'>
<div className='lb-reset-div'>
<button className='lb-reset-button' onClick={() => this.setState({exercise: null})}>Reset</button>
<select className='leaderboard-dropdown' onChange={this.handleUpdate('exercise')}>
<option>Please Select</option>
{exerciseDropdown}
</select>
</div>
{(this.state.exercise) ? (
<div className='lb-ul-div'>
<h3 className='selected-ex-title'>{this.state.exercise}</h3>
<ul className='leaderboard-ul'>
<li className="members-list"><p className="members-list-p">Name</p>
<p className="members-list-p-two">Max (lbs)</p></li>
{maxLis}
</ul>
</div>
): (!this.state.exercise) ? (
<div className='lb-ul-div'>
<h3 className='selected-ex-title'>Leaderboard</h3>
<ul className='leaderboard-ul'>
{members}
</ul>
</div>
): null}
<div className='pie-chart-div-lb'>
<h3 className='pie-chart-header'>What the World's Doing</h3>
<Pie circumfrence={300} data={PieChart}/>
</div>
</div>
)
}
}
export default withRouter(Leaderboard);
component 3
import React from 'react';
import { withRouter } from 'react-router';
import values from 'lodash/values';
import { Line, Pie } from 'react-chartjs-2';
class SearchBestWorkouts extends React.Component {
constructor(props) {
super(props);
this.state = {
inputVal: '',
name: '',
active: '',
result: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.props.requestAllExercises();
this.setState({active: 'FIRST'})
}
handleChange(e) {
e.preventDefault(e)
this.setState({ inputVal: e.target.value })
}
handleClick(e) {
this.setState({ inputVal: e.currentTarget.attributes.value.value})
}
handleSubmit(e) {
let newActive = this.state.active === 'FIRST' ? 'SECOND' : 'FIRST'
let allExercises = values(this.props.exercises);
let selected;
let name;
if (newActive === 'SECOND') {
allExercises.forEach(exercise => {
if (exercise.exercise_name === this.state.inputVal) {
selected = exercise,
name = exercise.exercise_name
}
})
e.preventDefault();
if (!name) {
this.setState({inputVal: 'Invalid Input, Please try Again'})
return 'Invalid Input'
}
this.setState({inputVal: '', active: newActive, name: name})
this.props.requestAllExercises();
} else if (newActive === 'FIRST') {
this.setState({inputVal: '', active: newActive, name: '' })
}
}
render () {
let allWorkouts = this.props.allWorkouts;
let exercises = this.props.exercises;
let setResults = allWorkouts.map(workout => {
return values(workout.setresults)
})
let mergedSets = [].concat.apply([], setResults)
const allResults = {}
const exerciseTypes = {}
const completedExercises = {};
for (var i = 0; i < mergedSets.length; i++) {
let set = mergedSets[i];
let exercise = exercises[set.exercise_id]
let name = exercise.exercise_name
let bodypart = exercise.bodypart
if (exerciseTypes[bodypart]) {
exerciseTypes[bodypart] += 1
} else if (!exerciseTypes[bodypart]) {
exerciseTypes[bodypart] = 1
}
if (exercise.ex_type === 'lift') {
if (!allResults[name]) {
allResults[name] = { labels: [],
datasets: [{
label: 'Weight over Time',
backgroundColor: '#2988BC',
borderColor: '#2F496E',
data: [],
}],
};
}
if (completedExercises[name] < (set.weight_lifted)) {
completedExercises[name] = set.weight_lifted
} else if (!completedExercises[name]) {
completedExercises[name] = set.weight_lifted
}
allResults[name].labels.push(allResults[name].labels.length + 1)
allResults[name].datasets[0].data.unshift(set.weight_lifted)
}
}
const PieChart = {
datasets: [{
data: Object.values(exerciseTypes),
backgroundColor: [
'#2D4262', '#363237', '#73605B', '#D09683'
],
}],
labels: Object.keys(exerciseTypes)
};
const best = Object.keys(completedExercises).map((exercise) => {
if (this.state.inputVal === '') return [];
let bests = [];
if (this.state.inputVal.length > 0) {
for (var j = 0; j < this.state.inputVal.length; j++) {
bests = [];
if (exercise.slice(0, j + 1).toUpperCase() === this.state.inputVal.slice(0, j + 1).toUpperCase()) {
bests.push(<li onClick={this.handleClick}
value={exercise}
className="best-lift-li"
key={exercise.id}>{exercise}</li>);
}
}
} else {
bests.push(<li onClick={this.handleClick}
value={exercise}
className="best-lift-li"
key={exercise.id}>{exercise}</li>)
}
return bests;
});
return (
<div>
{this.state.active === 'FIRST' ? (
<div className="best-lift-div">
<div className='best-lift-div-two'>
<h3 className="best-lift-title">Personal Records</h3>
<div className='best-lift-input-div'>
<input type="text" value={this.state.inputVal}
onChange={this.handleChange}
className="best-lift"
placeholder="Enter an Exercise"
/>
</div>
<ul className='best-lift-ul'>
{best}
</ul>
<button className='best-lift-button' onClick={this.handleSubmit}>Best Lift</button>
</div>
</div>
) : this.state.active === 'SECOND' ? (
<div className="best-lift-div">
<div className='best-lift-div-two'>
<h3 className="best-lift-title">
{this.state.name}: {completedExercises[this.state.name]}</h3>
<div className='chart-background'>
<Line width={250} height={200} data={allResults[this.state.name]}/>
</div>
<button className='best-lift-button' onClick={this.handleSubmit}>Back</button>
</div>
<div className='best-lift-div-three'>
<h3 className="best-lift-title">Workout Analysis</h3>
<div className='pie-chart-background'>
<Pie circumfrence={100} data={PieChart} />
</div>
</div>
</div>
) : null}
</div>
)
}
}
export default withRouter(SearchBestWorkouts)
component 4
import React from 'react';
import values from 'lodash/values'
import InfiniteScroll from 'react-infinite-scroll-component';
import { withRouter } from 'react-router'
class WorkoutShow extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick (e) {
e.preventDefault();
this.props.deleteWorkout(this.props.selectedWorkout).then(
() => {
this.props.requestUser(this.props.match.params.userId)
}
)
this.props.toggleParent();
}
render () {
const setArray = values(this.props.selectedWorkout.setresults)
const exercises = this.props.exercises
const results = setArray.map((result, idx) => {
if (result.workout_id === this.props.selectedWorkout.id) {
return <li key={result.id} className='workout-show-li'>
<p className='workout-title'><p>Set {idx + 1}: </p><p>{exercises[result.exercise_id].exercise_name}</p></p>
<ul>
{result.weight_lifted ? (
<li className='workout-result-li'><p className='workout-result-li'>Weight:</p>{result.weight_lifted}{result.weight_unit}</li>
) : null}
{result.reps ? (
<li className='workout-result-li'><p className='workout-result-li'>Reps:</p>{result.reps}</li>
) : null}
{result.distance ? (
<li className='workout-result-li'><p className='workout-result-li'>Distance:</p>{result.distance}{result.distance_unit}</li>
) : null}
{result.hour || result.min || result.sec ? (
<li className='workout-result-li'><p className='workout-result-li'>Duration:</p>
<div className='dur-format'>
{result.hour ? (
<p className='dur-result-hour'>{result.hour}:</p>
) : null}
{result.min ? (
<p className='dur-result'>{result.min}:</p>
) : null}
{result.sec ? (
<p className='dur-result'>{result.sec}</p>
) : null}
</div>
</li>
) : null }
</ul>
</li>
}
})
return (
<div className="workout-show-main">
<h3 className="workout-show-title">{this.props.selectedWorkout.name}
<button className='remove-workout-button' onClick={this.handleClick}>DELETE</button></h3>
<InfiniteScroll>
<ul className="workout-show-ul">
{results}
</ul>
</InfiniteScroll>
</div>
);
}
}
export default withRouter(WorkoutShow);
Being on a server introduces delays in fetching of data, and things that
work perfectly locally don't work as well on the server.
The major culprit is your code, which is making assumptions about return values. For example
const setArray = values(this.props.selectedWorkout.setresults)
const exercises = this.props.exercises
const results = setArray.map((result, idx) => {
Line 3 of this block blindly assumes that setArray is defined and an array. You need to add checks in at every step, or provide default values (see the end of the first line below)
const setArray = values(this.props.selectedWorkout.setresults) || []
const exercises = this.props.exercises
const results = setArray.map((result, idx) => {
I'll leave it as an exercise for you to add what I call 'defensive code' to check return values and handle missing data without barfing.
You can also add try..catch blocks to trao any errors that your defensive code doesn't handle.
It will take some time, but it's worth upgrading your methodology to include this as standard practice if you want to write good code

Resources