In the following example, User2's messages are getting erased from the state as soon as the User1 sends a message.
User1 sends a message, it gets displayed on screen. As soon as User2 replies, User1's messages disappear.
I'm persuaded it's a React setState mistake but I've followed the react-native-chat-ui's docs as much as possible but somehow there is something going wrong and I cannot put my finger on it.
Here's a video of the bug in action: https://streamable.com/rxbx18
Thank you.
import React, { useEffect, useState } from 'react';
import { Chat, MessageType } from '#flyerhq/react-native-chat-ui'
import { SafeAreaProvider } from 'react-native-safe-area-context'
const uuidv4 = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.floor(Math.random() * 16)
const v = c === 'x' ? r : (r % 4) + 8
return v.toString(16)
})
};
const user = { id: uuidv4(), firstName: 'User1' };
const chatbot = { id: uuidv4(), firstName: 'User2' };
const App = () => {
const [messages, setMessages] = useState<MessageType.Any[]>([])
const addMessage = (message: MessageType.Any) => {
setMessages([message, ...messages]);
};
const handleSendPress = (message: MessageType.PartialText) => {
// display user message
const textMessage: MessageType.Text = {
author : user,
createdAt: Date.now(),
id : uuidv4(),
text : message.text,
type : 'text',
};
addMessage(textMessage);
// display bot message
// NOTE: adding a timeout so that you can see user's message for a second...
setTimeout(() => {
const chatbotTextMessage: MessageType.Text = {
author : chatbot,
createdAt: Date.now(),
id : uuidv4(),
text : `Response that will erase user's messages...`,
type : 'text',
};
addMessage(chatbotTextMessage);
}, 1000);
};
return (
<SafeAreaProvider>
<Chat
messages={messages}
showUserNames={true}
onSendPress={handleSendPress}
user={user}
/>
</SafeAreaProvider>
);
}
export default App;
Related
1. I set name in statesCorelatedFields and setStatesCorelatedFields inside below codes,
how can I get state and setState variables from there? (please see below example)
2. Does my below approach right?
3. Any suggestion will be highly appreciated.
I am using react native 0.68.5.
Previously, I used class component, now I am migrating to function component.
I have a reuseable file and App file like below:
reuseable.js
// import ...
export const handleFocus = (
state,
setState,
focusStyle,
// array of state variables of corelated fields
statesCorelatedFields,
// array of setState methods of corelated fields
setStatesCorelatedFields,
// blur style if no text value
blurNoTextStyle,
) => {
const stateData = { ...state };
stateData.styleName = { ...focusStyle };
// for corelated fields: empty value and set blurNoTextStyle
if (statesCorelatedFields.length) {
let stateCorelatedFieldData;
for (i = 0; i < statesCorelatedFields.length; i++) {
stateCorelatedFieldData = { ...statesCorelatedFields[i] };
stateCorelatedFieldData.value = '';
stateCorelatedFieldData.styleName = { ...blurNoTextStyle };
setStatesCorelatedFields[i](stateCorelatedFieldData);
}
}
setState(stateData);
};
// export const handleChangeText=(state, setState, text, ...)=>{...}
// export const handleBlur=(state, setState, ...)=>{...}
// ...
App.js
// import ...
// import all methods from reuseable.js
const App = () => {
const [email, setEmail] = useState({
name: 'email',
value: '',
styleName: { ...styles.blurNoTextStyle },
error: '',
statesCorelatedFields: [],
setStatesCorelatedFields: [],
});
const [countryCode, setCountryCode] = useState({
name: 'countryCode',
value: '',
styleName: { ...styles.blurNoTextStyle },
error: '',
// I set name here; how can I get state and setState variable from here
statesCorelatedFields: ['phoneNumber'],
setStatesCorelatedFields: ['setPhoneNumber'],
});
const [phoneNumber, setPhoneNumber] = useState({
name: 'phoneNumber',
value: '',
styleName: { ...styles.blurNoTextStyle },
error: '',
statesCorelatedFields: [],
setStatesCorelatedFields: [],
});
return (
<>
{/* components */}
<TextInput
value={countryCode.value}
onChangeText={(text) => handleChangeText(countryCode, setCountryCode, text)}
onFocus={() => handleFocus(countryCode, setCountryCode, styles.focusStyle, countryCode.statesCorelatedFields, countryCode.setStatesCorelatedFields)}
onBlur={() => handleBlur(countryCode, setCountryCode)}
/>
{/* other components */}
</>
);
}
const styles = StyleSheet.create({
// styles goes here
});
export default App;
Thanks in advance.
Moves this setStatesCorelatedFields out of the loop body you are updating the state on every iteration which doesn't need. It causes to slow down your component
you can do like this:
if (statesCorelatedFields.length) {
let stateCorelatedFieldData;
for (i = 0; i < statesCorelatedFields.length; i++) {
stateCorelatedFieldData = { ...statesCorelatedFields[i] };
stateCorelatedFieldData.value = "";
stateCorelatedFieldData.styleName = { ...blurNoTextStyle };
}
setStatesCorelatedFields[i](stateCorelatedFieldData);
}
I have a component that I want to show a graph with multiple line series representing price changes over the last 24 hours. I have an endpoint that sends this data and I use the code below to show it.
One of the issues comes from errors seeming to come from the library itself meaning the graph will not even show up. Errors from the console when I load the page.
Other times, the page will load for a second and then go white and drain enough CPU to cause a crash.
The few times that the graph actually shows up on screen, it does not show any lines until the lines 81-85 are uncommented which it then shows the lines but does not zoom in on them leaving a mess on the screen.
Any help would be much appreciated.
/* eslint-disable new-cap */
/* eslint-disable #typescript-eslint/no-unused-vars */
/* eslint-disable no-magic-numbers */
import React, { useEffect, useState } from "react";
import { LegendBoxBuilders, lightningChart, Themes } from "#arction/lcjs";
import "./TopCurrencyGraph.css";
import axios from "axios";
export interface data {
data: dataPoint[];
}
export interface dataPoint {
currency: string;
percentage: number;
timestamp: string;
}
interface graphPoint {
x: number;
y: number;
}
const TopCurrencyGraph = () => {
const historicalAddr = `http://${
process.env.back || "localhost:8000"
}/historical24hChangeData`;
useEffect(() => {
const map: { [name: string]: graphPoint[] } = {};
axios
.get(historicalAddr)
.then((res) => {
const { points } = res.data;
const pointList = points as dataPoint[];
pointList.forEach((obj) => {
const newPoint = {
x: new Date(obj.timestamp).getTime() * (60 * 24),
y: obj.percentage * 100,
};
if (obj.currency in map) {
map[obj.currency].push(newPoint);
} else {
map[obj.currency] = [newPoint];
}
});
})
.catch((err) => {
console.log(err, historicalAddr);
});
const chart = lightningChart().ChartXY({
theme: Themes.lightNew,
container: "currency-graph",
});
chart.setTitle("Top Currencies");
chart.getDefaultAxisX().setTitle("Time");
chart.getDefaultAxisY().setTitle("Percentage Change");
const entries = Object.entries(map);
const names = entries.map(([a, _b]) => a);
const lists = entries.map(([_, b]) => b);
const seriesArray = new Array(5).fill(null).map((_, idx) =>
chart
.addLineSeries({
dataPattern: {
pattern: "ProgressiveX",
},
})
// eslint-disable-next-line arrow-parens
.setStrokeStyle((stroke) => stroke.setThickness(1))
.setName(names[idx])
);
seriesArray.forEach((series, idx) => {
if (idx === 1) {
series.add(lists[idx]);
}
});
chart.addLegendBox(LegendBoxBuilders.HorizontalLegendBox).add(chart);
return () => {
chart.dispose();
};
}, []);
// done thnx
return (
<div className="graph-container">
<div id="currency-graph" className="graph-container"></div>
</div>
);
};
export default TopCurrencyGraph;
Your code looks syntax wise correct, but I believe you are running into issues due to not managing asynchronous code (axios getting data from your endpoint) properly.
const map: { [name: string]: graphPoint[] } = {};
axios
.get(historicalAddr)
.then((res) => {
// This code is NOT executed immediately, but only after some time later.
...
})
// This code and everything below is executed BEFORE the code inside `then` block.
// Because of this, you end up supplying `undefined` or other incorrect values to series / charts which shows as errors.
const chart = lightningChart().ChartXY({
theme: Themes.lightNew,
container: "currency-graph",
});
You might find it useful to debug the values you supply to series, for example like below. I think the values are not what you would expect.
seriesArray.forEach((series, idx) => {
if (idx === 1) {
console.log('series.add', lists[idx])
series.add(lists[idx]);
}
});
Improvement suggestion
Here's my attempt at modifying the code you supplied to manage the asynchronous data loading correctly, by moving all code that relies on the data after the data is processed.
/* eslint-disable new-cap */
/* eslint-disable #typescript-eslint/no-unused-vars */
/* eslint-disable no-magic-numbers */
import React, { useEffect, useState } from "react";
import { LegendBoxBuilders, lightningChart, Themes } from "#arction/lcjs";
import "./TopCurrencyGraph.css";
import axios from "axios";
export interface data {
data: dataPoint[];
}
export interface dataPoint {
currency: string;
percentage: number;
timestamp: string;
}
interface graphPoint {
x: number;
y: number;
}
const TopCurrencyGraph = () => {
const historicalAddr = `http://${
process.env.back || "localhost:8000"
}/historical24hChangeData`;
useEffect(() => {
const chart = lightningChart().ChartXY({
theme: Themes.lightNew,
container: "currency-graph",
});
chart.setTitle("Top Currencies");
chart.getDefaultAxisX().setTitle("Time");
chart.getDefaultAxisY().setTitle("Percentage Change");
const seriesArray = new Array(5).fill(null).map((_, idx) =>
chart
.addLineSeries({
dataPattern: {
pattern: "ProgressiveX",
},
})
// eslint-disable-next-line arrow-parens
.setStrokeStyle((stroke) => stroke.setThickness(1))
);
chart.addLegendBox(LegendBoxBuilders.HorizontalLegendBox).add(chart);
axios
.get(historicalAddr)
.then((res) => {
const { points } = res.data;
const pointList = points as dataPoint[];
const map: { [name: string]: graphPoint[] } = {};
pointList.forEach((obj) => {
const newPoint = {
x: new Date(obj.timestamp).getTime() * (60 * 24),
y: obj.percentage * 100,
};
if (obj.currency in map) {
map[obj.currency].push(newPoint);
} else {
map[obj.currency] = [newPoint];
}
});
const entries = Object.entries(map);
const names = entries.map(([a, _b]) => a);
const lists = entries.map(([_, b]) => b);
seriesArray.forEach((series, idx) => {
series.setName(names[idx])
if (idx === 1) {
series.add(lists[idx]);
}
});
})
.catch((err) => {
console.log(err, historicalAddr);
});
return () => {
chart.dispose();
};
}, []);
// done thnx
return (
<div className="graph-container">
<div id="currency-graph" className="graph-container"></div>
</div>
);
};
export default TopCurrencyGraph;
I have hosted a peer to peer meeting react app on netlify. I have used Peerjs for my video purpose. Everything is working as expected except the video. For some networks the video of the the remote person is working and for some others it is not working. I looked up and found out that it may be a STUN/TURN issue. I then implemented all the STUN/TURN servers in my code. However the video is still not getting setup in some cases. In some cases it is working fine, in others the video is not showing up. Herewith, I am attaching th code for the video and the link to the site.
import React,{useEffect,useState} from 'react';
import {io} from "socket.io-client";
import {useParams} from 'react-router-dom';
import {Grid} from "#material-ui/core";
import Peer from 'peerjs';
var connectionOptions = {
"force new connection" : true,
"reconnectionAttempts": "Infinity",
"timeout" : 10000,
"transports" : ["websocket"]
};
const Videobox = ({isVideoMute,isAudioMute}) => {
var myPeer = new Peer(
{
config: {'iceServers': [
{urls:'stun:stun01.sipphone.com'},
{urls:'stun:stun.ekiga.net'},
{urls:'stun:stun.fwdnet.net'},
{urls:'stun:stun.ideasip.com'},
{urls:'stun:stun.iptel.org'},
{urls:'stun:stun.rixtelecom.se'},
{urls:'stun:stun.schlund.de'},
{urls:'stun:stun.l.google.com:19302'},
{urls:'stun:stun1.l.google.com:19302'},
{urls:'stun:stun2.l.google.com:19302'},
{urls:'stun:stun3.l.google.com:19302'},
{urls:'stun:stun4.l.google.com:19302'},
{urls:'stun:stunserver.org'},
{urls:'stun:stun.softjoys.com'},
{urls:'stun:stun.voiparound.com'},
{urls:'stun:stun.voipbuster.com'},
{urls:'stun:stun.voipstunt.com'},
{urls:'stun:stun.voxgratia.org'},
{urls:'stun:stun.xten.com'},
{
urls: 'turn:numb.viagenie.ca',
credential: 'muazkh',
username: 'webrtc#live.com'
},
{
urls: 'turn:192.158.29.39:3478?transport=udp',
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
username: '28224511:1379330808'
},
{
urls: 'turn:192.158.29.39:3478?transport=tcp',
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
username: '28224511:1379330808'
}
]} /* Sample servers, please use appropriate ones */
}
);
const peers = {}
const [socket, setSocket] = useState()
const {id:videoId} = useParams();
const videoGrid = document.getElementById('video-grid')
useEffect(()=> {
const s=io("https://weconnectbackend.herokuapp.com",connectionOptions);
setSocket(s);
return () => {
s.disconnect();
}
},[])
// let myVideoStream;
const [myVideoStream, setmyVideoStream] = useState()
const muteUnmute = () => {
const enabled = myVideoStream.getAudioTracks()[0].enabled;
if (enabled) {
myVideoStream.getAudioTracks()[0].enabled = false;
//setUnmuteButton();
} else {
//setMuteButton();
myVideoStream.getAudioTracks()[0].enabled = true;
}
}
const playStop = () => {
//console.log('object')
let enabled = myVideoStream.getVideoTracks()[0].enabled;
if (enabled) {
myVideoStream.getVideoTracks()[0].enabled = false;
//setPlayVideo()
} else {
//setStopVideo()
myVideoStream.getVideoTracks()[0].enabled = true;
}
}
useEffect(() => {
if(myVideoStream)
playStop()
}, [isVideoMute])
useEffect(() => {
if(myVideoStream)
muteUnmute()
}, [isAudioMute])
useEffect(() => {
if(socket== null)
return;
myPeer.on('open',id=>{
socket.emit('join-room',videoId,id);
})
const myVideo = document.createElement('video')
myVideo.muted = true
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
// myVideoStream = stream;
window.localStream=stream;
setmyVideoStream(stream);
console.log(myVideoStream,"myvideostream");
addVideoStream(myVideo, stream)
myPeer.on('call', call => {
call.answer(stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream)
})
})
socket.on('user-connected',userId =>{
connectToNewUser(userId, stream)
})
socket.on('user-disconnected', userId => {
if (peers[userId]) peers[userId].close()
})
})
}, [socket,videoId])
function addVideoStream(video, stream) {
video.srcObject = stream
video.addEventListener('loadedmetadata', () => {
video.play()
})
videoGrid.append(video)
}
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream)
})
call.on('close', () => {
video.remove()
})
peers[userId] = call
}
return (
<div id="video-grid" className="videoStyleFromDiv">
{/* <Video srcObject={srcObject}/> */}
</div>
)
}
export default Videobox
Website Link
The TURN servers you are using have been out of commission for a couple of years in the case of the ones taken from https://www.html5rocks.com/en/tutorials/webrtc/infrastructure/
Copying credentials from random places is not how TURN works, you will need to run your own servers.
enter image description here
I want to count "indie" and "action" at the same time when the button is clicked. However, the only real application is "action". Please tell me how.
This is my solution to your problem
import React, { useState, useEffect } from "react";
const games = [
{ id: 1, genre: ["indie", "action"] },
{ id: 2, genre: ["indie"] },
{ id: 3, genre: ["action"] }
];
function ButtonComponent(props) {
const { genre, fn } = props;
return <button onClick={() => fn(genre)}>Click</button>;
}
function TestPage() {
const [genre, setGenre] = useState({ indie: 0, action: 0 });
const addGenrecount = (genres) => {
setGenre((previousState) => {
let { indie, action } = previousState;
genres.forEach((genre) => {
if (genre === "indie") indie = indie + 1;
if (genre === "action") action = action + 1;
});
return { indie, action };
});
};
useEffect(() => console.log("genre", genre), [genre]); // Logs to the console when genre change
return games.map((game) => {
const { id, genre } = game;
return <ButtonComponent key={id} genre={genre} fn={addGenrecount} />;
});
}
export default TestPage;
You may also go to codesandbox to test the demo
https://codesandbox.io/s/xenodochial-dirac-q01h4?file=/src/App.js:0-968
Just Friendly Tip:
If you need help regarding react I recommend to upload your code to codesandbox so that we can easily reproduce or solve the problem
I cannot figure out why prototypes in my function component in React fails with this:
`index.js:1 Warning: Failed prop type: The prop `profiles` is marked as required in `Home`, but its value is `undefined`.`
The app is working fine and Profiles is defined and I'm using React-redux with hooks and maybe that causing the issue becasue I don't know actually what to do to make the PropTypes to work
My home where this come ups:
import React, { useEffect, useState } from "react";
import { Row, Col, Jumbotron, Container, Image } from "react-bootstrap";
import { ProfileMiddleware } from "../Store/Middleware";
import { PropTypes } from "prop-types";
import { useDispatch, useSelector } from "react-redux";
import { USERNAME } from "../Services/constAPI";
import Experiences from "../Components/Experiences/Experiences";
import { Spinner } from "../Components/Spinner/Spinner.js";
const Home = () => {
const dispatch = useDispatch();
const { profiles, displaySpinner } = useSelector(state => ({
profiles: state.ProfileReducer.profiles,
displaySpinner: state.ProfileReducer.displaySpinner
}));
useEffect(() => {
dispatch(ProfileMiddleware.getOneProfile(USERNAME));
}, [dispatch]);
return !profiles.object ? (
<>
<Jumbotron>
<Container>
<Row>
<Col md={6}>
<Image src={profiles.imageUrl} alt="profile" roundedCircle />
</Col>
<Col md={6}>
<h1>{profiles.firstname + " " + profiles.surname}</h1>
<h4>{profiles.title}</h4>
<h5>{profiles.area}</h5>
<p>{profiles.email}</p>
<p>{profiles.bio}</p>
</Col>
</Row>
<Spinner displaySpinner={displaySpinner} />
</Container>
</Jumbotron>
<Experiences />
</>
) : (
<h3 className="red-text mt-5">The profile is not available</h3>
);
};
Home.propTypes = {
profiles: PropTypes.object.isRequired
};
export default Home;
The reducer as I'm using Redux
import { ProfileActions } from "../Actions";
function ProfileReducer(
state = {
profiles: {},
displaySpinner: false
},
action
) {
console.log("data in action", action.data);
console.log("Action type", action.type);
switch (action.type) {
case ProfileActions.GET_ONE_PROFILE:
return {
...state,
displaySpinner: true
};
case ProfileActions.GET_ONE_PROFILE_SUCCESS:
return {
...state,
profiles: action.data,
displaySpinner: false
};
default:
return state;
}
}
export default ProfileReducer;
I can show else if necessary but the APP works but PropTypes saying profiles are undefined that I cannot understand.
You're not passing in any props to Home. If you were, it would look something like
const Home = (props) => {
Instead, you are getting profiles from your redux store. So simply change
Home.propTypes = {
profiles: PropTypes.object.isRequired
};
to
Home.propTypes = {};
profiles is not a prop being passed to Home. The proptypes for the Home component should be deleted.
You say in the comments:
I have to check the profiles to be an obj with PropTypes I'm trying to find a solution to make it works. If you know a solution please share an answer
If you can guarantee that in your selector that's even better, but there're ways to do so in the component.
So let's take a snippet of your code and do that:
const Home = () => {
const dispatch = useDispatch();
const { profiles, displaySpinner } = useSelector(state => ({
profiles: state.ProfileReducer.profiles,
displaySpinner: state.ProfileReducer.displaySpinner
}));
// note Array's and other variable are also objects
// so we need to do a special check
const isProfilesAnObject = profiles && profiles.constructor.name === 'Object';
// created outside of `every` loop, and ternary shortcut to empty array
const profilesKeys = isProfilesAnObject ? Object.keys(profiles) : [];
// check that profileKeys is a subset of REQUIRED_KEYS
// n.b. you need to define this somewhere
const isProfilesCorrect = REQUIRED_KEYS.every(requiredKey => profileKeys.includes(requiredKey))
useEffect(() => {
dispatch(ProfileMiddleware.getOneProfile(USERNAME));
}, [dispatch]);
return isProfilesCorrect ?
This should work like below:
const checkProfiles = profiles => {
const REQUIRED_KEYS = ['a', 'b', 'z', 'y']
const isProfilesAnObject = profiles && profiles.constructor.name === 'Object';
// created outside of `every` loop, and ternary shortcut to empty array
const profilesKeys = isProfilesAnObject ? Object.keys(profiles) : [];
// check that profileKeys is a subset of REQUIRED_KEYS
// n.b. you need to define this somewhere
const isProfilesCorrect = REQUIRED_KEYS.every(requiredKey => profilesKeys.includes(requiredKey))
return isProfilesCorrect;
}
const [profile1, profile2, profile3] = [{
'a': '10'
}, {
a: '10', b: '20', z: '260', y: '250'
},{
a: '10', b: '20', extra_key: 'I\'m being a little bit extra', z: '260', y: '250'
}]
// missing keys
console.log(`profile1 (with keys ${Object.keys(profile1)}) is: ${checkProfiles(profile1) ?'correct': 'incorrect'}`);
// just the right number of keys
console.log(`profile2 (with keys ${Object.keys(profile2)}) is: ${checkProfiles(profile2) ?'correct': 'incorrect'}`);
// doesn't check if there aren't extra keys
console.log(`profile3 (with keys ${Object.keys(profile3)}) is: ${checkProfiles(profile3) ?'correct': 'incorrect'}`);
There is also invariant which you can use for stricter checking. It throws an error if something is false, so you can put exact checking in there, as seen below.
(n.b. ignore the process and module objects I had to define, and note I'm using tiny-invariant for convenience )
const checkProfiles = profiles => {
const REQUIRED_KEYS = ['a', 'b', 'z', 'y']
const isProfilesAnObject = profiles && profiles.constructor.name === 'Object';
// created outside of `every` loop, and ternary shortcut to empty array
const profilesKeys = isProfilesAnObject ? Object.keys(profiles) : [];
// check that profileKeys is a subset of REQUIRED_KEYS
// n.b. you need to define this somewhere
const isProfilesCorrect = REQUIRED_KEYS.every(requiredKey => profilesKeys.includes(requiredKey))
// really need to make sure 'z' exists and is a string
invariant(typeof profiles.z === 'string', `Profiles is expected to have a key 'z' that is a string, but found ${JSON.stringify(profiles.z)}`)
return isProfilesCorrect;
}
console.log(checkProfiles({
a: 10,
z: 'valid'
}))
try {
checkProfiles({
a: 10,
z: 20
});
} catch (e) {
console.error(e);
}
<script>
var process = {
env: 'production'
}
var module = {
exports: {}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/tiny-invariant#1.2.0/dist/tiny-invariant.cjs.min.js">
</script>
Either of those should work depending on how seriously you need to make sure your variables exist and are correct
Actually, you didn't pass props, pay attention to:
const Home = () => {
You should write const Home = props => { or destruct the props in the beginning of your function component, like below:
const Home = ({ profiles }) => {
And then use it inside your execution context of the function component. Also, you can put the default value for your props like below:
const Home = ({ profiles = 'something' }) => {
The 'something' is a sample, it can be everything, or write like below at the end of your function component declaration:
Home.defaultProps = {
profiles: 'something'
};
I hope it helps you, but surely you should read the ReactJS docs a little bit more.