How to make my object methods globally accessible - javascript

I'm working on a voip project. I have 2 pages, one page is for making outgoing calls, the other page is only for receiving calls.
Im using an external js file where I define a few object methods so I can access them everywhere in my component.
Problems:
I'm using the same file for both receiving calls and making
outgoing calls.(My object methods should do different things based on
the type of call)
I have to manipulate my HTML using javascript from within that object.
What I want:
I would like to have my object methods globally accessible from
within my component.
Able to manipulate the state within my object methods so I could re-render
My external js file (My voip client will call these listeners automatically)
var callListeners = {
onCallProgressing: function (call) {
audioProgress.src = './style/ringback.wav';
audioProgress.loop = true;
audioProgress.play();
//Report call stats
$('div#callLog').append('<div id="stats">Ringing...</div>');
},
onCallEstablished: function (call) {
audioIncoming.srcObject = call.incomingStream;
audioIncoming.play();
audioProgress.pause();
audioRingTone.pause();
//Report call stats
var callDetails = call.getDetails();
$('div#callLog').append('<div id="stats">Answered at: ' + (callDetails.establishedTime && new Date(callDetails.establishedTime)) + '</div>');
},
onCallEnded: function (call) {
audioProgress.pause();
audioRingTone.pause();
audioIncoming.srcObject = null;
if($('button#takeCall')) {
$('button#takeCall').addClass('d-none');
$('button#refuseCall').addClass('d-none');
}
//Report call stats
var callDetails = call.getDetails();
$('div#callLog').append('<div id="stats">End cause: ' + call.getEndCause() + '</div>');
if (call.error) {
$('div#callLog').append('<div id="stats">Failure message: ' + call.error.message + '</div>');
}
}
}
My component
class Recipient extends Component {
constructor() {
super()
this.state = {
name: null,
user: 'a User',
}
}
componentDidMount() {
this.CreateAccount();
}
CreateAccount() {
const name = this.state.user;
axios
.post("/api/auth", { name })
.then(res => { sinchClient.start(res.data).then(() => this.handleSuccess()); })
.catch((error) => { console.log(error) });
}
answerCall(e) {
e.preventDefault();
call.answer();
console.log(callListeners);
}
hangUpCall(e) {
e.preventDefault();
call && call.hangup();
console.log(call.getDetails());
}
handleSuccess() {
console.log('ready to receive incoming calls!')
}
renderCallArea() {
let callArea;
callArea =
<div className="frame">
<div id="call">
<form id="newCall">
<button id="takeCall" className="ml-2 btn btn-light d-none" onClick={(e) => this.answerCall(e)}>Opnemen</button>
<button id="refuseCall" className="ml-2 btn btn-dark d-none" onClick={(e) => this.hangUpCall(e)}>Weigeren</button>
<button id="leaveCall" className="ml-2 btn btn-dark d-none" onClick={(e) => this.hangUpCall(e)}>Verlaat gesprek</button>
{/* <button id="answer" onClick={(e) => this.answerCall(e)}>Answer</button> */}
</form>
</div>
<div className="clearfix"></div>
<div id="callLog">
</div>
<div className="error">
</div>
</div>;
return callArea;
}
render() {
const wrapperStyle = {
backgroundColor: 'rgba(127, 130, 160)',
minHeight: '600px',
}
const jumboStyle = {
backgroundColor: 'rgba(109, 113, 152)',
color: 'white',
borderRadius: '0'
}
return (
<div className="wrapper" style={wrapperStyle}>
<div className="jumbotron" style={jumboStyle}>
<h1 className="text-center">Wachten op een gesprek...</h1>
</div>
<div className='container mt-2'>
{this.renderCallArea()}
</div>
</div>
)
}
}
export default Recipient;
Any tips on how I should achieve this?

I don't 100% know what you mean by external js file, but I would extract that into your react app as an import. This is what it would look like:
import React, { Component } from 'react';
import './App.css';
class External {
static onCallProgressing () {
// Do some action...
return <div>{ `Ringing...` }</div>
}
}
class App extends Component {
callExternal() {
return External.onCallProgressing()
}
render() {
return (
<div className="App">
{ this.callExternal() }
</div>
);
}
}
export default App;
If you need to pass the value of the function to the child:
You first declare a state:
state = {
data
}
Set the state after calling the external function
callExternal() {
this.setState({ data: External.onCallProgressing() })
}
Have the child receive props from the state:
render() {
return (
<div className="App">
<MyChild data={ this.state.data } />
</div>
);
}
Have the child render the props:
render() {
return (
<div>
{ this.props.data }
</div>
);
}

