what does bind(this) means? - javascript

I already know that what bind do, it bound your given object or function to the function you want, but bind(this) is really confusing me.What does this in bind really means.
Below is the code from my react app with firebase Database.
componentWillMount: function() {
this.firebaseRef = firebase.database().ref('todos');
this.firebaseRef.limitToLast(25).on('value', function(dataSnapshot) {
var items = [];
dataSnapshot.forEach(function(childSnapshot) {
var item = childSnapshot.val();
item['key'] = childSnapshot.key;
items.push(item);
}.bind(this));
this.setState({
todos: items
});
}.bind(this));
},

bind(this) here binds the context of your function inside forEach() to the scope of the componentWillMount().
this here refers to the the scope of componentWillMount().
With bind(this), this keyword inside the inner function will refer to the outer scope.
This is essential because in this case this.setState inside the forEach function can be called as its scope is limited to componentWillMount().
According to the docs:
The bind() method creates a new function that, when called, has its
this keyword set to the provided value, with a given sequence of
arguments preceding any provided when the new function is called.
Check out this demo which illustrates the usage of bind(this).
class App extends React.Component {
constructor(){
super();
this.state = {
data: [{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}]
}
}
componentDidMount() {
this.state.data.forEach(function(item) {
console.log('outer scope ', this);
}.bind(this))
this.state.data.forEach(function(item) {
console.log('Inner scope ', this);
})
}
render() {
return (
<div>Hello</div>)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.js"></script>
<div id="app"></div>

You didn't show full React component definition, but it most probably refers to React component instance where your componentWillMount is defined.

You can play nice with this inside forEach - according to the https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach - instead of binding pass this as second argument for forEach statement.
class App extends React.Component {
constructor() {
super();
this.state = {
data: [{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}]
}
}
componentDidMount() {
this.state.data.forEach(function(item) {
console.log('outer scope ', this);
}, this) // be nice for context - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
}
render() {
return ( <div> Hello < /div>)
}
}
ReactDOM.render( < App / > , document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.js"></script>
<div id="app"></div>

Related

Conditional output in dropdown component

I want to build a dropdown menu showing teachers who teach certain instruments (see image below)
The dropdown component is this:
import * as React from 'react'
import { useState, useEffect } from "react"
import { useStaticQuery, graphql } from 'gatsby'
import { BiChevronDown } from "react-icons/bi";
import StaffList from "./StaffList"
const rows = [
{
id: 1,
title: "Verantwortliche",
},
{
id: 2,
title: "Lehrende der Streichinstrumente",
instrument: "streichinstrumente"
},
{
id: 3,
title: "Lehrende der Zupfinstrumente",
},
{
id: 4,
title: "Lehrende des Tasteninstruments",
},
{
id: 5,
title: "Lehrende des Gesangs",
},
{
id: 6,
title: "Lehrende des Schlagzeugs",
},
{
id: 7,
title: "Lehrende des Akkordeons",
},
{
id: 8,
title: "Lehrende der Musiktheorie",
},
{
id: 9,
title: "Lehrende der Früherziehung",
}
]
class DropDownRows extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: false};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<div className="dropdown-rows">
{rows.map(row => (
<div key={row.id}>
<div className="row">
<div className="col">{row.title}</div>
<div className="col">
<BiChevronDown
onClick={this.handleClick}
style={{float: "right"}}/>
</div>
<div>
</div>
</div>
{this.state.isToggleOn ? <StaffList /> : ''}
</div>
))}
</div>
)
}
}
export default DropDownRows
it uses this StaffList component:
import * as React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { MDXProvider } from "#mdx-js/react"
function StaffList({ data }) {
return(
<StaticQuery
query={graphql`
query staffQuery {
allMdx {
edges {
node {
excerpt(pruneLength: 900)
id
body
frontmatter {
title
description
featuredImage {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
)
}
}
}
}
}
}
}
`}
render={data => (
<div className="staff-container">
{data.allMdx.edges.map(edge => (
<article>
<div className="staff-image-container">
<GatsbyImage key={edge.node.id} alt='some alt text' image={getImage(edge.node.frontmatter.featuredImage)} style={{margin: "0 auto", padding: "0"}} />
</div>
<div style={{margin: "0 2em"}}>
<div>
<h4 key={edge.node.id} style={{margin: "0"}}>{edge.node.frontmatter.title}</h4>
<h5>{edge.node.frontmatter.description}</h5>
</div>
<p><MDXProvider>{edge.node.excerpt}</MDXProvider></p>
</div>
</article>
))}
</div>
)}
/>
)
}
export default StaffList
this is one of the .mdx files i source my data from:
---
title: Diana Abouem à Tchoyi
featuredImage: Foto_07.jpg
description: Violine, Streicherklassen
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Right now the component looks like this:
How would I conditionally render the content for each bar. so not all bars opening with the same content but just the one clicked showing only those teachers who fall in the selected category. My idea is to have some sort of conditional statement, comparing the selected bar title with maybe a field in the teachers markdown file? Like
title: Hanna
category: Streichinstrumente
and then if (allMdx.edges.node.frontmatter.category === rows.title) {...}
That's as far as I can wrap my head around this. Maybe someone can help? Thank you in advance.
I would have one array of objects storing teachers' datas. Example below.
const teacherData = [
{ id: 'piano',
name: "Alex",
otherData: ...
},
... (similarly for the rest)
]
Then another array of objects/records for instrument-teachers. Such as:
const records = [
{
id: 'piano',
teacher: []
}
]
After this I would loop through each element in the teacherData array to get the finalized record Array with id being the type of instrument and teachers being all the teachers that play that type of instruments.
Now, getting into the display part. I would write my component as below:
const Dashboard = () => {
const [show, setShow] = React.useState([])
function handleClick(id) {
if show.includes(id) {
const newShow = show.filter(a => a !== id)
setShow(newShow)
} else {
const newShow = show.push(id)
setShow(newShow)
}
}
return (
<div className="dashboard">
{records.map((record) =>
(
<div className="instrument-wrapper">
<p onClick={() => handleClick(record.id)} >{record.id}</p> //The name of the instrument
{show.includes(record.id) ? (
<div>
... all the teachers here by mapping through the record.teachers
</div>
) : (null)}
<div>
)
)}
</div>
)
}

