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}`
);
}
Related
I am new to react and using "inspirational-quotes" from https://www.npmjs.com/package/inspirational-quotes trying to load a new quote on button click using bind().
I do not know what i do wrong.
This is the code if have right now (App.js):
enter code here
import React, { useState } from "react";
import './App.css';
const a = 'New Quote';
const Quote = require('inspirational-quotes');
const quotes = Quote.getQuote();
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isAppon: true,
quoteText: quotes.text,
quoteAuthor: quotes.author
};
this.newQuote = this.newQuote.bind(this);
}
newQuote() {
// alert('hee')
// this.setState({ quoteText: this.state.quoteText, quoteAuthor: this.state.quoteAuthor });
this.setState(prevState => ({ isAppon: !prevState.isAppon }));
}
render() {
return (<div className="App" >
<header >
{/* <p> A new quote: {this.state.quoteText} from {this.state.quoteAuthor}</p> */}
<button onClick={this.newQuote}> {a} < /button>
<div> A new quote: {this.state.quoteText} from {this.state.quoteAuthor}</div>
< /header> < /div>
);
}
}
export default App;
So you have a couple of things wrong here.
You're trying to use React Hooks (useEffect for example) in a class component, so that wont work. You'd need to use lifecycle events like componentDidMount.
I'm also not sure if you paste your code correctly as it isn't valid when I paste it, there are just a couple things missing but I can see what you wanted to paste so it's okay.
That said, you're also making your life difficult using a class based component when functional components are a thing in combination with hooks :)
(by the way i'm not saying class components don't have their place, they do. But they are not as beginner friendly)
Here's what you need to do:
import { useState, useEffect } from 'react';
import * as Quote from 'inspirational-quotes';
function App() {
const [quote, setQuote] = useState('');
const getNewQuote = () => {
setQuote(Quote.getQuote())
}
// on mount get a new quote
useEffect(() => {
getNewQuote();
}, [ ])
return (
<>
<button onClick={getNewQuote}>new quote</button>
<p>{ quote.text } - { quote.author } </p>
</>
);
}
export default App;
That said, your the library your using kinda sucks as the getQuote uses a "random" index that is calculated outside of the getQuote method. Because of this clicking the button won't create a new quote. If you can find a way to create a new instance of quote then you'll be in business. I tried a couple ways to achieve this but no luck.
I suggest looking into using a random quote API and modifying the getNewQuote method to use an async fetch request to get your next quote. I'll help you with this in the comments or an edit if you need.
Edit: Update as this question is based on a challenge that asks for a page refresh. See below for updated example:
import { useState, useEffect } from 'react';
import Quote from 'inspirational-quotes';
const App = () => {
const [quote, setQuote] = useState(null);
const getNewQuote = () => {
setQuote(Quote.getQuote());
}
// reload the current page
const refreshPage = () => {
window.location.reload();
}
// on mount get a new quote
useEffect(() => {
getNewQuote();
}, [ ])
return (
<>
<button onClick={refreshPage}>new quote</button>
<p>{ quote?.text } - { quote?.author } </p>
</>
);
}
export default App;
In This example we are keeping track of the quote in state using useState and assigning its value to quote.
We then use useEffect with an empty dependency array to tell React we want to do something when the page loads
We create two handle functions:
getNewQuote is for setting a quote to state (as an object).
refreshPage is a function to reload the current page.
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)
I'm using the SpaceX API to build a personal project. I'm using React Router to dynamically load components, rather than refreshing the whole website.
Here is my LaunchDetails component where I'm trying to output some data:
import React, { Component } from 'react'
class LaunchDetail extends Component {
state = {
launch: []
}
async componentDidMount () {
try {
const res = await fetch(`https://api.spacexdata.com/v3/launches/${this.props.match.params.flight_number}`)
const data = await res.json()
this.setState({
launch: data,
rocket: data.rocket
})
} catch (e) {
console.log(e)
}
}
render () {
const { launch, rocket } = this.state
console.log(rocket)
return (
<div>
<div>
<h1>{launch.mission_name}</h1>
<p>SpaceX Flight Number: {launch.flight_number}</p>
<p>Launched: {launch.launch_year}</p>
<p>Rocket: {rocket.rocket_name}, {rocket.rocket_type}</p>
</div>
</div>
)
}
}
export default LaunchDetail
Data one level deep like launch.mission_name is displaying correctly... However, when I try and go down another level, say, rocket.rocket_name (eg: launch.rocket.rocket_name), it throws the above error.
What is strange is that this works in another component, but that is using a different end point (all the data) and I'm mapping through it. Not sure if the way I'm calling the data in this component is to blame or not...
Does anyone have any idea why this could be happening?
EDIT: I've updated the code to be simpler after receiving some comments, error still persists:
import React, { Component } from 'react'
class LaunchDetail extends Component {
state = {
launch: []
}
async componentDidMount () {
try {
const res = await fetch(`https://api.spacexdata.com/v3/launches/${this.props.match.params.flight_number}`)
const data = await res.json()
this.setState({
launch: data
})
} catch (e) {
console.log(e)
}
}
render () {
const { launch } = this.state
return (
<div>
<div>
<h1>{launch.mission_name}</h1>
<p>SpaceX Flight Number: {launch.flight_number}</p>
<p>Launched: {launch.launch_year}</p>
<p>Rocket: {launch.rocket.rocket_name}, {launch.rocket_type}</p>
</div>
</div>
)
}
}
export default LaunchDetail
From here you can find that the react lifecycle methods go in the following order.
componentWillMount --> render --> componentDidMount
You have initialized state with
state = {
launch: []
}
So on the first render, state.rocket will be undefined.
To fix this, either initialise state.rocket or change componentDidMount to componentWillMount
With the former being prefered.
Note componentWillMount is deprecated in version 17.
After OP's edit. You are still initializing launch to []
On the first render launch.rocket will be undefined. Therefore launch.rocket.rocket_name will throw an error.
Either initialise launch to have a rocket field. Or do something like
(launch.rocket || {}).rocket_name, or something else to check that rocket is defined before accessing rocket_name.
I'm trying to get MobX to work with functional components in react. I want to do this without having to use decorators. I have set up an app with create-react-app, added MobX and MobX-react as dependencies.
However, I can't seem to get observables working within functional components.
import React from 'react';
import { extendObservable } from 'mobx';
import { observer } from 'mobx-react';
const Test = () => {
extendObservable(this, {
button: false
});
const handleB1 = () => {
this.button = false;
}
const handleB2 = () => {
this.button = true;
}
const getButton2 = () => {
console.log('button2');
return (
<button type="button" onClick={handleB2}>Button 2</button>
);
};
const getButton1 = () => {
console.log('button1');
return (
<button type="button" onClick={handleB1}>Button 1</button>
);
};
return (
<div>
{this.button ? getButton1() : getButton2()}
</div>
)
};
export default observer(Test);
Clicking the button I would expect the component to get rerendered due to the observable being changed, but I get an error:
×
Error: [mobx] Invariant failed: Side effects like changing state are not
allowed at this point. Are you trying to modify state from, for example, the render
function of a React component? Tried to modify: ObservableObject#2.button
I have tried declaring the observable as part of a functional component or before like this:
const buttonState = () => {
extendObservable(this, {
button: false
});
}
but in both cases I could not get the component to rerender or i was not sure if the observable was actually correctly set.
If i write the whole thing as a class like this it works perfectly
import React from 'react';
import { extendObservable } from 'mobx';
import { observer } from 'mobx-react';
class Test extends React.Component {
constructor(props) {
super();
extendObservable(this, {
button: false
});
}
handleB1 = () => {
this.button = false;
}
handleB2 = () => {
this.button = true;
}
getButton2 = () => {
console.log('button2');
return (
<button type="button" onClick={this.handleB2}>Button 2</button>
);
};
getButton1 = () => {
console.log('button1');
return (
<button type="button" onClick={this.handleB1}>Button 1</button>
);
};
render = () => {
return (
<div>
{this.button ? this.getButton1() : this.getButton2()}
</div>
)
}
};
export default observer(Test);
In React, functional components are not persistent. They run from top to bottom, return some JSX, rinse and repeat.
let i = 0;
const FunctionalComp = (props) => {
const foo = props.foo.toUpperCase();
return <span>Rendered {i++} times. {foo}</span>;
}
All this functional component will ever do is synchronously create the value foo and then return the span. When this component's parent re-renders, this component will do the same exact same, but with potentially new values.
It can never do anything else, and that is why it is powerful. That is why we can call it a functional component: Because it only depends on the values provided to it, because it does not cause side effects that would alter the direction of the rest of the application, and because given the same arguments, this function will produce the same result for the rest of eternity.
Predictable = powerful.
Now, a class component holds persistent state. It constructs, initializes its state, methods, and properties, and then renders and returns JSX. The class (object) still exists in memory, so all of the values and methods on it exist too.
The methods of class component are not so predictable.
class Foo {
name = 'tommy';
getUpperName() {
return this.name.toUpperCase();
}
setName(name) {
this.name = name;
}
}
Foo.getUpperName will not produce the same result every time it is ever used with the same arguments (hint: it doesn't accept any arguments and depends on the context around it to determine its result), so other pieces of the application may change Foo.name and, essentially, control Foo.getUpperName's outcome, potentially by accident.
The class can update its own state, causing itself and all children components to re-compute their JSX returns.
In a plain arrow function, after it returns, all that exists is the return value that it produces and the function statement (declaration). Nothing in between.
All this said, the functional component has no this bound to it. (That is a no-no in functional programming.) It will never have state.
So you can not do anything with this inside of a functional component and it can not hold observable values because every time it re-renders it would re-instantiate each of those values.
In your example above, even if this did refer to Test, this is what would happen:
Test would create the observable value button as false.
Test would render the button, which you would then click.
Test would create the observable value button as false.
Test would render the button, which you would then click.
Test would create the observable value button as false.
Test would render the button, which you would then click.
So on and so forth.
In MobX, your observables need to live on a persistent data structure and be passed into your render functions that return UI markup.
const store = observable({
name: 'tommy'
});
const changeName = () => store.name = store.name.split('').reverse().join('');
const Foo = observer((props) => {
return (
<button onClick={changeName}>{store.name}'s button</button>
)
});
This is not a great pattern, as neither Foo nor changeName are pure, this code would work, at least.
You need to do something like so:
const store = () => {
const self = {};
self.actions = {
setName: action((name) => self.name = name);
}
return extendObservable(self, { name: 'tommy' });
}
const App = (props) => {
return <span><Foo store={store} /></span>
}
const Foo = observer((props) => {
return (
<button onClick={props.store.actions.setName}>
{store.name}'s button
</button>
)
})
Again, this is not an ideal implementation, but it would work, and I am at work and have to get back to what they pay me to do. ;)
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