I have array of stateful objects (where either size of array or attributes of objects can change). My original plan was having a textfields where user can change course attributes but when i started implementing code. I noticed function changeCourseAttribute and overall code, was becoming more complicated than it should be so my question is there way to handling this, more elegantly ?
Because with my current implementation. For example if i had < CourseComponent /> i would have to call changeCourseAttribute pass all arguments it needs and on top of that it would also call re-render on all components which is unnecesarry.
I would be greatful if anyone could help thanks.
import { React, useState } from "react";
class Semester {
constructor(courses = []) {
this.courses = courses;
}
}
class Course {
constructor(name = "", credits = "", grade = "") {
this.name = name;
this.credits = credits;
this.grade = grade;
}
}
const App = () => {
const [semesters, setSemesters] = useState([new Semester()]);
function addSemester() {
setSemesters([...semesters, new Semester()]);
}
function removeSemester(semesterToBeRemoved) {
if (semesters.length > 1) {
setSemesters(
semesters.filter((semester) => semesterToBeRemoved !== semester)
);
}
}
function addCourse(semesterToBeChanged) {
setSemesters(
semesters.map((semester) =>
semester === semesterToBeChanged
? { ...semester, courses: [...semester.courses, new Course()] }
: semester
)
);
}
function removeCourse(semesterToBeChanged, courseToBeRemoved) {
if (semesterToBeChanged.courses.length > 1) {
setSemesters(
semesters.map((semester) =>
semester === semesterToBeChanged
? {
...semester,
courses: semester.courses.filter(
(course) => course !== courseToBeRemoved
),
}
: semester
)
);
}
}
function changeCourseAttribute(
semesterToBeChanged,
courseToBeChanged,
field,
data
) {
// This is just insanity..
}
return <div>{/* Rest of the code .. */}</div>;
};
export default App;
Related
I have a file where I try to determine which data should be used in a Gatsby template. I get an array that contains child pages in return, these child pages may contain other child pages. I want to support up to three levels of child pages.
I have a template where I use my paginator (component to find the correct pages), I look for correct pages to render bypassing the slug via pageContext from gatsby-node.js
Template (minus imports)
const projectsSubPages = ({ data, pageContext }) => {
return (
<Layout>
<Menu parentPage={pageContext.parentSlug} />
{data.allSanityProjects.edges.map((childNode) =>
<>
{childNode.node.childPages.length > 0 &&
<Paginator
pageData={childNode.node.childPages}
parentPage={pageContext.parentSlug}
key={childNode.node._id}
/>
}
</>
)}
</Layout>
);
};
export const query = graphql`
{
allSanityProjects {
edges {
node {
childPages {
_rawBlockContent
title
slug
childPages {
slug
title
childPages {
title
slug
childPages {
slug
title
_key
}
_key
}
_key
}
_key
}
_key
}
}
}
}
`;
export default projectsSubPages;
My paginator component (minus imports)
const subPageLevelFinder = ({ pageData, parentPage }) => {
const SubLevels = () => {
let pageLevel = "test";
if (pageData.slug === parentPage) {
pageLevel = pageData.slug
}
if (pageData.childPages && pageData.childPages.length > 0) {
pageData.childPages.map((secondLevel) => {
if (secondLevel.slug === parentPage) {
pageLevel = secondLevel.slug
return (pageLevel)
} else if (pageData.childPages.childPage && pageData.childPages.childPages.length > 0) {
secondLevel.childPages.map((thirdLevel) => {
if (thirdLevel.slug === parentPage) {
pageLevel = thirdLevel.slug
return (pageLevel)
}
})
} else {
return (
pageLevel = "No page level found"
)
}
})
}
return (
pageLevel
)
}
return (
<>
{console.log(SubLevels())}
{SubLevels()}
</>
)
};
See this gist for the return of the GraphQL query and gatsby-node.js https://gist.github.com/AndreasJacobsen/371faf073a1337b6879e4fd6b860b26f
My goal is to run a component that has a template in my paginator and passing the data this template should use from the SubLevels function, but this function returns the first set let value every time. So all of my if-statements fail, I can't figure out where the issue is, I've tried changing the if parameters several times, but this seems to fit the GraphQL query
It turns out that the error came from my trying to access array elements in a multi dimentional array.
So the array I got back had three elements, all with a slug. I tried to access the slug but in order to get that slug I had to loop through the elements.
See attached solution that works (but is not very efficient), notice that this solution has a map function at the very top level; this solved the issue.
import React from "react";
import SubPageTemplate from "./subPageTemplate";
import { Link } from "gatsby";
import { useStaticQuery, graphql } from "gatsby";
const BlockContent = require("#sanity/block-content-to-react");
const subPageLevelFinder = ({ pageData, parentPage, childSlug }) => {
const subLevels = () => {
let pageLevel = null;
pageData.map((mappedData) => {
if (mappedData.slug === childSlug) {
pageLevel = mappedData;
return pageLevel;
} else {
if (mappedData.childPages && mappedData.childPages.length > 0) {
if (mappedData.slug === childSlug) {
return (pageLevel = mappedData);
} else {
mappedData.childPages.map((secondLevel) => {
if (secondLevel.slug === childSlug) {
pageLevel = secondLevel;
return pageLevel;
} else if (
mappedData.childPages.childPage &&
mappedData.childPages.childPages.length > 0
) {
secondLevel.childPages.map((thirdLevel) => {
if (thirdLevel.slug === childSlug) {
pageLevel = thirdLevel;
return pageLevel;
}
});
}
});
}
} else {
return false;
}
}
});
return pageLevel;
};
return <>{subLevels() && <SubPageTemplate pageLevel={subLevels()} />}</>;
};
export default subPageLevelFinder;
Right now I am trying to console.log this.streamCreatorUid, but I'm running into a peculiar issue. In my redux debugger, I can clearly see my data in the proper place.
Here is my redux data for the stream creator, directly from my debugger.
streams -
[0] -
{category(pin): "Oldschool Runescape"
displayName(pin): "admin"
streamId(pin): "98ebc719-c7d5-4558-b99d-2d9f8306ec64"
title(pin): "accounttest"
uid(pin): "wsFc7pIMq5dMtw9hPU86DzUTdLO2"
}
I am trying to console.log this.streamCreatorUid from my mapstatetoprops, but it is returning the current user Uid of u9TcrICehNMlAmqyDHQY77L9CXq1 instead. I'm quite confused as to why this is happening, considering this is not the data shown in my debugger.
This is for a personal project. In the past I've accessed redux props like this with no issues, now I'm not quite sure why this is happening.
import React from 'react';
import { database } from '../../../firebaseconfig.js';
import { connect } from 'react-redux';
class StreamFollow extends React.Component {
constructor(props) {
super(props);
this.uid = this.props.uid;
this.displayName = this.props.displayName;
this.streamCreatorUid = this.props.streamCreatorUid;
this.streamCreatorDisplayName = this.props.streamCreatorDisplayName;
}
componentShouldUpdate(prevProps) {
if (this.props.uid !== prevProps.uid) {
this.uid = this.props.uid
}
if (this.props.streamCreatorUid !== prevProps.streamCreatorUid) {
this.streamCreatorUid = this.props.streamCreatorUid;
}
}
//creates a follower object under the stream creators uid
createFollower = (e) => {
const followerRef = database.ref(`User_Follow_Info/${e}/Follower`)
const followerInfoObject = {
uid: this.uid,
displayName: this.displayName
}
followerRef.push(followerInfoObject);
}
//creates a following object under the users uid
//Add in checks to see if following object already exists. We can't follow someone multiple times
createFollowing = (user) => {
const followingRef = database.ref(`User_Follow_Info/${user}/Following`);
const followingInfoObject = {
uid: this.streamCreatorUid,
displayName: this.streamCreatorDisplayName
}
console.log(this.streamCreatorDisplayName)
//Check to see if follow already exists.
/*followingRef.once('value', function (snapshot) {
if (snapshot.hasChild(DATA HERE)) {
alert('exists');
}
}); */
var isSignedIn = this.isSignedIn;
followingRef.orderByChild('uid').equalTo(this.uid).once('value').then(snapshot => {
console.log(snapshot.val());
console.log(this.streamCreatorUid);
if (isSignedIn) {
console.log(snapshot.val())
return
} else {
followingRef.push(followingInfoObject);
}
})
}
onSubmit = () => {
if (this.props.isSignedIn === true) {
this.createFollowing(this.uid);
this.createFollower(this.streamCreatorUid);
} else {
//add in a sign in modal if user is not logged in
console.log('please sign in')
}
}
render() {
return (
<div>
<button onClick={this.onSubmit}>Follow</button>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isSignedIn: state.auth.isSignedIn,
displayName: state.auth && state.auth.userInfo ? state.auth.userInfo.displayName : null,
uid: state.auth && state.auth.userInfo ? state.auth.userInfo.uid : null,
streamCreatorUid: state.streams && state.streams[0] ? state.streams[0].uid : null,
streamCreatorDisplayName: state.streams && state.streams[0] ? state.streams[0].displayName : null,
}
}
export default connect(mapStateToProps)(StreamFollow);
I am bit puzzled as when to to bind in constructor and when not to it .I tried reading two codes but couldn't get to a conclusion as when bind is needed ?
I need an explanation on the basis of two codes .
class Calculatetop extends React.Component{
constructor(props){
super(props);
this.state = this.initialState();
this.operatorStack = [];
this.operandStack = [];
this.shouldReset = false;
}
initialState() {
return {
currentDisplay:'',
};
}
reset() {
this.setState(()=> this.initialState());
this.operatorStack = [];
this.operandStack = [];
}
handleInput(input) {
let digits = ["0","1","2","3","4","5","6","7","8","9","."];
let operators = ["+","-","*","/","="];
if (input === "C") {
this.reset();
return;
}
if (digits.includes(input)) {
console.log("input is "+input);
if(this.shouldReset === true) {
this.state.currentDisplay = '';
this.shouldReset = false;
}
this.setState({
currentDisplay : this.state.currentDisplay + input
})
}
if (operators.includes(input)) {
console.log(this.operandStack);
console.log(this.operatorStack);
if(this.operatorStack.length > 0 && this.precedence(input) <= this.precedence(this.topOperator()) || input == "=") {
console.log("inside if ");
this.operandStack.push(parseFloat(this.state.currentDisplay));
console.log("this.state.currentdisplay"+this.state.currentDisplay);
this.solveStack();
let result = this.operandStack[0];
this.setState({
currentDisplay:`${result}`
})
if(input=="="){
this.operandStack = [];
this.operatorStack = [];
}
} else {
console.log("else part executed");
this.operandStack.push(parseFloat(this.state.currentDisplay));
}
if (input !== '=') {
this.operatorStack.push(input);
this.shouldReset = true;
}
}
}
topOperator() {
return this.operatorStack[this.operatorStack.length - 1];
}
solveStack() {
console.log("solvestack is executed");
while(this.operatorStack.length > 0) {
console.log(this.operandStack);
let operator = this.operatorStack.pop();
let operandTwo = this.operandStack.pop();
let operandOne = this.operandStack.pop();
this.operandStack.push(this.performOperation(operandOne,operandTwo,operator));
}
}
precedence(operator) {
return {
'+' : 1 , '-' : 1 , '*' : 2 , '/' : 2
}[operator];
}
performOperation(first,second,operator) {
if (operator === "+") {
return first + second;
}
else if (operator === "-") {
return first - second;
}
else if (operator === "*") {
return first * second;
}
else if (operator === "/") {
return first / second;
}
}
render()
{
return(
<div>
<h1>CalculatorApp</h1>
<CalculatorDisplay
currentDisplay={this.state.currentDisplay}
></CalculatorDisplay>
<CalculatorConfig inputHandler={(input) => this.handleInput(input)}></CalculatorConfig>
</div>
);
}
}
Here is the second code and I see bind being heavily used in this class but I couldn't understand as why we need to use this in second code .
class Indecisionapp extends React.Component{
constructor(props){
super(props);
this.handledeleteoptions=this.handledeleteoptions.bind(this);
this.handlepick=this.handlepick.bind(this);
this.handleaddoption=this.handleaddoption.bind(this);
this.state ={
options:[]
};
}
handledeleteoptions(){
this.setState(()=>{
return{
options:[]
};
});
}
handlepick(){
const randomnum=Math.floor(Math.random() *this.state.options.length);
const option=this.state.options[randomnum];
alert(option);
console.log(randomnum);
}
/// it takes an argument
handleaddoption(option){
console.log(option);
if(!option){
return 'Enter valid text';
}
else if(this.state.options.indexOf(option)>-1){
return 'the option already exits ';
}
this.setState((prevState)=>{
return{
options:prevState.options.concat([option])
};
});
}
render(){
const titlevar="Indecision App";
const subtitlevar='Put your life in the hands of a computer';
//const optionsvar=['one ','two','three'];
return(
<div>
<Header title={titlevar} subtitle={subtitlevar}/>
<Action
hasoptions={this.state.options.length>0}
handlepick={this.handlepick}
/>
<Options
options={this.state.options}
handledeleteoptions={this.handledeleteoptions}
/>
<Addoptions
handleaddoption={this.handleaddoption}
/>
</div>
);
}
}
One way to know if you need to bind (or use the new () => {}) syntax is if the context would switch. A few examples of this are in event handlers, or if you are passing the function as an argument. One way to just get rid of binding though is to use the newer (ES6) syntax for creating a function. One quick example would be:
export default class ButtonWrapper extends React.Component {
handleClick = (e) => {
this.props.handleClick(e);
}
render() {
return (
<button onClick={this.handleClick}>Button!</button>
);
}
}
Binding is typically needed when passing the method to a child component via a prop, as it will be called inside the scope of that object, which changes the value of this to reference the calling object.
You can either bind in the class's constructor or bind when the method is passed as a prop. It's considered best practice to bind in the constructor because, if you bind as you are passing it as a prop, a new method reference will be created every time the child component is rendered, potentially slowing down performance.
The other option is to use an arrow function when defining your class methods, () => {} which preserves the scope to the defining object, no matter where the method is passed.
Example:
class Foo extends React.Component {
constructor(props) {
this.boundMethodOne = this.boundMethodOne.bind(this)
}
boundMethodOne() {
return 'bound in constructor!'
}
boundMethodTwo() {
return 'bound in prop!'
}
unboundMethod() {
return this.hello()
}
arrowMethod = () => {
return 'bound by nature!'
}
hello() {
return "I'm inside Foo!"
}
_privateMethod() {
return this.hello()
}
render() {
console.log(this._privateMethod()) // doesn't need to be bound because it's not being passed anywhere
return (
<Bar
boundMethodOne={this.boundMethodOne}
boundMethodTwo={this.boundMethodTwo.bind(this)}
unboundMethod={this.unboundMethod}
arrowMethod={this.arrowMethod} />
)
}
}
class Bar extends React.Component {
hello() {
return "I'm inside Bar!"
}
render() {
console.log(this.props.boundMethodOne()) // => bound in constructor!
console.log(this.props.boundMethodTwo()) // => bound in prop!
console.log(this.props.unboundMethod()) // => I'm inside Bar!
console.log(this.props.arrowMethod()) // => bound by nature!
}
}
I want to use either forEach or map to loop through an array that has multiple objects in it. These objects have a key price and a value for it. I'm was trying to use forEach but I can't get it to work. This is my component:
import React, { Component } from 'react';
import {addCart} from './Shop';
import { connect } from 'react-redux';
export class Cart extends Component {
constructor(props) {
super(props);
this.state = {items: this.props.cart,cart: [],total: 0};
}
...
countTotal() {
this.state.cart.forEach((item, index) => {
console.log(this.state.items);
this.state.total = this.state.total + this.state.items.price;
console.log(this.state.total);
})
}
...
render() {
return(
<div className= "Webcart" id="Webcart">
</div>
);
}
}
...
In countTotal, console.log(this.state.items) outputs various objects that each look like
item:"Hoodie"
price:25
size:"large"
How can I loop through each object and get the price value so I can add it up in my function?
You should not assign directly to state, you should use setState instead. forEach is fine, but I would recommend you skip forEach and map and use reduce, pulling only the price key out of the object:
countTotal() {
this.setState({
total: this.state.cart.reduce((total, { price }) => total + price, 0)
});
}
To answer how to loop through an array, you can use a simple for loop in javascript as you would in a language like C;
let total = 0;
for(let i = 0; i < items.length; i++) {
total += item[i].price
}
React following a functional approach, we prefer map and reduce as it makes your code more declarative. Hence,
const total = items.reduce((acc, item) => {
return acc + item.price;
}, 0)
Your code would look like this then,
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
export class Cart extends Component {
constructor(props) {
super(props);
this.state = {items: props.cart,cart: [],total: 0};
}
countTotal() {
return this.state.items.reduce((acc, item) => {
return acc + item.price;
}, 0)
}
render() {
return(
<div className= "Webcart" id="Webcart">
{ this.countTotal() }
</div>
);
}
}
class App extends Component {
render() {
return (
<Cart cart={
[
{
item:"Hoodie",
price:25,
size:"large"
},
{
item:"Gloves",
price: 12,
size:"large"
},
{
item:"boots",
price:30,
size:"large"
},
]
} />
);
}
}
export default App;
Note
There is not setState used. total is derived data. Derived data must not reside in state.
Yet if for some reason you still need it, countTotal would look like this,
countTotal() {
this.setState(state => {
return {
total: state.items.reduce((acc, item) => {
return acc + item.price;
}, 0)
};
});
}
I'm trying to improve my test coverage for my react components, but I am having trouble testing variables and functions declared within the render method of my components. Below are a couple of examples that I'm unable to get covered:
1)
cityStateZip = `${cityStateZip} - ${location.zipExtension}`;
2)
directionsUrl = `maps://maps.apple.com/?saddr=My+Location&daddr=${gpsCoords.lat}+${gpsCoords.lng}`;
3)
const onClick = (pricingTileId) => {
if (store.selectedPharmacy !== pricingTileId) {
store.setPharmacy(pricingTileId);
}
};
Here's the code:
class Tile extends Component {
const { location, store } = this.props;
render() {
let directionsUrl = `https://maps.google.com/?saddr=My+Location&daddr=${gpsCoords.lat}+${gpsCoords.lng}`;
if (navigator.platform.indexOf('iPhone') !== -1
|| navigator.platform.indexOf('iPod') !== -1
|| navigator.platform.indexOf('iPad') !== -1) {
directionsUrl = `maps://maps.apple.com/?saddr=My+Location&daddr=${gpsCoords.lat}+${gpsCoords.lng}`;
}
let cityStateZip = `${location.city}, ${location.state} ${location.zip}`;
if (location.zipExtension) {
cityStateZip = `${cityStateZip} - ${location.zipExtension}`;
}
const onClick = (pricingTileId) => {
if (store.selectedLocation !== pricingTileId) {
store.setLocation(pricingTileId);
}
};
let selectedClass;
if (store.selectedLocation === id) {
selectedClass = 'selected';
}
return (
)
Is there an effective way to test the variables and functions declared in the render function that I'm overlooking? (I'm using Jest and Enzyme for my testing). Thank you!
You can refactor your component like this:
class Tile extends Component {
isMobile = () => {
let mob = navigator.platform
if (mob.indexOf('Iphone')) return true
if (mob.indexOf('Ipad')) return true
if (mob.indexOf('Ipod')) return true
return false
}
isZipValid = () => !!this.props.location.zipExtension
isLocationValid = (id) => this.props.store.location === id
handleClick = (pricingTileId) => {
const { store } = this.props;
if (store.selectedLocation !== pricingTileId) {
store.setLocation(pricingTileId);
}
}
render() {
let directionsUrl
let selectedClass = isLocationValid() && 'selected';
let cityStateZip = `${location.city}, ${location.state} ${location.zip}`;
if (!isMobile()) {
directionsUrl = `maps://maps.apple.com/?saddr=My+Location&daddr=${gpsCoords.lat}+${gpsCoords.lng}`;
}
if (isZipValid()) {
cityStateZip = `${cityStateZip} - ${location.zipExtension}`;
}
return (
<div> Anything</div>
)
}
..
============== PART 2 ==================
You can extract them to a separate file such as lib or helpers
and then import it to your components.
Like this:
Helper:
//helpers.js
export const isMobile = (mob) => {
if (mob.indexOf('Iphone')) return true
if (mob.indexOf('Ipad')) return true
if (mob.indexOf('Ipod')) return true
return false
}
Finally on the component:
export { isMobile } from './helpers'
if(isMobile(navigator.platform)){
//you just abstracted the function
}
Conclusion: your isMobile() can be easily tested from the helpers and supply to any component :)
Now you can easily test function by function
I hope it helped you :D
Kindly,