Why is Peer.Nick always undefined in LioWebRTC? - reactjs

I'm using the liowebrtc react library to create a chat app. This library assigns a unique identifier (peer.id) to each peer (people not you in your chatroom). However, you can also assign yourself a nickname which then gets passed as peer.nick. At least, that's how it's supposed to work. But the peer.nick value always returns undefined, even though it's present in the Peer object. For instance, I pass a nickname to the Liowebrtc component as shown below:
<LioWebRTC
options={{ debug: true,
dataOnly: true,
nick: "Pippin" }}
onReady={join}
onCreatedPeer={handleCreatedPeer}
onReceivedPeerData={handlePeerData} >
If I print the peer object to console in handleCreatedPeer (the function that gets called when the app is informed of the presence of someone other than yourself in the chat room), I can see peer has a nick with value "Pippin." However, trying to call peer.nick or peer["nick"] always produces undefined.
For instance,
console.log("Peer id: ", peer.id); // valid value
console.log("Peer nick: ", peer.nick); // undefined
console.log("Peer one: ", peer.oneway); // valid value
even though if I print the Peer object in console, I can see:
Strangely though, I I print the keys of the Peer object in console, I see:
As you can see, nick is missing from the key list, even though it's there when you log the object in its entirety.
Why is peer.nick always undefined? Is there a different way I'm supposed to call it?
Here is the ChatWrapper class in its entirety:
// Code modified from https://medium.com/#leontosy/building-a-p2p-web-app-with-react-and-liowebrtc-6a7e8c621085
import React, { useState } from 'react';
import { LioWebRTC } from 'react-liowebrtc';
import ChatBox from './ChatBox';
const ChatWrapper = props => {
const [chatLog, setChatLog] = useState([])
const addChat = (name, message, alert = false) => {
setChatLog( chatLog => {
return [ ...chatLog,
{
name,
message: `${message}`,
timestamp: `${Date.now()}`,
alert
}
];
})
};
const join = (webrtc) => webrtc.joinRoom(props.roomID.toString());
const handleCreatedPeer = (webrtc, peer) => {
console.log(peer.nick)
addChat(`Peer-${peer.id.substring(0, 5)} joined the room!`, ' ', true);
}
const handlePeerData = (webrtc, type, payload, peer) => {
switch(type) {
case 'chat':
addChat(`Peer-${peer.id.substring(0, 5)}`, payload);
break;
default:
return;
};
}
return (
<div>
<p>Room: {props.roomID}</p>
<LioWebRTC
options={{ debug: true,
dataOnly: true,
nick: "Pippin"}}
onReady={join}
onCreatedPeer={handleCreatedPeer}
onReceivedPeerData={handlePeerData} >
<ChatBox
chatLog={chatLog}
onSend={(msg) => msg && addChat('Me', msg)}
/>
</LioWebRTC>
</div>
);
}
export default ChatWrapper;
and here is the ChatBox class in its entirety:
// Code from https://medium.com/#leontosy/building-a-p2p-web-app-with-react-and-liowebrtc-6a7e8c621085
import React, { Component } from 'react';
import './ChatBox.css';
import { withWebRTC } from 'react-liowebrtc';
class ChatBox extends Component {
constructor(props) {
super(props);
this.state = {
inputMsg: ''
};
}
generateChats = () => {
if(this.chatBox) {
setTimeout(() => { this.chatBox.scrollTop = this.chatBox.scrollHeight; }, 2);
}
return this.props.chatLog.map((item) => (
<div className="chat" key={`chat-${item.name}-${item.timestamp}`}>
<b className="name" style={{ color: item.alert ? '#888' : '#333' }}>{item.name}</b> <span className="msg">{item.message}</span>
</div>
));
}
handleSend = (chatMsg) => {
this.props.webrtc.shout('chat', chatMsg);
this.props.onSend(chatMsg);
}
handleKeyUp = (evt) => {
if (evt.keyCode === 13) {
this.handleSend(this.state.inputMsg);
this.setState({ inputMsg: '' });
}
}
handleInputChange = (evt) => this.setState({ inputMsg: evt.target.value });
render() {
const { chatLog } = this.props;
return (
<div className="container">
<div className="chatHeader">
<h1 className="title">P2P Chat Example</h1>
<hr />
</div>
<div className="chatBox" ref={(div) => this.chatBox = div}>
{chatLog.length ? this.generateChats() : (
<div className="info">
<p>To test this component out, open this page in a new tab or send it to a friend.</p>
</div>
)}
</div>
<hr />
<div className="bottomBar">
<input className="chatInput" type="text" placeholder="Type a message..." onKeyUp={this.handleKeyUp} onChange={this.handleInputChange} value={this.state.inputMsg} />
</div>
</div>
);
}
}
export default withWebRTC(ChatBox);