Related

Conditional rendering on select

I am pretty new to the wonderful world of React.
I have two inputs passing data through from an API that renders a list of options. And I want to send the selected inputs from those options back to the parent in the input fields to display for another search.
I have tried passing state down to them and render them them optionally with both a ternary and an if else statement in the "SearchCityList" component in several ways but I either get both lists rendered and they would have to choose between one list that is doubled to put in each input field or it only puts the selected value in one input. Would appreciate any & all suggestions Thanks!
class Form extends Component {
state = {
showComponent: false,
showComponent2: false,
};
// open/close control over SearchCity component box
openSearch = () => {
this.setState({ showComponent: true });
};
openSearch2 = () => {
this.setState({ showComponent2: true });
};
closeSearch = () => {
this.setState({
showComponent: false,
showComponent2: false
});
};
// Passed down cb function to get selected city search in selectCity component
GoingTo = (flights) => {
this.setState({ GoingTo: [flights] });
};
LeavingFrom = (flights) => {
this.setState({ LeavingFrom: [flights] });
};
render() {
return (
<div>
<form className="form-fields container">
<div className="inputs">
<h1>Search for a flight!</h1>
<div className="depart">
<input
onClick={this.openSearch}
className="flight-search"
placeholder="Leaving From"
value={this.state.LeavingFrom}
></input>
<input type="date"></input>
</div>
<div className="Returning">
<input
onClick={this.openSearch2}
className="flight-search"
placeholder="Going To "
value={this.state.GoingTo}
></input>
<input type="date" placeholder="Returning"></input>
</div>
</div>
<button>Check Flights!</button>
</form>
{this.state.showComponent || this.state.showComponent2 ? (
<SearchCity
openSearch={this.openSearch}
openSearch2={this.openSearch2}
flightSearch={this.state.flightSearch}
closeSearch={this.closeSearch}
GoingTo={this.GoingTo}
LeavingFrom={this.LeavingFrom}
onSearchSubmission={this.onSearchSubmission}
closeSearch={this.closeSearch}
/>
) : null}
</div>
);
}
}
export default Form;
class SearchCity extends Component {
state = {
LeavingFrom: "",
GoingTo: "",
search: "",
flightSearch: [],
};
// Search submission / api call
onSearchSubmission = async (search) => {
const response = await Axios.get(
{
headers: {
"
useQueryString: true,
},
}
);
// set New state with array of searched flight data sent to searchCity component
const flightSearch = this.setState({ flightSearch: response.data.Places });
};
// Callback function to send search/input to parent "Form" component
submitSearch = (e) => {
e.preventDefault();
this.onSearchSubmission(this.state.search);
};
// closeSearch callback function sent from Form component to close pop up search box when X is pressed
closeSearch = () => {
this.props.closeSearch();
};
render() {
return (
<div className="container search-list">
<form onChange={this.submitSearch}>
<i className="fas fa-times close-btn" onClick={this.closeSearch}></i>
<input
onChange={(e) => this.setState({ search: e.target.value })} //query-search api
value={this.state.search}
className="search-input"
type="text"
placeholder="Search Locations"
></input>
<div className="search-scroll">
<SearchCityList
openSearch={this.props.openSearch}
openSearch2={this.props.openSearch2}
LeavingFrom={this.props.LeavingFrom}
GoingTo={this.props.GoingTo}
flightSearch={this.state.flightSearch}
/>
</div>
</form>
</div>
);
}
}
export default SearchCity;
function SearchCityList({ flightSearch, LeavingFrom, GoingTo }) {
const renderList = flightSearch.map((flights) => {
return (
<div>
<SelectCityLeaving LeavingFrom={LeavingFrom} flights={flights} />
<SelectCityGoing GoingTo={GoingTo} flights={flights} />
</div>
);
});
return <div>{renderList}</div>;
}
export default SearchCityList;
First of all, when dealing with state, make sure you initialize in the constructor and also ensure you bind your handlers to this component instance as this will refer to something else in the handlers if you don't and you won't be able to call this.setState().
constructor(props) {
super(props); // important
state = {
// your state
};
// make sure to bind the handlers so `this` refers to the
// component like so
this.openSearch = this.openSearch.bind(this);
}

