react-pixi is giving a error with useTick - reactjs

Im having some trouble with the use of useTick in react with react-pixi
getting a error:
No Context found with PIXI.Application. Make sure to wrap component
with AppProvider
So i tried to wrap the AppProvider, but that did´nt do it.
So here is my code for the specific component that´s import react-pixi:
import React, {useState,useEffect} from 'react';
import { render } from 'react-dom'
import { ParticleContainer, useApp, useTick, Stage, Sprite, Container } from '#inlet/react-pixi'
import { Application } from 'pixi.js'
let scale = { x: 0.5, y: 0.5 };
const TryPixi = () => {
const app = useApp();
const [itemRotation, setItemRotation] = useState(0);
useTick(delta => {
setItemRotation(itemRotation + 1)
})
return (
<Stage width={139} height={140} options={{ transparent: true }}>
<Container
x={70}
y={70}
>
<Sprite
image="../../assets/ninja.png"
anchor={[0.5, 0.5]}
scale={scale}
rotation={0.5}
/>
</Container>
</Stage>
);
};
export default TryPixi;
And call the component like this from another component:
<AppProvider><TryPixi /></AppProvider>
I have tried with and without the AppProvider, and yes the AppProvideris imported :-) Hope anyone can help.

You can only use useTick on component that returns only the animated object.
const Ninja = () => {
const [itemRotation, setItemRotation] = useState(0);
useTick(delta => {
setItemRotation(itemRotation + 1)
})
return (
<Sprite
image="../../assets/ninja.png"
anchor={[0.5, 0.5]}
scale={scale}
rotation={0.5}
/>
);
};
const TryPixi = () => {
return (
<Stage width={139} height={140} options={{ transparent: true }}>
<Container
x={70}
y={70}
>
<Ninja />
</Container>
</Stage>
);
};

As far as I could understand from Stage component source code, it creates its own application in componentDidMount here [lines 115 - 121]:
this.app = new Application({
width,
height,
view: this._canvas,
...options,
autoDensity: options?.autoDensity !== false,
})
It then provides this app to its children via wrapping the children in AppProvider component [lines 216 - 217]:
const { children } = this.props
return <AppProvider value={this.app}>{children}</AppProvider>
This means you can use the hooks that depend on this context only inside the children components of Stage, not in the Stage component itself; because useApp hook uses this context, and useTick hook uses the useApp hook.
So, your wrapper component that renders TryPixi should be like this:
<Stage><TryPixi /></Stage>
And then you call the hooks from inside TryPixi component.

Related

React, render dinamic child passing prop

