Can React Hooks have methods? - javascript

How does one write a React Hooks function to export methods in the export default?
I don't see examples anywhere, so I suspect that React Hooks don't work this way, but... I'm curious if it is possible to extend the hook function to export child methods.
Checkout.js:
import React from "react";
function Checkout() {
return <section className="checkout"></section>;
}
// Add Item Method
Checkout.addItemPrice = (item, price) => {
console.log("this is a method");
};
export default Checkout;
Checkout.test.js:
import React from "react";
import ReactDOM from "react-dom";
import Checkout from "./Checkout";
describe("Checkout Test Suite", () => {
it("Can add an item", () => {
var checkout = new Checkout();
checkout.addItemPrice('a', 1);
});
});
TypeError: checkout.addItemPrice is not a function
I haven't been able to find a way to write this in Hooks, but at the end of the day, I wind up splitting them into 2 functions, with 2 exports, and 2 imports.

Nothing to do with hooks. If you want your new Checkout() instance to have a addItemPrice method, then you should add it to the prototype:
Checkout.prototype.addItemPrice = (item, price) => {

When you execute:
var checkout = new Checkout();
an object of type reactComponent is returned to a checkout variable regardless of New keyword is used or not.
This object doesn't have a method you are trying to call.
You may try to define your method using a prorotype chain of this object (of reactComponent), but this object may be protected against any changes.
Even if you manage to set a method to reactComponet, this is not a good thing and surely not a React way to do things.

Related

I have two project with the an identical call to the useContext() react function. One project works and the other doesn't

I have to projects where I'm using the useContext() react function to create a user auth context. I'm using the same exact js file in both projects. For some reason in my new project the create context returns a slightly different object than the object in the old projects even though the initialization is identical. This is causing the createContext() function in the new project to return null. The context object in the first project has a calculateChangedBits item which might play a role in I have a theory that this might be due to some sort of react update. Here what is inside the js file. I also added two pictures to show the authUsercontext output for both the old and new project. The old project is the one with calculateCHangedBits in it.
(https://i.stack.imgur.com/kOeyt.jpg)](https://i.stack.imgur.com/kOeyt.jpg)](https://i.stack.imgur.com/gYUSw.jpg)](https://i.stack.imgur.com/gYUSw.jpg)
import { createContext, useContext, Context } from 'react'
const authUserContext = createContext({
authUser: null,
loading: true,
calculateChangedBits: null,
signInWithEmailAndPassword: async () => {},
createUserWithEmailAndPassword: async () => {},
signOut: async () => {}
});
export const useAuth = () => useContext(authUserContext);
I have isolated the issue to be with the return of the create context but I do not understand why the new useContext function returns null

How to implement your own useMemo from Scratch, for React

Basically this:
function MyComponent() {
let [count, setCount] = useState(1)
let memo = useMyMemo(() => new MyClass)
return <div onClick={update}>{count}</div>
function update() {
setCount(count + 1)
}
}
function useMyMemo(fn) {
// what to do here?
}
class MyClass {
}
I would like for useMyMemo to only return 1 instance of the class per component instance. How do I set this up to implement it without resorting to using any of the existing React hooks? If it's not possible without the React hooks, they why not? If it is possible only through accessing internal APIs, how would you do that?
As a bonus it would be helpful to know how it could be passed property dependencies, and how it would use that to figure out if the memo should be invalidated.
I think you're describing how useMemo already works, assuming you pass [] as the dependencies parameter. It should already create one instance of MyClass per instance of MyComponent. However, the documentation says that future versions of useMemo will not guarantee that the value is only called once, so you might want to try useRef.
const memo = useRef(null);
if (!memo.current) {
memo.current = new MyClass()
}
If you want it to create a new instance of MyClass when dependencies change, you'll have to either use useMemo (and accept the chance that the value might be invalidated occasionally), or do your own shallow comparison against the previous value. Something like this:
const useMyMemo = (create, dependencies) => {
const val = React.useRef(create());
const prevDependencies = React.useRef([]);
if (!shallowEquals(dependencies, prevDependencies.current)) {
val.current = create();
prevDependencies.current = [...dependencies];
}
return val;
};
I'll leave the implementation of shallowEquals to you, but I believe lodash has one.
Now you really asked for a function that doesn't use any React hooks, and mine uses useRef. If you don't want useRef, you can create your own simple memoization function that always returns the same pointer regardless of changes to .current.
Simple mock implementation of useMemo using useRef.
Note: You can shallowCompare or deepCompare to compute isChanged based on your need. Example if dependencies are objects.
/* File: useMemo.js */
import { useRef } from "react";
export function useMemo(callback, deps) {
const mPointer = useRef([deps]); // Memory pointer (Make sure to read useRef docs)
const isChanged = mPointer.current[0].some(
// If dependencies changed
(item, index) => item !== deps[index]
);
if (mPointer.current.length === 1 || isChanged) {
// If first time or changed, compute and store it
mPointer.current = [deps, callback()];
}
return mPointer.current[1];
}
See CodeSandbox demo.
Read: useRef returns a mutable ref object whose .current property is
initialized to the passed argument (initialValue). The returned object
will persist for the full lifetime of the component.
Pure javascript mock using closure - [ For educational purpose ONLY! ]
/* File: useMemo.js */
function memo() {
const stack = {};
return (callback, deps) => {
/* Creating hashkey with dependencies + function source without whitespaces */
const hash = deps.join("/") + "/" + callback.toString().replace(/\s+/g, "");
if (!stack[hash]) {
stack[hash] = callback();
}
return stack[hash];
};
};
export const useMemo = memo();
See CodeSandbox demo.
Idea explaination : With above code trying to create a unique hash by combining dependencies value & function source using toString,
removing spaces in string to make hash as unique as possible. Using this as a key
in the hashmap to store the data and retrieve when called.
The above code is buggy, for example callback result that are cached are not removed when component unmount's. Adding useEffect and return clean-up function should fix this during unmount. Then this solution becomes tightly coupled with react framework
Hope this helps. Feedback is welcomed to improve the answer!

Updating my imported variable's value in React.js

I am refractoring an app I've build using React.js. I am exporting a variable from the global scope of Spotify.js and importing it in two other files App.js and Button.js.
After calling a function from Spotify.js that sotres a new value to the variable, It's new value is exported to 'Button.js' but stays an empty string in 'App.js'.
Your help would be appriciated :)
export let userAccessToken = '';
export const Spotify = {
...
getUserAccessToken (){
//stores a new string to userAccessToken.
}
}
import {userAccessToken, Spotify} from '../../util/Spotify';
export class App extends React.Component {
//a conditional rendering happens depending on if(!userAccessToken)
}
import {userAccessToken, Spotify} from '../../util/Spotify'
export class Button extends React.Component {
componentDidMount() {
if (!userAccessToken) {
console.log(`Button's UAT before calling the fn: ${userAccessToken}`)
Spotify.getUserAccessToken();
console.log(`Button's UAT after calling the fn: ${userAccessToken}`);
}
}
...
}
This is not how you share data between react components.
Use react context or pass props between components
You could use react context to share data or simply pass it as props (if the components are closely related in the component tree)
The only thing I can recommend is to export the userAccessToken, something like this, but you can't change its value outside the module
export const Spotify = {
...
getUserAccessToken (){
//stores a new string to userAccessToken.
}
}
...
}
const accessToken = Spotify.getUserAccessToken();
export const userAccessToken = accessToken;
If you got to read this question I solved it.
Turns out I should have called my method Spotify.getUserAccessToken() from App.js using the react lifecycle method componentDidMount().
The export and import methods are like snapshots of the module and therefore when I exported the variable userAccessToke from Spotify.js before calling the my method I imported an empty string and it did not update in all files.
Thanks Jørgen and joseluismurillorios for your support and time spent answering :)