Testing Library - Testing for logic that depends on CSS

I have made this component, which all it does it receive a text (long paragraphs) and the CSS will truncate the text if its over the lines prop. If truncated, there is a 'show all' button that will remove the class that is hiding some of the text.
The component itself works great, but want to test it, for really no other reason other then to practice testing.
I have written one test:
import { render, fireEvent } from '#testing-library/vue';
import TruncateText from '../TruncateText.vue';
describe('TruncateText', () => {
it('should have truncate class on mount', async () => {
const text = 'Super Duper Long Text';
const { getByText } = render(TruncateText, { props: { text } });
const pTag = getByText(text);
expect(pTag).toHaveClass('truncate');
expect(pTag).toHaveTextContent(text);
});
it('container should be truncated', async () => {
const text =
'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt';
const lines = 2;
const { getByText } = render(TruncateText, {
props: { text, lines },
});
expect(lastThreeCharacters).toBe('...');
});
});
Any thoughts on how I can write the test to check if some text will be truncated?
Other potential tests are mocking the click emit so I can check if class is removed. That should really be enough for this component.
<template>
<div>
<p
class="text truncate"
ref="announcement"
:style="truncated ? `-webkit-line-clamp:${lines}` : ''"
>
{{ text }}
</p>
<OBButton
:text="$t('show-all')"
class="show-all-button"
size="x-small"
v-if="truncated"
#click="showAll"
/>
</div>
</template>
<script>
export default {
props: {
text: {
type: String,
required: true,
},
lines: {
type: Number,
default: 10,
},
},
data() {
return { truncated: false };
},
mounted() {
this.truncated = this.isOverflown(this.$refs.announcement);
},
methods: {
isOverflown(el) {
return el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth;
},
showAll() {
this.$refs.announcement.classList.remove('truncate');
this.truncated = false;
},
},
};
</script>
<style lang="scss" scoped>
.text {
white-space: pre-line;
}
.truncate {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
max-height: 24rem; //hack for IE1
}
.show-all-button {
margin-top: 1rem;
}
</style
By looking at the component, you're doing two things upon clicking the button:
removing the truncate class from the paragraph
setting truncated property to false.
I would bind the class to the property, using :class="{ truncate: truncated }" and then the only thing in need of testing onclick is that truncated has been set to false (you don't want to test if Vue works).
This removes the need for your showAll to manually remove the class from the paragraph: Vue is data driven => you change the data, Vue manages DOM accordingly.
To make it as clear as possible: you should only test the component's logic and nothing else. You're not testing Vue, JavaScript or the browser. You trust that those work (other people are in charge of writing tests for them). So, in order:
expect truncated to have the proper value upon mounting (effectively testing isOverflown() provides the expected output in each of the cases you care about)
when truncated is true trigger a click on the button (no need to expect the button to exist when truncated is true, as you'd be testing if v-if works). After clicking the button, in $nextTick() expect truncated to be set to false.
And here is the list of things you should not test:
that JavaScript works
that Vue works (applies :class and :style, applies v-if, etc...)
that the browser understands and applies -webkit-line-clamp
that your testing library works
that showAll has run after clicking the button (you want to be able to rename the method and your test should still pass if clicking the button sets truncated to false).
That's about it.

Check if string has unicode '\u25CF'

I want to detect a unicode and format it to next line. To make it look bulleted.
Here's the sample text that renders from the database.
I'm using AntD and ReactJS.
I'm thinking if I can use this toString.replace()
const renderColumn = (text, record, index, rowKey, cardKey) => {
return (
<Form.Item>
{getFieldDecorator(`${rowKey}-${cardKey}-${index}`, {
initialValue: text, // THIS IS WHERE THE TEXT RENDER
rules: [
{
required: true,
message: '*Please fill out this field!',
},
],
}
</Form.Item>
)
}
In this example I used split() and join() methods.
I hope this code works for you.
Example:
var mssg = document.querySelector('div').innerHTML;
mssg = mssg.split('●').join("<br>●");
document.querySelector('div').innerHTML = mssg;
<div>
● Lorem ipsum dolor ● Sit amet consectetur adipisicing elit ● Sapiente eum, ut quas accusantium quasi
</div>

How to get section to become sticky until the user has scrolled a specific length

I'm trying to replicate something similar to the Postmates fleet website, where they use position: sticky on a section and change elements within that section, until the user has scrolled through all the content.
Here's an example of what I mean:
So far I have set up a ref on the section I want to make sticky:
ref={(r) => this.ref = r}
...
/>
And then I get the height of the container on page load:
componentDidMount() {
if (this.ref) {
this.setState({ targetY: this.ref.getBoundingClientRect().bottom },
// tslint:disable-next-line:no-console
() => console.log(this.state, 'state'))
}
window.addEventListener('scroll', this.handleScroll)
}
After which I detect the scrolling of the page and know when the section is in view:
handleScroll(e) {
const scrollY = window.scrollY + window.innerHeight;
const { lockedIntoView, targetY } = this.state;
// tslint:disable-next-line:no-console
console.log(scrollY, "scrollY");
if (scrollY >= targetY) {
this.setState({ lockedIntoView: true }, () => {
// tslint:disable-next-line:no-console
console.log(`LockedIntoView: ${lockedIntoView}`);
});
} else {
this.setState({ lockedIntoView: false }, () => {
// tslint:disable-next-line:no-console
console.log(`LockedIntoView: ${lockedIntoView}`);
});
}
}
Before setting the container to sticky position:
<section
...
style={{position: lockedIntoView ? "sticky" : ""}}
...
</>
I would like to know how to make it like the postmates website, where the section just remains in full view of the screen until the content has been scrolled (or for right now, until the user has scrolled a specified height)?
Or just an idea of how they've done it, and what steps I need to take in order to replicate it?
Here's my Codesandbox
Few things to consider:
1) When you set the target to position: fixed, you are removing it from the dom flow so you need to compensate by adding some height back to the dom - example below does this on the body.
2) You need to factor in the height of the target when checking scrollY.
3) When you pass your target height, you add the target back into the dom flow and remove the additional height added in step 1.
4) You need to keep track if we scrolling up or down - this is done but comparing the last scroll position and the current.
See comments inline below for a rough example.
styles.css:
.lockedIntoView {
position: fixed;
top: 0;
}
index.js
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
interface IProps {
title: string;
content: string;
}
interface IHeroState {
targetY: number;
lockedIntoView: boolean;
}
let lastScrollY = 0;
class App extends React.Component<IProps, IHeroState> {
ref: HTMLElement | null;
constructor(props) {
super(props);
this.handleScroll = this.handleScroll.bind(this);
this.state = {
lockedIntoView: false,
targetY: 0
};
}
componentDidMount() {
if (this.ref) {
this.setState(
{ targetY: this.ref.getBoundingClientRect().bottom },
// tslint:disable-next-line:no-console
() => console.log(this.state, "state")
);
}
window.addEventListener("scroll", this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.handleScroll);
}
handleScroll(e) {
const scrollY = window.scrollY + window.innerHeight;
const { lockedIntoView, targetY } = this.state;
if (lockedIntoView) {
console.info("we locked");
// update the padding on the doc as we now removing the target from normal flow
window.document.body.style.paddingBottom = `${this.ref.getBoundingClientRect().height}px`;
// we passed the taret so reset - we have to factor the target height in the calc
if (scrollY > targetY + this.ref.getBoundingClientRect().height) {
window.document.body.style.paddingBottom = "0px";
this.setState({ lockedIntoView: false });
}
} else {
// if we scrollign down and at the target, then lock
if (
scrollY > lastScrollY &&
(scrollY >= targetY &&
scrollY < targetY + this.ref.getBoundingClientRect().height)
) {
console.info("we locked");
this.setState({ lockedIntoView: true });
}
}
// update last scroll position to determine if we going down or up
lastScrollY = scrollY;
}
render() {
const { lockedIntoView, targetY } = this.state;
const fixed = lockedIntoView ? "lockedIntoView" : "";
return (
<div className="App">
<div className="bg-near-white min-vh-100 ">
<h1>First section</h1>
</div>
<div
ref={r => (this.ref = r)}
className={`vh-100 bg-blue pa0 ma0 ${fixed}`}
>
<h2>
When this is in full view of the window, it should remain fixed
until the window has scrolled the full length of the window
</h2>
</div>
<div style={{ height: "300vh" }} className="bg-near-white min-vh-100 ">
<h2>The next section</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
codesandbox demo

How is it possible to interpolate a JavaScript variable based on React state into another variable? [duplicate]

This question already has answers here:
Accessing an object property with a dynamically-computed name
(19 answers)
Closed 4 years ago.
I have a React component pulling from a file with images, copy, and other data needed for the app. The issue I'm coming up against is simply syntactical: how can I reference this data based on the state of the component?
data.js looks like this (simplified version):
import changingAgents from '../public/images/changing-agents.jpg';
import cta from '../public/images/cta.jpg';
import logo from '../public/images/logo.jpg';
export const images = {
changingAgents,
cta,
logo,
}
export const copy = {
services: {
header: 'What are you looking for?',
firstTimeInvestor: {
title: 'First-Time Investor Service',
intro: 'At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque.',
},
changingAgents: {
title: 'Changing Agents',
intro: 'At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti.',
},
}
And here are the relevant parts of the component, Services.js:
import { copy, images } from '../../data';
export default class Services extends Component {
state = {
segmentTrigger: 'changingAgents',
}
changeSegmentTrigger(chosenSegmentTrigger) {
this.setState({segmentTrigger: chosenSegmentTrigger})
}
render() {
return (
<div className="services">
<h2>{copy.services.header}</h2>
<Section backgroundImage={this.state.segmentTrigger} className="services__segment-triggers" >
<div className="services__segment-triggers__select">
<Button color="yellow" onClick={() => this.changeSegmentTrigger('firstTimeInvestor')} label="First-Time Investor" />
<Button color="yellow" onClick={() => this.changeSegmentTrigger('changingAgents')} label="Changing Agents" />
</div>
<div className="services__segment-triggers__info">
{/* Insert header here! */}
</div>
</Section>
</div>
)
}
}
Basically what I need to do is add text that will change depending on this.state.segmentTrigger in <div className="services__segment-triggers__info">. I tried a few variations to no avail, such as:
<h2>{copy.services.${this.state.segmentTrigger}.title}</h2>
<h2>{copy.services.${this.state.segmentTrigger}.title}`
<h2>{copy.services.this.state.segmentTrigger.title}</h2>
Is this possible? I get syntax errors for the first two, and the third one just doesn't work (which seems obviously logical to me, it was a bit of a Hail Mary even trying it, as of course there is no key named this in the copy.services object).
The answer you're looking for is
<h2>{copy.services[this.state.segmentTrigger].title}</h2>
The reasoning is that you're trying to access a key of copy.services, but that you will not know what key you're trying to access until the user starts clicking buttons, and which one you access will be dynamically decided.
The way objects and keys work in JavaScript, is that the key is technically a string, so using this example:
let x = {
test1: 4,
test2: 5,
'test3': 6
};
x.test1 // is 4
x.test2 // is 5
x.test3 // is 6
x['test1'] // is 4
x['test2'] // is 5
x['test3'] // is 6
const y = 'test1';
x.y // undefined. we never set a key of y.
x[y] // this is interpreted as x['test1'] so the result is 4

Categories

Resources