I have to select the 'Search' button twice before the correct results are rendered - React JS

Apologies if this question has been asked before and solved, but I have been searching and haven't had much luck.
I have created an app where a person can search for a book and the results are returned. The problem I am having is when I enter a book title and select the search button an empty array is rendered and then an array with the proper results are returned but not rendered. When I select the search button again the proper results are rendered. I would like the correct results to be rendered on the first click.
I logged the results and it looks like the empty array is being rendered first instead of the correct results. I believe it could be due to the async nature of the API call.
Here is my code:
class App extends Component {
state = {
data: []
}
handleBookSearch = (book) => {
let data = utils.searchBooks(book);
this.setState({ data: data });
}
render() {
return (
<div className="">
<Banner
onSearch={this.handleBookSearch}
onRender={this.renderBooks} />
<div className="custom-margin">
<BookDisplay bookData={this.state.data} />
</div>
</div>
);
}
}
export default App;
class PostDisplay extends Component {
render() {
const { bookData } = this.props;
return (
bookData.map(book => (
<div>
<div className="cap-color">
<h4 className="card-header"><i className="fas fa-book fa-fw"></i>{book.title}</h4>
</div>
<div className="card-body">
<img src={book.thumbnail} alt={book.title} />
<h5 className="card-title margin-above"><b>Author:</b> {book.authors}</h5>
<h6><b>Publisher:</b> {book.publisher}</h6>
<h6><b>Published On:</b> {book.publishedDate}</h6>
<h6><b>Supported Languages:</b> {book.language}</h6>
<p className="card-text"><b>Description:</b> {book.description}</p>
<a href={book.link} target="_blank" rel="noopener noreferrer"
className="btn btn-primary cap-theme-project">Buy the book!</a>
</div>
</div>
))
);
}
}
export default PostDisplay;
class Banner extends Component {
constructor() {
super();
this.state = {
search: ''
};
}
updateSearch(event) {
const searchParam = event.target.value;
this.setState({ search: searchParam });
}
render() {
return (
< div className="title-banner" >
<h1 className="header-padding"><i className="fas fa-book fa-fw"></i><b>Google Library</b></h1>
<div className="container">
<div className="row justify-content-center">
<div className="col-12 col-md-10 col-lg-8 opacity">
<div className="card-body row no-gutters align-items-center">
<div className="col">
<input className="form-control form-control-lg form-control-borderless"
type="text" placeholder="Search for a book..." ref="searchQuery"
value={this.state.search}
onChange={this.updateSearch.bind(this)} />
</div>
<div className="col-auto">
<button onClick={() => this.props.onSearch(this.state.search)}
className="btn-margin btn btn-lg btn-primary"><i class="fas fa-search"></i></button>
</div>
</div>
</div>
</div>
</div>
</div >
);
}
}
export default Banner;
var books = require('google-books-search');
export let data = [];
export function searchBooks(title) {
books.search(title, function (err, results) {
if (!err) {
data = results;
} else {
console.log('ERROR: ' + err);
}
});
return data;
}
Use callback function. It will set state after callback method called.
handleBookSearch = (book) => {
let data = utils.searchBooks(book,(data)=>{
this.setState({ data: data });
});
}
export function searchBooks(title,callback) {
books.search(title, function (err, results) {
if (!err) {
callback(results);
} else {
console.log('ERROR: ' + err);
}
});
}
Yes your assumption is correct it is due to async operation. Handle promise like this.
handleBookSearch = async (book) => {
let data = await utils.searchBooks(book);
this.setState({ data: data });
}

TypeError: Cannot read property 'edit' of undefined