React - binding `this` to an imported function

In my React app, I have a handful of functions that I'd like to be able to access across a few similar components... however, I want to bind this to the shared functions so that they can do things like update the component state, etc... however, it seems that importing the functions and then trying to bind this in the 'typical' React manner does not work.
Here's an illustration of what I'd like to accomplish - in this case, clicking the rendered button would call the function from the imported shared function file and update the component state:
//shared_functions.js
const sharedFunctions = {
testFunction = () => {
this.setState({functionWasRun: true})
}
}
//MyComponent.jsx
import React, { Component } from 'react';
import sharedFunctions from '../static/scripts/shared_functions.js';
let { testFunction } = sharedFunctions;
class MyComponent extends Component {
constructor(props){
super(props);
this.testFunction = this.testFunction.bind(this)
this.state = {
functionWasRun: false
}
}
render(){
return(
<div>
<button onClick={this.testFunction}>Click</button>
</div>
)
}
}
Trying to run this code as is will return an error like:
Uncaught (in promise) TypeError: Cannot read property 'bind' of undefined
and I get what that's all about... but what I'd like to know is: is it possible to bind this to an imported function?
I'm starting to get a lot of similar-looking functions popping up throughout my app and I'd love to simplify things by abstracting them into a shared script, but I'm not sure how to achieve the typical this binding that's needed to achieve state-setting.
The following line is not trying to bind the imported testFunction but rather a method testFunction of <MyComponent>
To bind the imported function, refer to it directly, as follows:
this.testFunction = testFunction.bind(this);
// Notice how: ^--- there is no longer a this here
NB: You're example tries to use bind on an arrow function You cannot bind a new context to an arrow function. The this context of an arrow function will always be set to the location
were it is defined. You can get around this by declaring
testFunction using a regular function declaration:
const sharedFunctions = {
function testFunction(){
this.setState({functionWasRun: true})
}
}
I used it in the following way:
In constructor:
this.handleChange = handleChange.bind(this);
In the imported file (be careful, no arrow):
export const handleChange = function handleChange(event)
{
const { name, value } = event.target;
this.setState({
[name]: object
});
};
import ToastTimeout from 'toastTimout'
ToastTimeout.bind(this)('message to popup')
I was able to get the context of this in a simple service with a setTimeout function that changed the variable on this context
sorry for the sudo code

