I'm having some difficulty getting my camera to follow an object. The object is using a hook useSphere from #react-three/cannon. My sphere moves around fine, but the camera does not follow. Ive narrowed it down to my sphere position is not being updated because matrixAutoUpdate gets set to false. I'm unsure why this is happening because on other examples of Cannon that I tested matrixAutoUpdate stays true. Any ideas would be awesome, I have been stuck for a while and can't find anything on internet. Below is some code
import React from "react";
import {useFrame, useThree} from "#react-three/fiber";
import {useKeyboardControls} from "../hooks/useKeyboardControls";
import {FPVControls} from "./FPVControls";
import {Vector3} from "three";
import {useSphere} from "#react-three/cannon";
const SPEED = 6
export const Player = (props) => {
const {camera} = useThree()
//sphere movement controls
const {
moveForward,
moveBackward,
moveLeft,
moveRight,
jump
} = useKeyboardControls()
const [ref, api] = useSphere(() => ({
mass: 1,
type: 'Dynamic',
...props
}))
useFrame(() => {
camera.position.copy(ref.current.position)
const direction = new Vector3()
const frontVector = new Vector3(
0,
0,
(moveBackward ? 1 : 0) - (moveForward ? 1 : 0)
)
const sideVector = new Vector3(
(moveLeft ? 1 : 0) - (moveRight ? 1 : 0),
0,
0
)
direction
.subVectors(frontVector, sideVector)
.normalize()
.multiplyScalar(SPEED)
.applyEuler(camera.rotation)
api.velocity.set(direction.x, 0, direction.z)
})
console.log("ref position", ref)
return (
<>
<FPVControls/>
<mesh ref={ref}>
<sphereGeometry />
<meshStandardMaterial color={"#f30707"} />
</mesh>
</>
)
}
I found this helpful on a similar problem maybe it can help you :
a conversation about a similar issue
This similar issue is about a position not being updated by using cannon hooks, the key is to subscribe to the api position and store it in a ref as from what i understood.
Here is a more precise example in the AI component (line 69, you can find the ref on line 79):
usage example
Related
I am absolutely clueless regarding an issue i am having with blurry images on iOS.
I am working with react, and i am using #use-gesture library (use-gesture.netlify.app) to enable pinch zoom on some small images and videos. It all works like a charm on desktop, but on my mobile device images are always blurry-bad-quality when pinch-zoomed. Videos are not affected by that quality loss. I don´t think it has anything todo with #use-gesture, since ive been using a lightbox component before that and had the same issue.
I've already read every single thread i could find regarding image scale on iOS but wasnt able to find a solution yet.
What i've gathered/tried so far:
Adding to index.html:
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1">
and adding this to the img tag:
resizeMode="contain"
aspectRatio="1.5"
height= "undefined"
width= "undefined"
Does it maybe have something to do with a difference in image rendering on mobile devices, to me it seems like images dimensions are defined onLoad in the size of my thumbnails and original image size/resolution is ignored? idk..
This is the code of a complete re-build of the issue i am having based on a new react project but i still get the same effect
import test from './imgtest.jpg';
import './App.css';
import React, { useRef, useEffect, useState } from 'react';
import { useSpring, animated } from "react-spring";
import { createUseGesture, dragAction, pinchAction } from '#use-gesture/react'
const useGesture = createUseGesture([dragAction, pinchAction])
function App() {
useEffect(() => {
const handler = e => e.preventDefault()
document.addEventListener('gesturestart', handler)
document.addEventListener('gesturechange', handler)
document.addEventListener('gestureend', handler)
return () => {
document.removeEventListener('gesturestart', handler)
document.removeEventListener('gesturechange', handler)
document.removeEventListener('gestureend', handler)
}
}, [])
const ref = React.useRef(null)
const [style, api] = useSpring(() => ({
x: 0,
y: 0,
scale: 1,
rotateZ: 0,
config: {
},
}))
useGesture(
{
onPinch: ({ origin: [ox, oy], first, active, movement: [ms], offset: [s, a], memo }) => {
if (first) {
const { width, height, x, y } = ref.current.getBoundingClientRect()
const tx = ox - (x + width / 2)
const ty = oy - (y + height / 2)
memo = [style.x.get(), style.y.get(), tx, ty]
}
const x = memo[0] - (ms - 1) * memo[2]
const y = memo[1] - (ms - 1) * memo[3]
api.start({ scale: s , offset: active ? x : 0, offset: active ? y : 0 })
return memo
},
},
{
target: ref,
pinch: { scaleBounds: { min: 1, max: 4 }, rubberband: true },
}
)
return (
<div className="App">
<animated.div ref={ref} style={style}>
<img
resizeMode="contain"
aspectRatio="1.5"
height= "undefined"
width= "undefined"
src={test}
className="App-logo"
/>
</animated.div>
</div>
);
}
export default App;
Please can somebody help me with this?
IOS devices have different DPIs than desktops. You need a bigger image to get a clear image.
also check out this link
This is a very small two-page project, so this won't take much time.
I am trying to convert this Github repo from class-based component to functional component. I am very close but the logic is just not working properly.
The useState hook especially, as in the values are not getting saved. So I tried a different approach.
This is the expected output which is the live demo of the original project:
https://matt-eric.github.io/web-audio-fft-visualization-with-react-hooks/
And this is where I am. This is the sandbox link.
Ignore the The error you provided does not contain a stack trace. error for now. Click on the x and refresh the small project window (not your browser tab) a couple of times until the audio plays on refresh. This is because google stops you from playing music on load.
I want to audio to play with the click of the button and not on load. But it is not working.
Thank whoever goes and looks into it.
There's some cleanup needed but this is working. https://codesandbox.io/s/xenodochial-wildflower-c5b35
All I really did was put all the functions in a useCallback made audioFile a ref, and then made the toggleAudio function which either plays or pauses the audio depending on its current state. One of the biggest problems I saw was that you were trying to initialize the audio on click, but that really should be done on mount, then the audio just starts when you click. Also if you initialize on every click then it causes errors because it's already initialized.
Let me know if you have any questions!
Using your sandbox, I found a couple of things missing:
You needed to memoize your audoFile (since you create a new audio and it never changes)
Your functions need to be stable, and therefore need to be react hook functions, specifically useCallback functions.
In your onClick function in the demo (the start button) you called to initalizeAudioAnalyzer but that was already intialized with your useEffect on functionCont.jsx and doesn't need to be initialized again. Once I removed this, it all worked.
Here is the fixed up code that is now playing the audio:
functionCont.jsx:
import React, { useCallback, useEffect, useMemo, useState } from "react";
import VisualDemo from "./functionViz";
// import VisualDemo from "./VisualDemo";
import soundFile from "../audio/water.mp3";
const FunctionCont = () => {
const audioFile = useMemo(() => {
const audio = new Audio();
audio.crossOrigin = "anonymous";
return audio;
} , []);
const [audioData, setAudioData] = useState();
let frequencyBandArray = [...Array(25).keys()];
const initializeAudioAnalyser = useCallback(() => {
const audioContext = new AudioContext();
const source = audioContext.createMediaElementSource(audioFile);
const analyser = audioContext.createAnalyser();
audioFile.src = soundFile;
analyser.fftSize = 64;
source.connect(audioContext.destination);
source.connect(analyser);
setAudioData(analyser);
audioFile.play();
}, [audioFile]);
useEffect(() => {
initializeAudioAnalyser();
}, [initializeAudioAnalyser]);
const getFrequencyData = useCallback((styleAdjuster) => {
const bufferLength = audioData.frequencyBinCount;
const amplitudeArray = new Uint8Array(bufferLength);
audioData.getByteFrequencyData(amplitudeArray);
styleAdjuster(amplitudeArray);
}, [audioData]);
return (
<div>
<VisualDemo
frequencyBandArray={frequencyBandArray}
getFrequencyData={getFrequencyData}
// audioData={audioData}
audioFile={audioFile}
/>
</div>
);
};
export default FunctionCont;
functionViz.jsx
import React, { useRef, useEffect, useState } from "react";
import Paper from "#material-ui/core/Paper";
import IconButton from "#material-ui/core/IconButton";
import Tooltip from "#material-ui/core/Tooltip";
import EqualizerIcon from "#material-ui/icons/Equalizer";
import { makeStyles } from "#material-ui/core/styles";
import "../stylesheets/App.scss";
const useStyles = makeStyles((theme) => ({
flexContainer: {
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
paddingTop: "25%"
}
}));
const VisualDemo = (props) => {
const classes = useStyles();
const amplitudeValues = useRef(null);
function adjustFreqBandStyle(newAmplitudeData) {
amplitudeValues.current = newAmplitudeData;
let domElements = props.frequencyBandArray.map((num) =>
document.getElementById(num)
);
for (let i = 0; i < props.frequencyBandArray.length; i++) {
let num = props.frequencyBandArray[i];
domElements[
num
].style.backgroundColor = `rgb(0, 255, ${amplitudeValues.current[num]})`;
domElements[num].style.height = `${amplitudeValues.current[num]}px`;
}
}
function runSpectrum() {
props.getFrequencyData(adjustFreqBandStyle);
requestAnimationFrame(runSpectrum);
}
function handleStartButtonClick() {
requestAnimationFrame(runSpectrum);
}
return (
<div>
<div>
<Tooltip title="Start" aria-label="Start" placement="right">
<IconButton
id="startButton"
onClick={handleStartButtonClick}
// disabled={!!props.audioData ? true : false}
>
<EqualizerIcon />
</IconButton>
</Tooltip>
</div>
<div className={classes.flexContainer}>
{props.frequencyBandArray.map((num) => (
<Paper
className={"frequencyBands"}
elevation={4}
id={num}
key={num}
/>
))}
</div>
</div>
);
};
export default VisualDemo;
For some reason, I just cannot get the most basic example of react-use-gesture to work. What I am trying to do is just have a square follow my mouse location when you drag it. I copy-pasted the example from their documentation multiple times (https://github.com/pmndrs/react-use-gesture) and still cannot get it to work. I just don't understand it anymore. I created a stackblitz to show you my code. What am I still doing wrong?
Stackblitz with code: https://stackblitz.com/edit/react-mg2u8p?file=src/Square.js
I will also include the most relevant code here:
import React from "react";
import { useSpring, animated } from "react-spring";
import { useDrag } from "react-use-gesture";
const Square = () => {
const [{ x, y }, set] = useSpring(() => ({ x: 0, y: 0 }));
const bind = useDrag(({ down, movement: [mx, my] }) => {
set({ x: down ? mx : 0, y: down ? my : 0 });
});
return (
<animated.div
{...bind()}
className="Square"
style={{ x, y, touchAction: "none" }}
/>
);
};
export default Square;
It's a version problem.
Your example uses code for react-spring version 9+, but the version of react-spring you're using in the example is 8.0.27.
The example the documentation gives for version 8 is this:
import { useSpring, animated } from 'react-spring'
import { useDrag } from 'react-use-gesture'
function PullRelease() {
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
// Set the drag hook and define component movement based on gesture data
const bind = useDrag(({ down, movement }) => {
set({ xy: down ? movement : [0, 0] })
})
// Bind it to a component
return (
<animated.div
{...bind()}
style={{
transform: xy.interpolate((x, y) => `translate3d(${x}px, ${y}px, 0)`),
}}
/>
)
}
So in your case you would only need to change PullRelease to Square and add className="Square" to the animated.div like you had it in your question.
For both the documentation on the v8 and v9 implementation of this using React UseGesture see this.
If you want to use the v9 version, you currently need to install react-spring#next according to the documentation (see previous link).
I'm trying to use react-native-background-timer to produce an on-screen timer which the user can control. If anyone understands why this error is occurring, I would really appreciate the help!
This is the screen in which I am implementing the timer:
import * as React from 'react';
import {Button, Text, View, TouchableOpacity, StyleSheet } from 'react-native';
import styled from 'styled-components'
import colours from '../components/Colours';
import * as Font from 'expo-font';
import BackgroundTimer from 'react-native-background-timer';
import Sound from 'react-native-sound';
export default class MindfulnessScreen extends React.Component {
state={
fontLoaded:false,
}
async componentDidMount() {
await Font.loadAsync({
'montserrat-regular': require('../assets/fonts/Montserrat/Montserrat-Regular.ttf'),
'montserrat-light': require('../assets/fonts/Montserrat/Montserrat-Light.ttf'),
'montserrat-semibold': require('../assets/fonts/Montserrat/Montserrat-SemiBold.ttf'),
'montserrat-bold': require('../assets/fonts/Montserrat/Montserrat-Bold.ttf'),
'MontserratBold': require('../assets/fonts/Montserrat/Montserrat-Bold.ttf'),
'MontserratSemibold': require('../assets/fonts/Montserrat/Montserrat-SemiBold.ttf'),
'MontserratRegular': require('../assets/fonts/Montserrat/Montserrat-Regular.ttf'),
'MontserratLight': require('../assets/fonts/Montserrat/Montserrat-Light.ttf'),
});
this.setState({ fontLoaded:true});
}
constructor(props) {
super(props)
this.state = {
text:'10:00',
sessionInProgress: false
}
}
resetTimer () {
this.setState({
text: '10:00',
sessionInProgress: false
})
BackgroundTimer.stopBackgroundTimer();
}
startTimer () {
let seconds = 60*10
let seconds = 60*10
BackgroundTimer.runBackgroundTimer(() => {
let secondHand = currSeconds % 60
secondHand = (secondHand === 0) ? '00' : secondHand
secondHand = (secondHand !== '00' && secondHand < 10) ? `0${secondHand}` : secondHand
let displayTimer = `${Math.floor(currSeconds/60)}:${secondHand}`
this.setState({
text: displayTimer
})
if (currSeconds === 0) {
this.stopSession()
}
currSeconds--
}, 1000)
}
beginSession = () => {
this.startTimer()
this.setState({
sessionInProgress: true
})
}
stopSession = () => {
this.resetTimer()
this.setState({
sessionInProgress:false
})
}
render() {
return (
<Container>
<Titlebar>
<Title>Mindfulness</Title>
</Titlebar>
<Scroll>
<HeaderBar>
<Details>Paying more attention to the present moment – to your own thoughts and feelings, and to the world around you – can improve your mental wellbeing.</Details>
<Details>Mindfulness meditation involves sitting silently and paying attention to thoughts, sounds, the sensations of breathing or parts of the body, bringing your attention back whenever the mind starts to wander.</Details>
</HeaderBar>
<HeaderBar>
<Header>this.state.text</Header>
</HeaderBar>
<HeaderBar>
{ !this.state.sessionInProgress &&
<TouchableOpacity onPress={this.beginSession}>
<Header2>Begin Session</Header2>
</TouchableOpacity>
}
{ !this.state.sessionInProgress &&
<TouchableOpacity onPress={this.stopSession}>
<Header3>Stop</Header3>
</TouchableOpacity>
}
</HeaderBar>
</Scroll>
</Container>
);
}
}
The error reads as follows:
Identifier 'seconds' has already been declared (45:8)
Therefore it is pointing to one of the lines reading let seconds = 60*10
When I remove one of these declarations, I get a new error which states:
Invariant Violation: Native module cannot be null.
Thanks for reading, I'm fairly new to react native so apologies for silly mistakes!
Did you eject the project from expo ?
It seems that "react-native-background-timer" cannot be run with the expo managed workflow (native code needs to be compiled).
Anyway, you should take a look to Expo BackgroundFetch : https://docs.expo.io/versions/latest/sdk/background-fetch/
When typing on the keyboard I was seeing some warnings about the input being ahead of the JS code..
Native TextInput(react native is awesome) is 4 events ahead of JS - try to make your JS faster.
So added the debounce and got this to "work":
...
import { debounce } from 'lodash'
...
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data,
indexRef: data.reduce((result, item, index) => {
result[item.title] = index
return result
}, {}),
ready: false,
}
this.updatePitch = this.updatePitch.bind(this)
this.saveLocally = debounce(this.saveLocally, 300).bind(this)
}
...
updatePitch(id, text) {
// Copy the data
let data = [...this.state.data]
const index = data.findIndex(obj => obj.id == id)
data[index].pitch = text
// Update the state
this.setState({ data }, this.saveLocally(data))
}
saveLocally(data) {
try {
AsyncStorage.setItem('data', JSON.stringify(data))
this.forceUpdate()
} catch (error) {
// Well..
}
}
render() {
...
BTW: I'm doing some "prop drilling" for now - but planning to do use the Context API (react 16.3)
The warning seems to have gone by adding debounce (1).. But I'm seeing some strange issues - particularly on the iPhone 8 plus simulator (not seeing the same on iPhone 6 simulator or Android device)
Issues observed:
TextInput don't expand - it just add scolling (expands on iPhone 6 and Android device)
Some layout issues in the FlatList - seems like it has problems finding correct height of list elements..
What is the best practice for fast JS code and saving to both state and AsyncStorage?
(1) One other way than using debounce could be to use getDerivedStateFromProps and add some sort of timer pushing the state to the parent component after some period of time.. But wasn't sure that this would make the JS code faster. So didn't try it.
UPDATE (again)
I open sourced the entire code since it is too hard to give all the needed information in a SO post when the code is so nested.
The entire code is here:
https://github.com/Norfeldt/LionFood_FrontEnd
(I know that my code might seem messy, but I'm still learning..)
I don't expect people to go in and fix my code with PR (even though it would be awesome) but just give me some code guidance on how to proper deal with state and AsyncStorage for TextInput.
I know I have some style issues - would love to fix them, but also comply with SO and keep it on topic.
Update II
I removed forceUpdate and replaced FadeImage with just vanilla react native Image.
but I'm still seeing some weird issues
Here is my code
import React from 'react'
import {
StyleSheet,
SafeAreaView,
FlatList,
StatusBar,
ImageBackground,
AsyncStorage,
Platform,
} from 'react-native'
import SplashScreen from 'react-native-splash-screen'
import LinearGradient from 'react-native-linear-gradient'
import { debounce } from 'lodash'
import Section from './Section'
import ButtonContact from './ButtonContact'
import { data } from '../data.json'
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data,
indexRef: data.reduce((result, item, index) => {
result[item.title] = index
return result
}, {}),
ready: false,
}
}
async componentDidMount() {
SplashScreen.hide()
try {
let BusinessPlan = await AsyncStorage.getItem('BusinessPlan')
if (BusinessPlan !== null) {
// We have data!!
let data = JSON.parse(BusinessPlan)
data = this.state.data.map(item => {
const index = data.findIndex(obj => obj.id == item.id)
const pitch = index >= 0 ? data[index].pitch : ''
return { ...item, pitch }
})
this.setState({ data, ready: true })
} else {
this.setState({ ready: true })
}
} catch (error) {
// Error retrieving data
}
}
updatePitch = (id, text) => {
// Copy the data
let data = [...this.state.data]
const index = data.findIndex(obj => obj.id == id)
data[index].pitch = text
// Update the state
this.setState({ data }, this.saveLocally(data))
}
saveLocally = data => {
try {
AsyncStorage.setItem('BusinessPlan', JSON.stringify(data))
} catch (error) {
// Well..
}
}
render() {
return (
<LinearGradient
style={{ flex: 1 }}
start={{ x: 0.0, y: 0.25 }}
end={{ x: 0.5, y: 1.0 }}
colors={['#000000', '#808080', '#000000']}
>
<StatusBar
barStyle={'light-content'}
backgroundColor={Platform.OS == 'iOS' ? 'transparent' : 'black'}
/>
<SafeAreaView>
<ImageBackground
source={require('../images/BackgroundImage.png')}
style={{ width: '100%', height: '100%' }}
resizeMode={'cover'}
>
<FlatList
data={this.state.data}
initialNumToRender="16"
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Section
id={item.id}
title={item.title}
pitch={item.pitch}
updatePitch={debounce(this.updatePitch, 1000)}
questions={item.questions}
ready={this.state.ready}
/>
)}
ListFooterComponent={<ButtonContact />}
style={{
backgroundColor: 'transparent',
borderColor: '#000',
borderWidth: StyleSheet.hairlineWidth,
}}
/>
</ImageBackground>
</SafeAreaView>
</LinearGradient>
)
}
}
const styles = StyleSheet.create({
sectionHeader: {
fontSize: 24,
marginHorizontal: 5,
},
})
(I also updated my git repo)
Update III
It seems that the setup I have for state and AsyncStorage works fine with a debounce. The issues I was seeing was because I'm draining the CPU (next step to fix).
I tried your code:
"I'm seeing some strange issues - particularly on the iPhone 8 plus
simulator (not seeing the same on iPhone 6 simulator or Android
device)" ==> I confirmed this
The app takes about 100% CPU.
After a while trying I figured out:
"I'm seeing some strange issues - particularly on the iPhone 8 plus
simulator (not seeing the same on iPhone 6 simulator or Android
device)" ==> doesn't right, just wait a little TextInput will expand.
There are nothing wrong with state and AsyncStorage. I didn't get any warning.
The root issue is the animation in FadeImage :
The app render many Carousel, and each Carousel has AngleInvestor, and FadeImage. The problem is FadeImage
FadeImage run Animated with duration 1000 => CPU is overloaded
==> That why TextInput add scroll then take a long time to expand, and FlatList look like has problem, but not. They are just slowly updated.
Solution:
Try to comment FadeImage, you will see the problem gone away.
Don't start so many animations as the same time. Just start if it appears (Ex: the first card in Carousel )
UPDATE
I got your problem: typing fastly cause setState call so many times.
You use can debounce for this situation:
In App.js
render() {
console.log('render app.js')
...
<Section
id={item.id}
title={item.title}
pitch={item.pitch}
updatePitch={debounce(this.updatePitch, 1000)} // the key is here
questions={item.questions}
ready={this.state.ready}
/>
You can change the delay and watch the console log to see more. As I tried, delay about 500 can stop the warning.
P/s: You should try to remove forceUpdate