clickHandler in stateless component? - reactjs

I am a React noobie and I'm trying to create a simple (reusable) history back button using a stateless component but I'm not sure how to incorporate / where to put a clickHandler. Do I need to use a stateful component? This is my non-working approximation of what I'm trying to do.
import React from 'react';
const BtnBack = () => (
<button className="btn btn-back" onClick={this.handleClick}>BACK</button>
);
handleClick() {
// history back code
};
export default BtnBack;

You're writing object / class like code outside of an object or class. Just think of this code like normal JavaScript:
import React from 'react';
const YourButton = () => (
<button onClick={yourFunction}>BACK</button>
)
function yourFunction(event) {
console.log('hello there')
}
You can also inline this function if you want to pass more arguments along:
const YourButton = () => (
<button onClick={event => yourFunction(event, 'foo', 'bar')}>BACK</button>
)
However, in this situation it's very common to pass functions down from a parent who may be interacting with state for instance.
const YourButton = props => (
<button onClick={props.yourFunction}>BACK</button>
)
Also you're saying "in a const" but you can use let or var if you want, or even export it directly.

Related

Refreshing module data on button click in react

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.

Implementing events in a REACT function component

I am trying to figure out how to implement my own custom events. I asked the question here but the word event seems to confuse my question. I was asked to add a new question, so I will try to do my best in another way:
Related post
My component:
import React, { useState } from "react";
const DropdownPaging2 = props => {
function myClickFunc(val) {
alert("WHAT SHOULD I ADD HERE TO FIRE MY EVENT TO THE CONSUMING COMPONENT");
}
return <div onClick={() => myClickFunc(100)}>CLICK me</div>;
};
export default DropdownPaging2;
Using my component in another components (comsuming component) render function:
<DropdownPaging2></DropdownPaging2>
I would like implement so I can pass a new event to the consuming component. Something lige this:
<DropdownPaging2 myCustomEvent={() => myCustomEvent(100)}></DropdownPaging2>
You can pass functions as props to your DropdownPaging2 component like you mentioned:
<DropdownPaging2 myEvent={() => myCustomEvent(100)}></DropdownPaging2>
And then use it in the component like this.
const DropdownPaging2 = props => {
const myClickFunc = (val) => {
if(props.myEvent){
props.myEvent();
} else {
// default if no function is passed
}
}
return <div onClick={() => myClickFunc(100)}>CLICK me</div>;
};
export default DropdownPaging2;
This way you are free to pass a custom function
Just make your component use the custom callback if it was passed as a prob, otherwise use the default one.
return <div onClick={prop.myCustomEvent ? prop.myCustomEvent : () => myClickFunc(100)}>CLICK me</div>;

Material UI's withTheme() hides component from ref prop

For the rare times when you need a reference to another JSX element in React, you can use the ref prop, like this:
class Widget extends React.PureComponent {
example() {
// do something
}
render() {
...
<Widget ref={r => this.mywidget = r}/>
<OtherWidget onClick={e => this.mywidget.example()}/>
Here, the Widget instance is stored in this.mywidget for later use, and the example() function can be called on it.
In Material UI, you can wrap components around a withTheme() call to make the theme accessible in their props:
export default withTheme()(Widget);
However if this is done, the ref receives an instance of WithTheme rather than Widget. This means the example() function is no longer accessible.
Is there some way to use ref with a component wrapped by withTheme() so that the underlying object can still be accessed, in the same manner as if withTheme() had not been used?
Here is an example demonstrating the issue. Lines 27 and 28 can be commented/uncommented to see that things only fail when the withTheme() call is added.
In order to get the ref of the component which is wrapped with withStyles, you can create a wrapper around Widget, and use that with withStyles like
const WithRefWidget = ({ innerRef, ...rest }) => {
console.log(innerRef);
return <Widget ref={innerRef} {...rest} />;
};
const MyWidget = withTheme()(WithRefWidget);
class Demo extends React.PureComponent {
constructor(props) {
super(props);
this.mywidget = null;
}
render() {
return (
<React.Fragment>
<MyWidget
innerRef={r => {
console.log(r);
this.mywidget = r;
}}
/>
<Button
onClick={e => {
e.preventDefault();
console.log(this.mywidget);
}}
variant="raised"
>
Click
</Button>
</React.Fragment>
);
}
}
Have a look at this answer to see an other alternative approach
losing functions when using recompose component as ref
This is a shorter alternative based on Shubham Khatri's answer. That answer works when you can't alter the inner component, this example is a bit shorter when you can modify the inner component.
Essentially ref doesn't get passed through withTheme() so you have to use a prop with a different name, and implement ref functionality on it yourself:
class Widget extends React.PureComponent {
constructor(props) {
props.ref2(this); // duplicate 'ref' functionality for the 'ref2' prop
...
const MyWidget = withTheme()(Widget);
...
<MyWidget
ref2={r => {
console.log(r);
this.mywidget = r;
}}
/>

using mobx with react functional components and without decorators

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. ;)

Correct way to pass functions to handlers in 'dumb' components in React

Looking at this simple example where the prop toggleData would be a redux thunk action mapped to the container props.
Is this the recommended way to pass a function like this to a child 'dumb' component? I read an article online saying that using arrow functions inside handlers is expensive and not very efficient from a performance perspective.
class Container extends React.Component {
render(){
return (
<Home toggleData={this.props.toggleData}/>
)
}
}
const Home = (props) => {
return (
<button onClick={()=>{props.toggleData()}}></button>
)
}
Yes! Avoid using arrow functions inside the JSX. Because the function will be created on every render, this might lead to performance issues later on.
If you don't need to send parameters you can do something like this:
const Home = (props) => {
return (
<button onClick={props.toggleData}></button>
)
}
If you need to use parameters, I usually define a class to create the callback using an arrow function, this way it gets created and bound only once.
class Home extends PureComponent {
onToggle = () => {
this.props.toggleData(1, 2, 3);
}
render() {
return (
<button onClick={this.onToggle}></button>
)
}
}

Resources