Related

React state not updating accordingly

I have a search component where the search result updates according to the search input, where if there is data returned from the API, it is rendered as a book grid, if there is no data, a message is displayed, and if the search input is empty, nothing is rendered.
My problem is that when query state updates the searchResult state does update but when I delete the search input so fast (make the search input empty), query becomes updates as an empty string but searchResult does not update according to it. What could be the problem?
Here is the code to the search component: (Note: I tried the componentDidUpdate() method and the setState() callback function but nothing worked)
import React, { Component } from "react";
// import "React Router" components
import { Link } from "react-router-dom";
// import custom components
import Book from "./Book";
// import required API
import * as BooksAPI from "../BooksAPI";
export default class BookSearch extends Component {
state = {
query: "",
searchResult: [],
};
handleInputChange = (query) => {
this.setState(
{
query: query.trim(),
},
() => {
if (query) {
console.log(query);
BooksAPI.search(query).then((books) => {
this.setState({ searchResult: books });
});
} else {
this.setState({ searchResult: [] });
}
}
);
};
// componentDidUpdate(currentProps, currentState) {
// if (currentState.query !== this.state.query && this.state.query) {
// BooksAPI.search(this.state.query).then((books) => {
// this.setState({ searchResult: books });
// });
// } else if (currentState.query !== this.state.query && !this.state.query) {
// this.setState({ searchResult: [] });
// }
// }
render() {
const { query, searchResult } = this.state;
const { updateBookShelves } = this.props;
return (
<div className="search-books">
<div className="search-books-bar">
<Link to="/" className="close-search">
Close
</Link>
<div className="search-books-input-wrapper">
<input
type="text"
placeholder="Search by title or author"
value={query}
onChange={(event) => this.handleInputChange(event.target.value)}
/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid">
{ searchResult.error ?
<p>No results matching your search</p>
: searchResult.map((book) => (
<Book
key={book.id}
book={book}
updateBookShelves={updateBookShelves}
/>
))
)
) )}
</ol>
</div>
</div>
);
}
}
I am not 100% sure about this solution since it is using setState inside callback of other setState but you can give it a try.
I think you can probably need to use setTimeout before calling api for data and before checking if query exist or not we can set timeout to null so it will not call unwanted api calls.
handleInputChange = query => {
this.setState(
{
query: query.trim()
},
() => {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
if (query) {
this.timeout = setTimeout(() => {
BooksAPI.search(query).then(books => {
this.setState({ searchResult: books });
});
}, 800);
} else {
this.setState({ searchResult: [] });
}
}
);
};

if else statement not working in react component

