I am making a game using matter.js in reactjs.
I am suffering a problem where I cannot return anything in the render().
Probably this is because matter.js has its own renderer.
So I did not retun anytihng into the render section.
If i don't return anything in the render section componentDidMount do not run.
I am Unable to run componentDidMount() and i cannot call any function even though i bind it.
I am calling a start_game() function to start game but any other function that I call within start_game() an error comes which says that no such function exist even though I have declared it within the class.
import React, { Component } from 'react';
import Matter from 'matter-js';
const World = Matter.World;
const Engine = Matter.Engine;
const Renderer = Matter.Render;
const Bodies = Matter.Bodies;
const Events = Matter.Events;
const Mouse = Matter.Mouse;
const MouseConstraint = Matter.MouseConstraint;
const Body = Matter.Body;
class Walley extends Component
{
constructor(props)
{
super(props);
this.world = World;
this.bodies = Bodies;
this.engine = Engine.create(
{
options:
{
gravity: { x: 0, y: 0,}
}
});
this.renderer = Renderer.create(
{ element: document.getElementById('root'),
engine: this.engine,
options:
{
width: window.innerWidth,
height: window.innerHeight,
background: 'black',
wireframes: false,
}
});
this.state = {
play_game: false,
score:0,
};
this.play_game=this.play_game.bind(this);
}
play_game(){
this.setState
({
score: 50,
});
}
startGame()
{
console.log(this.state.play_game);
//don't know how here the value of play_game is set to true even it was false in componentWillMount()
if(this.state.play_game){
this.play_game();
//unable to access play_game
}
}
componentWillMount()
{
if (confirm("You want to start game?"))
{
this.setState({play_game: true});
}
console.log(this.state.play_game);
//even though play_game is set true above yet output at this console.log remains false
}
render()
{
if(this.state.play_game)
this.startGame();
}
componentDidMount()
{
//unable to access
console.log('Did Mount');
}
}
export default Walley;
render needs to be a pure function, meaning you should not call startGame from inside render. I think you want something closer to this:
// remove this
// componentWillMount() {}
componentDidMount() {
if (confirm("You want to start game?")) {
this.setState({play_game: true}, () => {
console.log(this.state.play_game);
});
startGame();
}
}
render() {
// eventually you will want to return a div where you can put your matter.js output
return null;
}
Related
I am working on a ReactJS 15 project using sagas and reselect as middle ware to fetch data. I can successfully get data in componentWillReceiveProps and set the data in the state, however there still not data in the render function first run when I take the data from the state. Anyone knows what's going on here? BTW, I used json-server as mock data server.Below is part of my code:
Component:
constructor(props) {
super(props);
this.state = {
timelineData: [],
};
}
componentDidMount() {
// use react redux to make the api call here
this.props.fetchTimelineData({
id: parse(this.props.location.search.substr(1)).id,
});
}
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps);
// Successfully take the data from the nextProps (timelineData is not [])
const { timelineData } = nextProps;
this.setState({
timelineData,
});
}
render() {
// the first render called timelineData is an empty array, which will not able to populate the UI
// which RaiseTimeline is empty
const { timelineData } = this.state;
return (
<RaiseTimelineStyled>
<RaiseDetailsGrid>
<Accordion
title={
<RaiseAccordionHeader image={image} title={'Timeline'} />
}>
<Timeline.wrapper>
<RaiseTimelineStyled.divider>
<RaiseTimelineStyled.container>
<RaiseTimeline timelineEvents={timelineData} />
action.js (works fine):
export const setTimelineData = timelineData => console.log('actions.js', timelineData) || ({
type: ACTIONS.SET_TIMELINE_DATA,
timelineData,
});
Api.js (works fine):
class TimelineAPI {
// payload will be used after backend done
static fetchTimelineData(payload) {
return http.get(`${baseURI}/timeline`).then(result => console.log('api', result.data) || result.data);
}
}
Reducers: (works fine)
function TimelineDataReducer(state = initialState, action) {
switch (action.type) {
case ACTIONS.SET_TIMELINE_DATA:
console.log('reducer', action.timelineData);
return state.set('numbers', action.timelineData);
default:
return state;
}
}
Sagas: (works fine)
export function* fetchTimelineData(action) {
yield put(togglePendingScreen(true));
const { result, error } = yield call(TimelineAPI.fetchTimelineData, action.payload);
if (error) {
yield put(
toggleErrorModal({
isOpen: true,
text: error.code,
source: 'Fetch Timeline Data',
}),
);
} else {
console.log('Sagas', result.timeline);
yield put(ACTIONS.setTimelineData(result.timeline));
}
yield put(togglePendingScreen(false));
}
Selectors(works fine):
import { createSelector } from 'reselect';
const selectTimelineData = state => state.get('TimelinePageData').toJS();
const selectTimeline = () =>
createSelector(selectTimelineData, TimelineDataState => TimelineDataState.numbers);
export { selectTimeline };
To me it seems logical that you have no data on the first run.
The reason is there render() function is called once before the componentDidMount() in the react life cycle. (V15)
Look here for the react 15 life cycle : https://gist.github.com/bvaughn/923dffb2cd9504ee440791fade8db5f9
I got the answer, everything is correct, but another person name but another person name same component and set its state in side the constructor, so it's not able to render in the first time
I am currently writing tests for a component I implemented using the react-test-renderer and an issue I've come across is trying to test the callback function that is called when I scroll on my Animated.ScrollView.
Here's a succint version of what I currently have with generic naming:
Component
export class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
public componentWillMount() {
this.index = 0;
this.routeSelectionAnimation = new Animated.Value(0);
}
public componentDidMount() {
this.routeSelectionAnimation.addListener(({ value }) => {
// some work is done before the following line
// the function I want to test is being called
this.props.onIndexUpdate(index);
});
}
public render() {
const scrollAnimation = Animated.event(
[{
nativeEvent: {
contentOffset: {
x: this.routeSelectionAnimation,
},
},
},
],
{ useNativeDriver: true },
);
// * more processing *
return (
<Animated.ScrollView
horizontal={true}
scrollEventThrottle={1}
showsHorizontalScrollIndicator={false}
snapToInterval={...}
pagingEnabled={true}
onScroll={scrollAnimation} // what matters is this
style={styles.scrollView}
>
{other components}
</Animated.ScrollView>
);
}
This works correctly on the device, the function passed by props onIndexUpdate(int) is called when the ScrollView scrolls.
I would like to test this behaviour, however I can't seem to simulate the scroll on the ScrollView. I was thinking of doing something like:
const props = {
onIndexUpdate: jest.fn(),
};
const getTestInstance = () => {
return TestRenderer.create(
<MyComponent onIndexUpdate={props.onIndexUpdate} />
).root;
};
describe("Animated.ScrollView", () => {
it("calls function", () => {
const instance = getTestInstance();
const scrollView = instance.findByType(Animated.ScrollView);
scrollView.instance.scrollToEnd({animated: false});
expect(props.onIndexUpdate).toHaveBeenCalledTimes(3);
});
});
But it does not work. What I get as a result is TypeError: scrollView.instance.scrollToEnd is not a function which clearly shows I'm looking at this wrong...
I'd greatly appreciate input on how to fix this or approach the testing in a more correct way.
You missed out a print statement, thats why it wont work. You should have put:
print scrollView.instance.scrollToEnd({animated: false});
I have a React component and there will be an infinite update on the component whenever I connect it to redux and reference to properties from the store. If I just reference/connect one property from the store, the infinite update will not occur. It only happens when I connect two or more and I really cannot determine why this is happening.
When I add shouldComponentUpdate, it will actually not stop the infinite updating, it will just slow it down a lot. I really have no idea what is going on.
UPDATE:
Interestingly, if I just take out the componentDidUpdate function, it will not do an infinite loop and crash the browser.
import React, { Component } from 'react';
import { connect } from 'react-redux';
class UnreadMessages extends Component {
constructor(props) {
super(props);
this.state = {
messageReceived: false,
unreadMessages: 0
}
this.unreadMessages = 0;
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.lastViewedMessageTime === this.props.lastViewedMessageTime) {
this.setState({ messageReceived: true },
() => {
setTimeout(
() => {
this.setState({ messageReceived: false });
},
300
);
}
);
}
const conOne = prevProps.messages.length !== this.props.messages.length;
const conTwo = this.props.visible === false && window.innerWidth < 768;
if (conTwo) {
let index = this.props.messages.length - 1;
const conOne = this.props.messages[index].type === 'chat.msg';
const conTwo = this.props.messages[index].member_type === 'agent';
if (conOne && conTwo) {
this.setState({
unreadMessages: this.state.unreadMessages + 1
});
}
}
if (this.props.visible === true) {
this.setState({ unreadMessages: 0 });
}
}
render () {
let displayBadge = this.state.unreadMessages > 0 ? true : false;
console.log('DISPLAY BAD', displayBadge)
let pulse = this.state.messageReceived === true ? 'pulse' : '';
console.log('PULSE', pulse)
if (!displayBadge) {
return null;
}
return (
<span className={`msgBadge ${pulse}`}>{this.state.unreadMessages}</span>
)
}
}
function mapStateToProps(state) {
return {
lastViewedMessageTime: state.lastViewedMessageTime,
messages: state.chats.toArray(),
visible: state.visible
};
}
export default connect(mapStateToProps, null)(UnreadMessages);
As #Hamms pointed out in the comments, using this.setState inside componentDidUpdate is most likely going to cause issues.
Take the first few lines of componentDidUpdate.
componentDidUpdate(prevProps, prevState) {
if (prevProps.lastViewedMessageTime === this.props.lastViewedMessageTime) {
this.setState({ messageReceived: true },
...
If the state or any other prop apart from lastViewedMessageTime is changed and causes an update then the lastViewedMessageTime prop will be the same in the current props as in the prevProps.
This will cause a state change - therefore an update - and the same will be true about lastViewedMessageTime.
You are already stuck in an endless update cycle.
You should look into static getDerivedStateFromProps or memoization. These should help avoid these issues.
In my ComponentWillReceiveProps() methods my prop are getting updated which was fetched from API only. Please help how to recover the props.
My ComponentWillReceiveProps():
constructor(props) {
super(props)
this.state = {
filtered_chart_data: {}
}
props.getEngagementInfo()
props.getChartData()
}
componentWillReceiveProps(nextProps) {
const lastSavedCharts = this.state.filtered_chart_data
if (!_.isEmpty(nextProps.diagnose.chart_data)) {
if (_.isEmpty(this.state.filtered_chart_data)) {
return this.setState({
filtered_chart_data: nextProps.diagnose.chart_data
})
}
return this.setState(
{
filtered_chart_data: lastSavedCharts
},
() => this.updateFilters(nextProps.diagnose.chart_data)
)
}
}
updateFilters = chartDataToApplyFilters => {
if (!_.isEmpty(this.state.filtered_chart_data)) {
const { filters } = this.props.diagnose
this.handleFilterPlantRegion(
filters.plant_region,
chartDataToApplyFilters
)
this.handleFilterPlant(filters.plant, chartDataToApplyFilters)
}
}
In my nextProps the variable nextProps.diagnose.chart_data is updating every time, but it is being fetch from API.
Can you help How to not update this props?
you can try shouldComponentUpdate(), from docs
I'm trying to increment the tally property of a given habit, in this context I'm targeting tally: 4. On a related note, I'm open to suggestions for better ways of structuring my data.
class App extends Component {
constructor(props){
super(props)
this.state = {
displayedHabit: "Exercise",
currentUser: "Achilles",
userList: [
{
user: "Achilles",
id: 0,
habits: [
{
habit: "Exercise",
tally: 123
},
{
habit: "Eat Vegetables",
tally: 4
},
]
}
]
}
}
Here is the implementation I've tried after searching for solutions. I don't believe it's working because assignments that use .find() that work after mounting are broken after I call the increment function through an event handler - leading me to believe .find() is no longer being called on an array.
increment = () => {
let newCount = this.state.userList[0].habits[1].tally + 1
this.setState({
userList: {
...this.state.userList[0].habits[1],
tally: newCount
}
})
}
In React, it's very important that you don't mutate your data structures. This essentially means that you have to copy the data structure at each level of your nested data. Since this is too much work, I suggest you use a library made for this purpose. I personally use object-path-immutable.
Example:
import {set} from 'object-path-immutable';
increment = ({userIndex, habitIndex}) => this.setState({
userList: set(this.state.userList, [userIndex, 'habits', habitIndex, 'tally'], this.state.userList[userIndex].habits[habitIndex].tally + 1)
});
I would recommend restructuring your data such that you have objects which actually map the data in accessible ways ie:
this.state = {
displayedHabit: "Exercise",
currentUser: "Achilles",
userList: {
"Achilles": { // Could also be id
id: 0,
habits: {
"Exercise": 123,
"EatVegetables": 4
}
}
}
}
This would allow you to do something like
increment = () => {
const {userList} = this.state;
this.setState({
userList: {
...userList,
Achilles: {
...userList.Achilles
habits: {
...userlist.Achilles.habits
'EatVegetables': userlist.Achilles.habits.EatVegetables + 1
}
}
}
})
}
This would be further simplified by using object-path-immutable which would allow you to do something simple like:
increment = () => {
const {userList} = this.state;
this.setState({
userList: immutable.set(userList, 'Achilles.id.habits.Exercise', userList.Achilles.id.habits.Exercise + 1)
})
}
In order to make this more generic though I would recommend doing what something similar to what earthling suggested:
import {set} from 'object-path-immutable';
incrementUserHabit = ({userIndex, habitIndex}) => this.setState({
userList: set(this.state.userList, [userIndex, 'habits', habitIndex, 'tally'], this.state.userList[userIndex].habits[habitIndex].tally + 1)
});
This way your code is more reusable
Something like this?
class App extends React.Component {
constructor() {
super();
this.state = {
userList: {
'12': {
name: 'Achilles',
habits: {
Exercise: {
tally: 123
},
'Eat Vegetables': {
tally: 231
}
}
}
}
}
}
inc() {
const {userList} = this.state;
userList['12'].habits['Exercise'].tally++;
this.setState({userList});
}
render() {
return (
<div>
<h2>{this.state.userList['12'].habits['Exercise'].tally}</h2>
<button onClick={() => this.inc()}>Increment</button>
</div>
)
}
};
Play with it here:
https://codesandbox.io/s/10w0o6vn0l