Stubbing class field-functions in ES7

In my test suite, how can I stub a class' property, which is a function*? With normal methods it's easy using Object.getOwnPropertyNames(component.prototype) and monkey patching each found method, but after a long time of struggle I haven't found any way to extract the functions created by assigning to class' fields.
My testing stack consists of Jest with Jasmine2 and babel.
The problem with transpiling is that the arrow-function-properties are (as expected, of course) assigned to instance of the output transpiled "class" (function actually, of course). So I don't see any way of stubbing them other then instantiating this object, am I right? Here is the example of input es7 code and the babel's output. However I don't particularly like this solution, seems very hacky. The other drawback of this solution is that I don't get to directly instantiate the component's class.
(*) The background of this question is unit testing React components written in es7-like classes with arrow functions assigned to class' properties for the purpose of auto binding.
I was having the same problem, when writing unit tests for a project I'm working, and I think I got a good pattern to solve it. Hopefully it helps:
Context
Here is an example of a React component that has a method handleClick defined using the fat arrow notation.
import React, { Component } from 'react';
class Foo extends Component {
componentWillMount() {
this.handleClick();
}
handleClick = (evt) => {
// code to handle click event...
}
render() {
return (
<a href="#" onClick={this.handleClick}>some foo link</a>
);
}
}
Problem
As described in this link Babel will transpile the code so that the handleClick method is only available after instantiation (check lines 31 to 33 of the generated constructor function)
The problem here is that sometimes you need to have access to methods defined using the fat arrow notation before instantiating the class.
Lets say for example that you are writing unit tests for the componentWillMount class method and you want to stub the handleClick so that you only test the desired unit. But now you have a problem, since you can only have access to handleClick after instantiation and componentWillMount method will be called automatically by React as part of its instantiation lifecycle.
Solution
Here is how I can apply a simple pattern to solve problems like this:
import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import Foo from './foo';
describe('Foo', () => {
describe('componentWillMount method', () => {
const handleClickStub = sinon.stub();
class FooWrapper extends Foo {
constructor(props) {
super(props);
this.handleClick = handleClickStub;
}
}
it('should register a click event listener to the externalElement property', () => {
handleClickStub.reset();
mount(<FooWrapper />);
expect(handleClickStub.calledOnce).to.be.true;
});
});
});
Explanation
I've wrapped the original Foo component into a FooWrapper where on its constructor after initializing the original component I replace the original handleClick method with a stubbed version allowing me to property test my componentWillMount class.
Due to the way babel transpiles the arrow function syntax on class methods via transform-class-properties, the class method is no longer bound on the prototype, but rather the instance.
Using Jest 19's built-in assertion and .spyOn methods, this was my solution:
import React from 'react';
import { shallow } from 'enzyme';
describe('MyComponent', () => {
it('should spy properly', () => {
const wrapper = shallow(<Component />);
const wrapperInstance = wrapper.instance();
const spy = jest.spyOn(wrapperInstance, 'functionName');
wrapperInstance.functionName();
expect(spy).toHaveBeenCalledTimes(1);
})
});

Categories

Resources