I am trying to implement a condition in my react component . When the user triggers the onClick the state updates allStakes creating one array of 4 values. The problem is that I do not want the user to input more than 4 values so tried to give the limit by doing an if else statement. I tried to add a console.log in both statements.The weird fact is that setState get updated but the csonole.log is never displayed.The component keeps rendering all the values that I insert even if the array is longer than 4. Thanks in advance
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
allStakes: []
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
const { allStakes } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
onClick={(newStake) => {
if (allStakes.length <= 3) {
this.setState({ allStakes: [allStakes, ...newStake] })
console.log('stop')
} else if (allStakes.length == 4) {
console.log('more than 3')
}
}}
/>
</div>
</li>
))
}
</ul>
</div>
May be it happens because of incorrect array destructuring. Try to change this code:
this.setState({ allStakes: [allStakes, ...newStake] })
by the next one:
this.setState({ allStakes: [newStake, ...allStakes] })
Your state belongs to your FetchRandomBet component and you are trying to update that from your imported component. There are 2 solutions to that.
1> Wrap your Stake component to a separate component with onClick handler something like this
<div onClick={(newStake) => {
if (allStakes.length <= 3) {
this.setState({
allStakes: [allStakes, ...newStake
]
})
console.log('stop')
} else if (allStakes.length == 4) {
console.log('more than 3')
}
}}><Stake /></div>
Or
2> Pass the state as a prop to the Stake component which will be responsible to update the state for FetchRandomBet. something like this
<Stake parentState={this}/>
And inside the Stake component change the parentState on click of wherever you want.
I solved the problem. I transfered the onClick method in stake component and I handled the upload of the common array with an array useState. I add the value to newStake and when I click ok I retrieve newStake and spread it into a new array and then I check that array. If there is a value should not keep adding otherwise it can add values. It works fine. Thanks anyway
import React, { useState } from 'react';
import CurrencyInput from 'react-currency-input-field';
function Stake(props) {
const [newStake, setStake] = useState(null);
const [allStakes, setStakes] = useState(null);
const changeStake = (e) => {
setStake([e.target.value])
}
const mySubmit = () => {
if (!allStakes) {
setStakes([...newStake, allStakes])
props.onClick(newStake);
} else if (allStakes) {
console.log('stop')
}
}
return (
<>
<CurrencyInput
onChange={changeStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
<button onClick={mySubmit}>yes</button>
<button>no</button>
{newStake}
</>
);
}
export default Stake;

Parsing error: 'import' and 'export' may only appear at the top level (react)

I'm using react and getting the following error with my code: "Parsing error: 'import' and 'export' may only appear at the top level"
I checked all my brackets and it looks like everything is right on that end. This error arose when I attempted to debug my constructor code by adding a class (tbh not sure if that's right
either). So not sure if the current parsing error is associated with that fix?
const Header = () => (
<div className = 'header grid'>
<h1 className="title">Jiffy</h1>
</div>
)
// import React, { useState } from 'react';
// function App() {
// // Declare a new state variable, which we'll call "count"
// const [props] = useState('');
class App {
constructor() {
constructor(props); {
super(props);
this.state = {
searchTerm: ''
};
}
// with create react app we can write our methods
// as arrow functions, menaing we don't need the constructor and bind
const handleChange = event => {
const { value } = event.target;
this.setState((prevState, props) => ({
// we take our old props and spread them out here
...prevState,
// and then we overwrite the ones we want after
searchTerm: value
}));
if (value.length > 2) {
}
};
const handleKeyPress = event => {
const { value } = event.target;
// when we have 2 or more characters in our search box
// and we have also pressed enter, we thten want to run a search
if (value.length > 2 && event.key === 'Enter') {
alert(`search for ${value}`);
}
};
// const searchTerm = this.state.searchTerm
const { searchTerm } = this.state;
return (
<div className="page">
<Header />
<div className='search grid'>
<input
className='input grid-item'
placeholder='Type something'
onChange={handleChange}
onKeyPress={handleKeyPress}
value={searchTerm} />
</div>
</div>
);
export default App;
}
}

Reference a Field's value in a FieldArray for className change

I have a React Component (originally written by someone else) that displays a form for person (redux-form). Recently, I've changed the component to be a FieldArray (component of redux-form).
I have a validation for the email Field that impacts className for the email Field (red colored when email is incorrectly formatted, black colored otherwise). It worked fine when it wasn't a FieldArray but now it validates all email Fields at once because of
// (in constructor)
this.email = React.createRef();
// (in Field)
ref={props.parent.email}
, i.e. props.parent.email is a global/static ref.
Example: There are two persons. One of them has an incorrectly formatted email, but both emails are displayed in red.
As I understand it, I'd need to have a dynamic ref but that didn't work the way I tried.
ref={`${person}.email.ref`}
Error is
"Function components cannot have refs. Did you mean to use React.forwardRef()?"
I didn't find anything helpful on forwardRef regarding FieldArray, besides the fact that it is a valid prop.
My objective is: When several persons are created by the user and/or loaded from Redux store, be able to show every correctly formatted email in black, and every incorrectly formatted email in red.
Any help is greatly appreciated!
import React from "react";
import { Field, reduxForm, FieldArray } from "redux-form";
import { connect } from "react-redux";
import classNames from 'classnames'
import { MAIL_PATTERN } from "../some_file";
import MoreGeneralComponent from "../some_other_file";
const renderField = ({ input, label, type, options, className, meta: { touched, error }, style, disabled, hidden }) => (
<div style={style}>
<label>{label}</label>
<div>
<input {...input} disabled={disabled ? true : false} hidden={hidden ? true : false} type={type} placeholder={label} className={className} />
</div>
</div>
);
const renderPerson = ({ fields, meta: { error, submitFailed }, ...props }) => {
setTimeout(props.validate(fields));
return (
<ul className="personlist">
{fields.map((person, index) => (
<li key={index} className="person">
<h4>Person #{index + 1}</h4>
<Field
component={renderField}
type="text"
ref={props.parent.email}
className={classNames({invalid: !props.parent.state.validEmail})}
validate={props.parent.validateEmail}
name={`${person}.email`}
label="Email"
key={`${person}.email`}
></Field>
<button type="button" onClick={() => fields.remove(index)}>
Remove
</button>
</li>
))}
{(!(fields.length >= props.max)) && (
<li className="addperson">
<button
type="button"
onClick={() => fields.push({})}
disabled={fields.length >= props.max}
>
Add Person
</button>
{submitFailed && error && <span>{error}</span>}
</li>)}
</ul>
);
};
class Person extends MoreGeneralComponent {
constructor(props) {
super(props);
if (this.state.ready) {
this.max = 4;
this.data = ["email"];
this.email = React.createRef();
}
}
validate = fields => {
if (!fields || !fields.getAll()) {
return;
}
let valid = true;
fields.getAll().forEach(field => {
for (let d of this.data) {
if (!field[d] || field[d].length < 2) {
valid = false;
return;
} else if (d === "email") {
valid = field[d] && MAIL_PATTERN.test(field[d]) ? valid : undefined;
}
}
});
if (valid !== this.state.valid) {
this.setState({
valid: valid
});
}
};
validateEmail = (value) => {
const valid = value && MAIL_PATTERN.test(value) ? value : undefined;
this.setState({validEmail: !!valid});
return valid
}
renderQuestion() {
return (
<div className={style.question}>
<fieldset>
<FieldArray
name="persons"
component={renderPerson}
props={{ max: this.max, validate: this.validate, parent: this }}
rerenderOnEveryChange={true}
/>
</fieldset>
</div>
);
}
}
const mapStateToProps = s => {
const persons = s.persons
var initialValuesPersons = []
persons.map(item => initialValuesPersons.push({
"email": item.email || ""
}))
var initialValues = { "persons": initialValuesPersons}
return {
initialValues,
formdata: s.form
}
}
export default connect(mapStateToProps, null)(reduxForm(
{
form: 'person',
destroyOnUnmount: false,
enableReinitialize: true,
keepDirtyOnReinitialize: true
})(Person))
With the help of a colleague I came up with a work-around that just uses JS (working around using setState).
toggleClass = (elementName, addInvalid) => {
const element = document.getElementsByName(elementName);
element.forEach(item => {
// remove class and add later, if necessary
item.classList.remove("invalid");
if (addInvalid) {
item.classList.add("invalid");
}
})
}
validateEmail = (value, person, form, nameField) => {
const valid = value && MAIL_PATTERN.test(value) ? value : undefined;
this.toggleClass(nameField, !valid);
return valid;
}

Passing props to Parent component

I am really novice to React and I am stuck with this one.
I want to pass data from NewAction component to its parent NewActionSet.
I dont know what i am missing.
I am developing an on-boarding platform with a lot a components and I aim to send all the data entered into all the components to a server.
React parent Component:
import React from 'react'
import './NewActionSet.css'
import axios from 'axios'
import { Container, Segment, Header, Input } from 'semantic-ui-react'
import NewAction from './NewAction'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewActionSet extends React.Component {
constructor (props) {
super(props)
this.state = {
actions: [],
actionType: '',
actionValue: '',
creationStatus: undefined
}
}
handleActions = value => {
this.setState({
actionsList: value
})
console.log(this.state.actionsList)
}
handleSubmit = event => {
event.preventDefault()
console.log(this.state)
axios
.post(
'/assistant/actions/',
{ ...this.state.values },
{ headers: {
xsrfHeaderName: 'X-CSRFToken',
xsrfCookieName: 'csrftoken'
},
withCredentials: true
}
)
.then(response => {
console.log(response)
this.setState({
creationStatus: true
})
})
.catch(error => {
console.log(error)
this.setState({
creationStatus: false
})
})
}
addNewAction = () => {
let { actions } = this.state
this.setState({
actions: [...actions, <NewAction onNewAction={this.handleActionstoParent} />]
})
}
handleActionstoParent = (action2Value, selectedAction) => {
this.setState({
actionType : selectedAction,
actionValue : action2Value
})
// console.log(this.state.actionType, this.state.actiondValue)
}
renderActions () {
return this.state.actions.map((action, index) => {
return (
<NewAction
key={index}
type={this.props.actionType}
content={action.content}
onNewAction={this.handleActionstoParent}
/>
)
})
}
render () {
let index = 0
return (
<Container>
<Header> Action sets </Header>
<Header color='grey' as='h3'>
SET #{index + 1}
</Header>
{this.renderActions()}
<button onClick={() => this.addNewAction()}> New Action </button>
</Container>
)
}
}
export default NewActionSet
React child component
import React from 'react'
import './NewActionSet.css'
import { Header, Dropdown } from 'semantic-ui-react'
import NewSpeechText from './NewSpeechText'
import NewAddPageURL from './NewAddPageURL'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewAction extends React.Component {
constructor (props) {
super(props)
this.state = {
availableActions: [
{ key: 1, text: 'Navigate to page', value: 'Navigate to page' },
{ key: 2, text: 'Play speech', value: 'Play speech' }
],
selectedAction: '',
actionValue: '',
currentElement: ''
}
}
handleActionURL = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
handleActionSpeech = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (selected) => {
var action2Value = this.state.actionValue;
console.log(action2Value)
var action2Type = this.state.actionType
this.props.onNewAction(action2Value, action2Type)
console.log(action2Type)
// console.log(this.state.actionValue, this.state.selectedAction)
}
handleChange = (e, { value }) => {
let element
this.setState({
selectedAction: value
})
if (value === 'Navigate to page') {
element = <NewAddPageURL onNewAddPageURL={this.handleActionURL} onChange={this.handleActionstoParent()} />
} else if (value === 'Play speech') {
element = <NewSpeechText onNewSpeechText={this.handleActionSpeech} onChange={this.handleActionstoParent()} />
}
this.setState({
currentElement: element
})
}
render () {
const { value } = this.state
let index = 0
return (
<div className='action'>
<div className='container'>
<Header color='grey' as='h4'>
ACTION #{index + 1}
</Header>
<div className='row'>
<div className='col-md-4'>
<Dropdown
onChange={this.handleChange}
options={this.state.availableActions}
placeholder='Choose an action'
selection
value={value}
/>
</div>
<div className='col-md-4' />
<div className='col-md-4' />
</div>
<div style={{ marginBottom: '20px' }} />
{this.state.currentElement}
</div>
</div>
)
}
}
export default NewAction
Can you please assist?
Thanks a lot
The handleActionstoParent function in NewAction component is the problem.
When you send data from child to parent, actually the data is not updated data.
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (e) => {
this.setState({ [e.target.name]: e.target.value }, () => {
var action2Value = this.state.actionValue;
var action2Type = this.state.actionType;
this.props.onNewAction(action2Value, action2Type);
});
}
You could pass a function to NewAction, in example below we pass handleDataFlow function to our child component and then use it in our child component to pass data higher:
import React from 'react'
import './NewActionSet.css'
import { Header, Dropdown } from 'semantic-ui-react'
import NewSpeechText from './NewSpeechText'
import NewAddPageURL from './NewAddPageURL'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewAction extends React.Component {
constructor (props) {
super(props)
this.state = {
availableActions: [
{ key: 1, text: 'Navigate to page', value: 'Navigate to page' },
{ key: 2, text: 'Play speech', value: 'Play speech' }
],
selectedAction: '',
actionValue: '',
currentElement: ''
}
}
handleActionURL = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
handleActionSpeech = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (selected) => {
var action2Value = this.state.actionValue;
console.log(action2Value)
var action2Type = this.state.actionType
this.props.onNewAction(action2Value, action2Type)
console.log(action2Type)
// console.log(this.state.actionValue, this.state.selectedAction)
}
handleChange = (e, { value }) => {
let element
this.setState({
selectedAction: value
})
this.props.handleDataFlow(value)
if (value === 'Navigate to page') {
element = <NewAddPageURL onNewAddPageURL={this.handleActionURL} onChange={this.handleActionstoParent()} />
} else if (value === 'Play speech') {
element = <NewSpeechText onNewSpeechText={this.handleActionSpeech} onChange={this.handleActionstoParent()} />
}
this.setState({
currentElement: element
})
}
render () {
const { value } = this.state
let index = 0
return (
<div className='action'>
<div className='container'>
<Header color='grey' as='h4'>
ACTION #{index + 1}
</Header>
<div className='row'>
<div className='col-md-4'>
<Dropdown
onChange={this.handleChange}
options={this.state.availableActions}
placeholder='Choose an action'
selection
value={value}
/>
</div>
<div className='col-md-4' />
<div className='col-md-4' />
</div>
<div style={{ marginBottom: '20px' }} />
{this.state.currentElement}
</div>
</div>
)
}
}
export default NewAction
Data flow in React is unidirectional. Data has one, and only one, way to be transferred: from parent to child.
To update parent state from child you have to send action (in props).
<NewAction updateParentState={this.doSmth} />
...
const doSmth = params => { this.setState({ ... })
and in NewAction you can call it in specific case
let parentUpdateState = ....
this.props.updateParentState(parentUpdateState);

Resources