I have a component that checks for unique codes in my SQL backend. It works great.
What is the correct react syntax to call the code when I need it, right after I press the Save button and before I update the SQL database (providing the returned code is unique).
Currently the VerifySiteCodeComponent only gets called on the initial render. How do I move the call to inside the saveCpyMaster routine or force it to run when I press the Save button?
I have included the code for the VerifySiteCodeComponent for added clarity and possible aid in a solution.
Probably a simple solution for someone, just not me. Still new to the React world. #davidsz - any ideas
...
import React, { Component } from 'react'
import VerifySiteCodeComponent from './VerifySiteCodeComponent';
class ParentComponent extends Component {
constructor(props) {
super(props)
this.state = {
cpymasterid:13,
sitemasterCode:'AZ302',
uniquehere:true,
answer:'not yet'
}
this.changeUnique = this.changeUnique.bind(this);
}
componentDidMount(){
}
changeUnique = (unique) =>{
this.setState({uniquehere: unique})
if (this.state.uniquehere)
{
this.setState({answer:"True"})
} else {
this.setState({answer:"False"})
}
}
saveCpyMaster(){
//Need result from <VerifySiteCodeComponent> here
//after data entry and before I update the SQL file.
//How do I do that?
}
render() {
return (
<div>
<button className="btn btn-success" onClick={this.saveCpyMaster}>Save</button>
<VerifySiteCodeComponent onUpdate={this.changeUnique} cpymasterid={this.state.cpymasterid} sitemasterCode={this.state.sitemasterCode}/>
{this.state.answer}
{this.state.cpymasterid}
</div>
)
}
}
export default ParentComponent
import { Component } from 'react';
import SiteMasterService from '../Services/SiteMasterService'
//-------------
class VerifySiteCodeComponent extends Component {
constructor (props){
super(props)
this.state = {
unique:true,
check_cpymasterid: this.props.cpymasterid,
check_sitemasterCode: this.props.sitemasterCode
}
}
componentDidMount()
{
SiteMasterService.getSiteMastersByCode(this.state.check_sitemasterCode).then((res) => {
for (let i = 0; i < res.data.length; i++)
{
if (parseInt(res.data[i].cpymaster_cpymasterId,10) === this.state.check_cpymasterid )
{
this.setState({unique:false})
}
}
this.props.onUpdate(this.state.unique)
});
}
render() {
return this.state.unique;
}
}
export default VerifySiteCodeComponent;
...
Based on the logic in your VerifySiteCodeComponent it probably should not be it's own component. I would move the logic into a utility function and pass in the variables you are holding in it's state as function arguments. So something like
export function verify_code(check_code, check_id, change_unique){
let unique = true;
SiteMasterService.getSiteMastersByCode(check_code).then((res) => {
for (let i=0; i<res.data.length; i++) {
if (parseInt(res.data[i].cpymaster_cpymasterId,10) === check_id){
unique = false;
}
}
change_unique(this.state.unique)
return unique
}
This would then allow you to import and call the function on componentDidMount if need be, as well as in the onclick handler saveCPYMaster.
Take a look at this resource for building external utility files. It may help you with the errors you're running into with functions. https://www.pluralsight.com/guides/importing-utility-functions-in-reactjs
I can't explain it perfect(because i don't work with class components a lot) but i think that you need to use arrow functions or "bind" for that onClick.
try to make this:
saveCpyMaster = () => {
your code here...
}
and make that onClick like this onClick = {()=>this.saveCpyMaster()}
i think it will help and solve your problem but you should read about arrow functions and bind(i realize that i need to read mroe too XD)
Related
I have a component I call that is a passed a recordID and returns the text associated to the Id. 33 should = Tower
will render "Tower" on the screen. All good, but...
When I try to use the component in the following IF statement it does not work.
...
if (<GetAssetTypeNameComponent datafromparent = {assettype_assettypeId}/> === "Tower")
{
this.props.history.push(`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`);
}
Using the passed parameter does work if I change the code to:
...
if (assettype_assettypeId === "33")
{
this.props.history.push(`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`);
}
...
What am I doing wrong?
Rob
Component Code that needs to be a Function....
...
class GetAssetTypeNameComponent extends Component {
constructor (props){
super(props)
this.state = {
assettype:[]
}
}
componentDidMount()
{
AssetTypeService.getAssetTypeById(this.props.datafromparent).then( (res) =>{
let assettype = res.data;
this.setState({isLoading:false});
this.setState({
assettypeName: assettype.assettypeName,
assettypeType: assettype.assettypeType
});
});
}
render() {
return (
<div>
{this.state.assettypeName}
</div>
);
}
}
export default GetAssetTypeNameComponent;
...
Following Function code compiles:
...
import React, { useState} from 'react';
import AssetTypeService from './AssetTypeService'
const GetAssetTypeNameFunction = (props) =>{
// destructuring
const { assettype_assettypeId } = props;
const [assetType,setAssetType] = useState()
AssetTypeService.getAssetTypeById(assettype_assettypeId).then( (res) =>
setAssetType(res.data));
const arrayMap = assetType.map((post)=>{
return(
<ul>
{post.assettypeName}
</ul>
);})
return (
{arrayMap}
);
}
export default GetAssetTypeNameFunction;
...
Get execution error:
I think because I calling the function from within an eventHandler:
...
editAssets(assetsid,assettype_assettypeId){ if (GetAssetTypeNameFunction(assettype_assettypeId) === "Tower") { this.props.history.push(/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}); }]
...
----- Error: Invalid hook call. Hooks can only be called inside of the body of a function component. I am responding to a onClick in a list to route to a specific component based on the function $
How do I get around this?
A component renders content to be displayed in the page. The retuned value of rendering a component is a tree of nodes that contain your content. All this means that <GetAssetTypeNameComponent> may contain the text content Tower, but it is not equal to the string "Tower". It just doesn't make any sense to render a component as the test for a conditional like this.
In React you want to use logic to tell react how to render. You do not want to render and then use the result in your logic.
It's hard to give advice on the best way to fix that with so little code, but maybe you want a a simple function to coverts the id into some text for you.
function getAssetName(id) {
return someLogicSomewhere(id).result.orWhatever
}
And now you can do something like:
if (getAssetName(assettype_assettypeId) === 'Tower')
{
this.props.history.push(
`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`
);
}
I'm trying to make a web game using react to work as the UI part of the game, and use phaser 3 as the engine. I've integrated phaser and react with ion-phaser/react, and it works well if all the data is there when the phaser app is ran, however I can't figure out if I can feed data to the phaser app while it's running.
For example:
const GameEngine = ({width, backgroundColor}) => {
const state = { /* ... game config */ };
let {initialize, game} = state;
return <IonPhaser game={game} initialize={initialize} />;
};
this works well if the props width and background are provided, but when they change, phaser doesn't update. Is there a way to continually feed data in the game from outside?
One option would be to publish events to Phaser's event system from React. This is pretty straightforward if you already have a reference to the Phaser game object in your React component.
In your React component, you could do something like:
game.events.emit('MY_EVENT', 'Hello world!')
Then, in any Phaser scene, you could do something like:
this.game.events.on('MY_EVENT', (event) => {
console.log(event)
})
I was also struggling with this issue.
Ended up settling for a hacky workaround.
Defined the game config object outside of the React state and keep checking for the instance keys existence on the game config object. See the getInstance function below:
import React from "react";
import Phaser from "phaser";
import { IonPhaser } from "#ion-phaser/react";
const game = {
/* ... game config */
};
function getInstance() {
if (game.instance) {
console.log("ready")
return Promise.resolve(game.instance);
} else {
console.log("waiting")
return new Promise((resolve) => {
const interval = setInterval(() => {
if (game.instance) {
clearInterval(interval);
resolve(game.instance);
}
});
}, 30);
}
}
export default class GameEngine extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: false,
};
}
componentDidMount() {
getInstance().then((instance) => {
console.log(instance)
this.setState({ isReady: true });
});
}
render() {
const { initialize } = this.props;
return (
<IonPhaser
game={game}
initialize={initialize}
/>
);
}
}
I opened an issue here on the GitHub ion-phaser project
Following up to Ben's answer... For anybody coming here wondering how to get the game object... I added this to my code to get it working as an example. It's specific to my setup, but should be helpful to others:
in App.js:
import { useRef } from 'react';
in function App() you'll want: const gameRef = useRef(null)
pass it to your IonPhaser component: <MyIonPhaserComponent gameRef={gameRef} ...
in your IonPhaser component:
...
return (
<>
<IonPhaser ref={gameRef} ...
</>
)
...
in whatever other react component you want to emit events from, pass it gameRef={gameRef} as well, then use it with:
var PhaserGameInstance = gameRef.current.getInstance().then( game => phaserGameObject = game);
someFunctionToCallOnClick(){
phaserGameObject.events.emit("MY_CUSTOM_EVENT", "hello");
}
If not in a function, the phaserGameObject will probably be undefined until the getInstance() resolves
Then back in the game setup in MyIonPhaserComponent, within the create function:
this.game.events.on('MY_CUSTOM_EVENT', (event) => {
console.log(event)
})
At this point, your other react component should emit events that are immediately picked up within Phaser
I'm working through a react tutorial and the instructor is showing that the event handler in this code won't work, because this() is accessing the outer environment. But I get no error. Can someone explain it to me?
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0,
};
handleIncrement() {
console.log(this.state);
console.log(this.props);
}
render() {
return (
<div>
<button onClick={this.handleIncrement()}>
Increment
</button>
</div>
);
}
}
export default Counter;
The thing is, when your event handler needs to access this from a local scope, and you call the method like this; this.handleClick(), you are telling JavaScript to implement the task of the method IMMEDIATELY it gets there (in your case, immediately it is rendered), which conventionally, doesn't require binding to this.
But when you 'call' (I put it in quotations because the right word should be REFER) a method like this; this.handleClick, you are actually referring to the method (meaning it should be invoked only when the user does something), not invoking it immediately. This either requires binding this.handleClick= this.handleClick.bind(this); or the use of arrow function for your method handleClick = () => {};. It is mostly used for onClick functionalities.
You are not getting that error in your code because you included the parentheses - this.handleIncrement(). If you remove the parentheses and still consoleLog this.state in your handleIncrement, you will definitely get undefined error. But if your handleIncrement is only logging something outside a state, you will not get the error.
If you understand my basic explanation, kindly accept the answer.
Any function called directly from render method will get the container object as this
But when we assign a function to onClick event, we don't want to call that function immediately... so we assign it like this
<button onClick={this.handleIncrement}>
(only the function name without () at the end) ... and this says to call the function when the button is clicked.
But when you click the button the function will not be called from the render method anymore so the this reference will be changed and produce an error.
In your case, you added the () to your this.handleIncrement function invoking it immediately... so it's not causing any problem but it will give you wrong results in almost all cases since it won't get called on click but it will get called with each render.
Since your simple code gets rendered only on button click it's probably correcting the problem. Add a second button and it will give wrong result or the UI will freeze.
The correct way is to remove the () after this.handleIncreament and bind the function inside constructor ... this.handleIncreament = this.handleIncreament.bind(this)
Without bind() method you can use directly arrow function in handleincrement.
check below code
const { Component } = React;
class Counter extends Component {
state = { count: 0 };
handleIncrement=()=> {
const { count } = this.state;
this.setState({ count: count + 1 });
}
render () {
return <label>
<div>Count {this.state.count}</div>
<button onClick={this.handleIncrement}>Increment</button>
</label>;
}
}
ReactDOM.render(<Counter/>, document.querySelector('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main/>
I agree with Afzal Hossain.
<button onClick={this.handleIncrement()}>
This line will call handleIncrement function on render. This is not the correct way to add an event.
<button onClick={this.handleIncrement}>
This will be the correct approach to call the function. But since it's a callback, it will have no knowledge of what this is since it's not in the same context.
React Documentation makes it really clear why we should always bind callback functions with this to have the context available in that particular function.
However, if you don't want to bind your function, there are two workarounds mentioned in react documentation
Public Class Fields syntax
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0,
};
handleIncrement = () => {
console.log(this.state);
console.log(this.props);
}
render() {
return (
<div>
<button onClick={this.handleIncrement}>
Increment
</button>
</div>
);
}
}
export default Counter;
ReactDOM.render(<Counter/>, document.querySelector('main'));
Arrow functions
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0,
};
handleIncrement() {
console.log(this.state);
console.log(this.props);
}
render() {
return (
<div>
<button onClick={() => this.handleIncrement()}>
Increment
</button>
</div>
);
}
}
export default Counter;
For more details, refer to this documentation link.
As Afzal Hossain says, you're invoking this.handleIncrement() when the element renders rather than when the button is clicked.
You need to provide the function handle itself to onClick, and bind() it to the correct context when it is constructed, so that this always accesses the instance of Counter within handleIncrement().
Here is a working implementation of the suggestions made in his answer:
const { Component } = React;
class Counter extends Component {
state = { count: 0 };
handleIncrement = this.handleIncrement.bind(this);
handleIncrement () {
const { count } = this.state;
this.setState({ count: count + 1 });
}
render () {
return <label>
<div>Count {this.state.count}</div>
<button onClick={this.handleIncrement}>Increment</button>
</label>;
}
}
ReactDOM.render(<Counter/>, document.querySelector('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main/>
I was reading the section on Don’t Mutate the Original Component. Use Composition from this link.
https://reactjs.org/docs/higher-order-components.html
I then reviewed a project I'm trying to build. At a high level, this is what my code looks like:
class Wrapper extends Component {
constructor(props) {
this.wrappedComponent = props.wrappedComponent;
}
async componentWillAppear(cb) {
await this.wrappedComponent.prototype.fetchAllData();
/* use Greensock library to do some really fancy animation on the wrapper <Animated.div> */
this.wrappedComponent.prototype.animateContent();
cb();
}
render() {
<Animated.div>
<this.wrappedComponent {...this.props} />
</Animated.div>
}
}
class Home extends Component {
async fetchAllData(){
const [r1,r2] = await Promise.All([
fetch('http://project-api.com/endpoint1'),
fetch('http://project-api.com/endpoint2')
]);
this.setState({r1,r2});
}
animateContent(){
/* Use the GreenSock library to do fancy animation in the contents of <div id="result"> */
}
render() {
if(!this.state)
return <div>Loading...</div>;
return (
<div id="result">
{this.state.r1.contentHTML}
</div>
);
}
}
export default class App extends Component {
render() {
return <Wrapper wrappedComponent={Home} />;
}
}
My questions are:
In my Wrapper.componentWillAppear(), I fire the object methods like this.wrappedComponent.prototype.<methodname>. These object methods can set it's own state or animate the contents of the html in the render function. Is this considered mutating the original component?
If the answer to question 1 is yes, then perhaps I need a better design pattern/approach to do what I'm trying to describe in my code. Which is basically a majority of my components need to fetch their own data (Home.fetchAllData(){then set the state()}), update the view (Home.render()), run some generic animation functions (Wrapper.componentWillAppear(){this.animateFunctionOfSomeKind()}), then run animations specific to itself (Home.animateContent()). So maybe inheritance with abstract methods is better for what I want to do?
I would probably actually write an actual Higher Order Component. Rather than just a component which takes a prop which is a component (which is what you have done in your example). Predominately because I think the way you have implemented it is a bit of a code smell / antipattern.
Something like this, perhaps.
class MyComponent extends React.Component {
constructor() {
super();
this.animateContent = this.animateContent.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.r1 !== nextProps.r1) {
this.animateContent();
}
}
componentDidMount() {
// do your fetching and state setting here
}
animateContent() {
// do something
}
render() {
if(!this.props.r1) {
return <div>Loading...</div>;
}
return (
<div id="result">
{this.props.r1.title}
</div>
);
}
}
const myHOC = asyncFn => WrappedComponent => {
return class EnhancedComponent extends React.Component {
async componentDidMount(){
const [r1, r2] = await asyncFn();
this.setState({ r1, r2 })
this.animateContent();
}
animateContent = () => {
// do some animating for the wrapper.
}
render() {
return (<WrappedComponent {...this.props} {...this.state} />)
}
}
}
const anAsyncExample = async () => {
const result = await fetch("https://jsonplaceholder.typicode.com/posts");
return await result.json();
}
const MyEnhancedComponent = myHOC(anAsyncExample)(MyComponent);
Here's a working JSFiddle so you can see it in use:
https://jsfiddle.net/patrickgordon/69z2wepo/96520/
Essentially what I've done here is created a HOC (just a function) which takes an async function and returns another function which takes and a component to wrap. It will call the function and assign the first and second result to state and then pass that as props to the wrapped component. It follows principles from this article: https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e
I have a little piece of code that renders data from the database according to the path name. My only problem is that when I try to retrieve that data, using this.state.note._id it returns an error that says it cannot find _id of undefined. How would I access my object that is put into a state? It only gives the error when I try to access the items inside the object such as _id
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: [],
document: (<div></div>)
};
}
componentWillMount() {
this.tracker = Tracker.autorun(() => {
Meteor.subscribe('notes');
let note = Notes.find({_id: this.props.match.params.noteId}).fetch()
this.setState({ note: note[0] });
});
}
renderDocument(){
console.log(this.state.note);
return <p>Hi</p>
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}
I know that the reason it is returning undefined is because (correct me if I am wrong) the page is rendering the function before the the tracker could refresh the data. How would I get like some sort of callback when the tracker receives some data it will call the renderDocument function?
You're initializing your note state as an array but then you're setting it to a scalar later. You're also not checking to see if the subscription is ready which means that you end up trying to get the state when it is still empty. The tracker will run anytime a reactive data source inside it changes. This means you don't need a callback, you just add any code you want to run inside the tracker itself.
You also don't need a state variable for the document contents itself, your render function can just return a <div /> until the subscription becomes ready.
Note also that .findOne() is equivalent to .find().fetch()[0] - it returns a single document.
When you're searching on _id you can shorthand your query to .findOne(id) instead of .findOne({_id: id})
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: null
};
}
componentWillMount() {
const sub = Meteor.subscribe('notes');
this.tracker = Tracker.autorun(() => {
if (sub.ready) this.setState({ note: Notes.findOne(this.props.match.params.noteId) });
});
}
renderDocument(){
return this.state.note ? <p>Hi</p> : <div />;
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}