I'm slowly getting introduced into advanced React and best practices for production apps. I want to know what is considered the "best practice" way of rendering a dinamic child passing props regarding maintainability, readability etc:
Here is the codeSandBox for more details
First method, render children as function passing the prop:
import { useState } from "react"
import "./Mouses.css"
export default function MouseChildren ({children}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
/* this is bad because if more than one child is passed it will break */
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{children(mousePos)}
</div>
)
}
and then you call it this way:
<MouseChildren>
{position => <RandomDiv position={position} method="children as function method" />}
</MouseChildren>
Second method, render function as prop:
import { useState } from "react"
import "./Mouses.css"
export default function MouseRenderMethod ({render}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{render(mousePos)}
</div>
)
}
and you call it like this:
<MouseRenderMethod render={position => <RandomDiv position={position} method="render as prop method:" />} />
and the third method is using React.Children:
import React, { useState } from "react"
import "./Mouses.css"
export default function MouseCreateComponent({children}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{React.Children.map(React.Children.toArray(children), child => {
if (React.isValidElement(child)) return React.cloneElement(child, {position: mousePos}, null)
})}
</div>
)
}
and you call it like this:
<MouseCreateComponent>
<RandomDiv method="React.CloneElement method" />
</MouseCreateComponent>
I'm not sure which way is considered to be best over the others. If you from your experience can explain a bit
If you want to pass anything in your code without retyping it, you need to use useContext and the structure is:
One example with full-functionality which it may help you is:
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [userData, setUserData]=useState('nothing here')
// any code you want to pass in the code
//e.g. a function
const randomFunction = ()=>{
//do something here
}
return (
<AppContext.Provider
value={{
userData,
setUserData,
randomFunction
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
then all you have to do is to wrap all the components (children) you want, e.g. wrap <App /> so, more or less everything:
<AppProvider>
<App />
</AppProvider>
So now in this case you can use everything from your AppContext in all your code, you can pass more variables and functions if you want, and you import that by using:
import { useGlobalContext } from '/pathYouHaveIt';
function App() {
const {
userData,
setUserData,
randomFunction,
} = useGlobalContext();
// now you can use those like you have them set-up in the App()

How to pass values to components using dynamic import of NextJS

I have a problem with dynamic import in Next.js. It would be great if someone could give me an answer or some advice to do this in a different way.
The thing is that I have a component that renders a leaflet-map, this map have a pointer so I could click the map and have longitude and latitude, this is the code:
import React from 'react'
import {MapContainer, Marker,TileLayer, useMapEvents } from 'react-leaflet'
import { iconMap } from '../../assets/customeIcon/iconMap';
import 'leaflet/dist/leaflet.css'
const MapView =({selectedPosition,setSelectedPosition}) =>{
const [initialPosition, setInitialPosition] = React.useState([38,-101]);
const Markers = () => {
const map = useMapEvents({
click(e) {
setSelectedPosition([
e.latlng.lat,
e.latlng.lng
]);
},
})
return (
selectedPosition ?
<Marker
key={selectedPosition[0]}
position={selectedPosition}
interactive={false}
icon={iconMap}
/>
: null
)
}
return <MapContainer center={selectedPosition || initialPosition} zoom={5} style={{height:"300px",width:"540px"}}>
<TileLayer url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
></TileLayer>
<Markers />
</MapContainer>
}
export default MapView
As you can see this component has the parameters selectedPosition and setSelectedPosition. This is where I save the clicked position and return it to the parent component.
For example, the parent component used to call the map component this way:
const Parent = () => {
const [selectedPosition, setSelectedPosition] = React.useState(null);
...
<MapView selectedPosition={selectedPosition} setSelectedPosition={setSelectedPosition} > </MapView>
}
This used to work great, but now because of a problem with react-leaflet I have to call the map in a different way, using Next.js dynamic import, I had to create a new component that is like this:
import dynamic from 'next/dynamic';
function MapCaller() {
const Map = React.useMemo(() => dynamic(
() => import('./MapView'),
{ ssr: false, }
), [])
return <Map />
}
export default MapCaller
So now the parent component has to call the MapCaller instead of directly calling the MapView:
const Parent = () => {
const [selectedPosition, setSelectedPosition] = React.useState(null);
...
<MapCaller > </MapCaller>
}
With this I resolved the problem of react-leaflet, but I have other problem now, remember that I used to pass the position values to the map component, how could I do to pass that values with this new approach? How the parent component could communicate with the map to get the selected position? Is there another approach to do this?
Thanks!
Your <MapCaller> component is simply wrapping the existing <MapView>, so you could simply pass the props down to it.
const Map = dynamic(() => import('./MapView'), { ssr: false })
function MapCaller({ selectedPosition, setSelectedPosition }) {
return <Map selectedPosition={selectedPosition} setSelectedPosition={setSelectedPosition} />
}
Then use it in the parent component:
const Parent = () => {
const [selectedPosition, setSelectedPosition] = React.useState(null);
//...
<MapCaller selectedPosition={selectedPosition} setSelectedPosition={setSelectedPosition} />
}

Testing mobx react observer with react usecontext - React, Mobx

New to testing mobx with React. I'm testing a a simple side navigation bar, open/closes on hamburger menu. I have set up a store for the Navbar:
export class Store {
toggle = false;
setToggle(bool){
this.toggle = bool;
};
};
decorate(Store, {
toggle: observable,
setToggle: action
});
export default createContext(new Store());
And here is the Navbar component:
export default observer(() => {
const store = useContext(Store); //SideNavStore is imported from mobx store folder
const handleOnClick = () => {
store.setToggle(false);
}
return(
<div data-testid="test">
<Drawer open={store.toggle} onClose={handleOnClick}>
<div role="presentation">
<List>
{routes.map(route => {
return <>
<ListItem button onClick={handleOnClick}>
<ListItemText primary={route.name}/>
</ListItem>
</>
})}
</List>
</div>
</Drawer>
</div>
);
});
This is App.js
export default () => {
return (
<div className="App">
<Navbar ></Navbar>
</div >
)
}
Test.js
describe('Navbar Interaction', () => {
describe('Inpsecting Navbar Contents', () => {
beforeEach(cleanup);
class Store {
sideNavToggle = true;
}
const DecoratedStore = decorate(Store,{
sideNavToggle: observable
});
const renderWithStore = (store) => {
render(
<Navbar />
);
}
it('Expect links are present', () => {
const store = new DecoratedStore();
const { getByText } = renderWithStore(store);
expect(getByText("My Dashboard")).toBeTruthy();
});
});
})
My test here fails because it can't find the text in the document, Drawer Open is set to false configured by the store.toggle. Trying to figure out how to inject the store or dummy store in the test, there are some tutorials about using Provider/Inject but that requires mobx-react and I believe they are deprecated; I would like to stick with mobx-react-lite. In renderWithStore, I'm stuck on how to pass the dummy store in the test. I could pass the store as a props but I believe that requires provider/inject which I don't want to do that if necessary. I rather import the store directly in the Navbar component using React.useContext. I don't see tests using React.useContext and Mobx Observer in the web. Has anyone encountered this type of scenario or can you provide a better approach? Also what is the best practice with using React and Mobx stores? Using React-testing-library for my tests. Your help is appreciated!
mobx-react-lite does advice users to prefer React.useContext over provider/inject.
Take a look at https://mobx-react.js.org/recipes-context
const storeContext = React.createContext<TStore | null>(null)
export const StoreProvider = ({ children }) => {
const store = useLocalStore(createStore)
return <storeContext.Provider value={store}>{children}</storeContext.Provider>
}
As for testing, if you want the component to use custom store or a mock store, you could do it by passing-in the store to StoreProvider. This how Redux users test their components.
export const StoreProvider = ({ store, children }) => {
return <storeContext.Provider value={store}>{children}</storeContext.Provider>
}

Define a functional component inside storybook preview

I have a custom modal component as functional component and in typescript. This modal component exposes api's through context providers and to access them, I'm using useContext hook.
const { openModal, closeModal } = useContext(ModalContext);
Example code on how I use this api's:
const TestComponent = () => {
const { openModal, closeModal } = useContext(ModalContext);
const modalProps = {}; //define some props
const open = () => {
openModal({...modalProps});
}
return (
<div>
<Button onClick={open}>Open Modal</Button>
</div>
)
}
And I wrap the component inside my ModalManager
<ModalManager>
<TestComponent />
</ModalManager>
This example works absolutely fine in my Modal.stories.tsx
Problem:
But this doesn't work inside my Modal.mdx. It says I cannot access react hooks outside functional component. So, I need to define a TestComponent like component to access my modal api's from context. How to define it and where to define it so that below code for preview works?
import {
Props, Preview, Meta
} from '#storybook/addon-docs/blocks';
<Meta title='Modal' />
<Preview
isExpanded
mdxSource={`
/* source of the component like in stories.tsx */
`}
>
<ModalManager><TestComponent /></ModalManager>
</Preview>
I'm not sure if this is a hack or the only way. I created the TestComponent in different tsx file and then imported it in mdx. It worked.
You may have a utility HOC to render it inside a MDX file as below
HOCComp.tsx in some Utils folder
import React, { FunctionComponent, PropsWithChildren } from 'react';
export interface HOCCompProps {
render(): React.ReactElement;
}
const HOCComp: FunctionComponent<HOCCompProps> = (props: PropsWithChildren<HOCCompProps>) => {
const { render } = props;
return render();
};
export default HOCComp;
Inside MDX File
import HOCComp from './HOC';
<HOCComp render={()=> {
function HOCImpl(){
const [count,setCount] = React.useState(180);
React.useEffect(() => {
const intId = setInterval(() => {
const newCount = count+1;
setCount(newCount);
},1000)
return () => {
clearInterval(intId);
}
})
return <Text>{count}</Text>
}
return <HOCImpl />
}}
/>

How to use recompose's toClass HOC to add a ref to a functional component?

I'm trying to add a ref to a functional component in React Native as to use scrollToEnd on the FlatList component.
I would like to use recompose for this and as their docs state, toClass() should be able to handle this. However, no examples are given.
Currently, this is my failing implementation. Could someone tell me what I'm doing wrong?
I'd be much obliged!
import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native';
import { graphql } from 'react-apollo';
import { compose, toClass, lifecycle } from 'recompose';
import CommentItem from './CommentItem';
import { commentsQuery } from '../../queries/comments';
const CommentScreen = ({ onRef, currentUser, data: { comments, loading } }) => {
if (loading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
return (
<FlatList
ref={ref => {
this.refs.commentList = ref;
}}
data={comments}
keyExtractor={item => item.id}
renderItem={({ item }) => <CommentItem {...item} currentUser={currentUser} />}
/>
);
};
export default compose(
toClass,
graphql(commentsQuery),
lifecycle({
componentDidMount() {
console.log('PROPZZZ', this.commentList);
},
}),
)(CommentScreen);
CommentScreen.propTypes = {
fetchComments: PropTypes.func.isRequired,
updateId: PropTypes.number.isRequired,
comments: PropTypes.arrayOf(Object),
text: PropTypes.string.isRequired,
};
Just use a class components instead of a functional component if you need to use ref. However, if for some reasons you need to use a functional component, you can use withHandlers. Please see the answer of this SO question for how to use it.
I'm going to paste some code I managed to make work with a similar problem, I know it is not exactly your code but it shows the solution:
import React from 'react'
import { compose, withState, withHandlers, lifecycle } from 'recompose'
import Left from './Left'
import Right from './Right'
import Modals from './Modals'
import { ActionBar, Container } from './styles'
let ref = null
const enhance = compose(
withState('position', 'setPos', 'relative'),
withHandlers({
isTop: ({ setPos }) => () => {
const { top } = ref && ref.getBoundingClientRect()
top && setPos(top <= 0 ? 'fixed' : 'relative')
},
}),
lifecycle({
componentDidMount() {
window.addEventListener('scroll', this.props.isTop)
},
componentWillUnmount() {
window.removeEventListener('scroll', this.props.isTop)
},
})
)
export default enhance(({ position, ...props }) => (
<Container innerRef={el => (ref = el)}>
<ActionBar position={position}>
<Left {...props} />
<Right {...props} />
<Modals {...props} />
</ActionBar>
</Container>
))
As you can see the key here was storing the ref in a variable and use a mix of withState + withHandlers + lifecycle to make it work, I didn't need to use the toClass HoC in the end because as long as you use any other HoC from recompose you are already using React component classes underneath :)
Hope it helps!

Resources