I keep getting the following 2 errors for my buttons:
TypeError: Cannot read property 'edit' of undefined
TypeError: Cannot read property 'remove' of undefined
I am building a todo list, each note has 2 buttons 'add' and 'Remove'.
I managed to get the note buttons working when I call DisplayNote once.
Whenever I try to make multiple notes with JS map the buttons stop working and I can't figure out why its not working now. Code is attached.
todo list image
import React from 'react';
class DisplayNote extends React.Component {
handleEdit(e) {
console.log('sdfsdfdfs');
this.props.edit(e)
}
handleRemove(e) {
console.log('sdfsdfdfs');
this.props.remove(e)
}
render(){
return(
<div className="note">
<p>{this.props.note}</p>
<span>
<button onClick={this.handleEdit.bind(this)}>Edit</button>
</span>
<span>
<button onClick={this.handleRemove.bind(this)}>Remove</button>
</span>
</div>
);
}
}
class EditNote extends React.Component {
handleSave(e) {
var val = this.refs.newText.value;
this.props.saveNote(val)
}
render(){
return (
<div className="note">
<textarea ref="newText" defaultValue="test">
</textarea>
<button onClick={this.handleSave.bind(this)}>Save</button>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.edit = this.edit.bind(this);
this.saveNote = this.saveNote.bind(this);
this.remove = this.remove.bind(this);
this.state = {
editing: false,
notes: ['Call Tim','sdsdsd', 'dentist', 'Email Julie']
}
}
AppObject = {
count: 1,
price: 15.00,
amount: '12'
}
AppArray = ['tim','ali', 'jim', 'tom']
edit(e) {
this.setState({editing: true});
console.log('AppObject', this.AppObject);
}
saveNote(val) {
this.setState({editing: false});
console.log('Save note value ' + val)
}
remove() {
alert('remove');
console.log('AppArray', this.AppArray);
}
eachNote(note, i) {
return(
<DisplayNote key={i}
note={note}
edit={(e) => this.edit(e)}
remove={(e) => this.remove(e)}>
{note}
</DisplayNote>
);
}
render() {
if(this.state.editing) {
return (
<div>
<EditNote saveNote={(e) => this.saveNote(e)} />
<div>{this.props.count}</div>
</div>
);
}else{
return (
<div>
/* Calling it once*/<DisplayNote edit={(e) => this.edit(e)} remove={(e) => this.remove(e)} />
<div>{this.props.count}</div>
<div>
/* Using map to create multiple notes */{this.state.notes.map(this.eachNote)}
</div>
</div>
);
}
}
}
App.propTypes = {
count: function(props, propName){
if(typeof props[propName] !== 'number'){
return new Error('Count prop must be a number');
}
if(props[propName] > 100){
return new Error('Creating ' + props[propName] + ' notes is too much!');
}
}
}
export default App;
I think you are loosing the context inside map function, you need to define the binding for that also.
Use this line in the constructor, it will bind that function:
this.eachNote = this.eachNote.bind(this);
Or use that function like this:
{this.state.notes.map((note, i) => this.eachNote(note,i)}
Or
{this.state.notes.map(this.eachNote)}
eachNote = (note, i) => { //use arrow function here
}

this.someFunction is not a function

After having read about the bind requirement for methods to be bound to a React ES6 class, I am still having some difficulty with this example:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.state = { products: [] };
this.updateState = this.updateState.bind(this);
}
componentDidMount() {
this.updateState();
}
handleProductUpvote(productId) {
Data.forEach((item) => {
if (item.id === productId) {
item.votes = item.votes + 1;
return;
}
});
this.updateState();
}
updateState() {
const products = Data.sort((a,b) => {
return b.votes - a.votes;
});
this.setState({ products });
}
render() {
const products = this.state.products.map((product) => {
return (
<Product
key={'product-' + product.id}
id={product.id}
title={product.title}
description={product.description}
url={product.url}
votes={product.votes}
submitter_avatar_url={product.submitter_avatar_url}
product_image_url={product.product_image_url}
onVote={this.handleProductUpvote}
/>
);
});
return (
<div className='ui items'>
{products}
</div>
);
}
}
class Product extends React.Component {
constructor() {
super();
this.handleUpvote = this.handleUpvote.bind(this);
}
handleUpvote() {
this.props.onVote(this.props.id);
}
render() {
return (
<div className='item'>
<div className='image'>
<img src={this.props.product_image_url} />
</div>
<div className='middle aligned content'>
<div className='header'>
<a onClick={this.handleUpvote}>
<i className='large caret up icon'></i>
</a>
{this.props.votes}
</div>
<div className='description'>
<a href={this.props.url}>
{this.props.title}
</a>
</div>
<div className='extra'>
<span>Submitted by:</span>
<img
className='ui avatar image'
src={this.props.submitter_avatar_url}
/>
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<ProductList />,
document.getElementById('content')
);
This returns
Uncaught TypeError: this.updateState is not a function(...) at handleProductUpvote
Is the initialized binding not sufficient in this case?
Whenever you see this issue, you don't want to be adding the bind to the method that it's trying to call right then, but the method that you are inside of when the "this.xxx not defined" issue occurs.
Currently, it's getting the function handleProductUpvote just fine - but it's calling it in the wrong object context. So you need to do the same thing as you did with updateState in the constructor, but with that function. Though I have limited react knowledge I believe it's common to do that for every function that's used as an event listener or callback.

Toggle active class on child components

I'm having a bit of a head ache trying to figure out the React way of implementing this.
I have a Searches component which houses SearchItems, when an item is clicked among other things I need to set it's state to active to that it gets the correct CSS, I managed to get this working fine but how would I go about removing the active state from the others?
I was thinking that I could pass down a function from the top level component that would take the ID of the search, when clicked it'd zip through SearchItems and change their state to either true/false depending on which ID it was?
Code below!
Top level component:
import React from "react";
import {Link} from "react-router";
import Search from "./Search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
]
};
}
render() {
const { searches } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
Search items component
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
this.setState({selected: true});
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}
I think you don't need the state in the child component at all. In fact is a good idea to avoid having state in most components so they are easy to reason and reuse.
I would leave all the state only on the parent component in this case.
TOP Component:
import React from "react";
import Search from "./search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : null
};
}
_onSearchSelect(searchId) {
this.setState({'activeElement': searchId})
}
render() {
const { searches, activeSearchId } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}
isActive={search.id === activeElement}
onSelect={this._onSearchSelect.bind(this)} />
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD Component:
import React from "react";
export default class Search extends React.Component {
_getPanelClassNames() {
const { isActive } = this.props
return 'row panel panel-success ' + (isActive ? 'active' : 'default')
}
_onSelect() {
const { id, onSelect } = this.props;
onSelect(id)
}
render() {
const { searchName, matches } = this.props;
const panelStyle = { height: '90px', marginBottom: '6px', boxShadow: '' }
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={panelStyle} className={this._getPanelClassNames()}>
<div className="col-xs-4">
Search Name: {searchName}
</div>
<div className="col-xs-3">
Must Have: PHP, MySQL
</div>
<div className="col-xs-3">
Could Have: AngularJS
</div>
<div className="col-xs-2">
<button type="button" onClick={this._onSelect.bind(this)}
style={buttonStyle} className="btn btn-default btn-lg"
>
{matches}
</button>
</div>
</div>
);
}
}
You can also see it running in Plunker: https://plnkr.co/edit/sdWzFedsdFx4MpbOuPJD?p=preview
Ok it turns out this is simpler than I thought and is simply a case of understanding how react works(and not getting confused) .
When you have a top level component you pass it's state via props to children, when you update the state in the top level component it'll pass that down to the children and you can use componentWillReceiveProps to take action.
I added a function to my top level component called updateActiveSearch which simply sets the state of the TOP level component I then passed the activeElement state as a prop to the child Elements along with the function. When a child element calls this function to set itself as active all of them will fire componentWillReceiveProps, they simply just need to check their own ID against the one they've received, if it matches they're active, if it doesn't they're not!
So my top level component now looks like this:
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : 0
};
}
// This function gets passed via a prop below
updateActiveSearch(id){
//console.log(id);
this.setState({activeElement : id});
}
render() {
const SearchItems = this.state.searches.map((search) => {
return <Search activeElement={this.state.activeElement} goFunction={this.updateActiveSearch.bind(this)} key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD COMPONENTS
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
// This happens right before the props get updated!
componentWillReceiveProps(incomingProps){
if(incomingProps.activeElement == this.props.id){
this.setState({selected: true});
} else {
this.setState({selected: false});
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
//this.state.panelStyle.boxShadow = '-2px 3px 20px 5px rgba(255,198,0,1)';
this.setState({selected: true});
this.props.goFunction(this.props.id);
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}

Categories

Resources