How to preserve referencies when Component is exported via a function? - javascript

Documentation describes how to add a ref to a class component when using ReactJS version 16.3+.
Here is a simplified and working example using two files:
MyForm.js file:
import React, { Component } from 'react';
import MyInput from "./MyInput";
class MyForm extends Component {
constructor(props) {
super(props);
this.myInput = React.createRef();
this.onClick = this.onClick.bind(this);
}
onClick(){
console.log(this.myInput.current.isValid());
}
render() {
return (
<div>
<MyInput ref={this.myInput} />
<button onClick={this.onClick}>Verify form</button>
</div>
);
}
}
export default MyForm;
MyInput.js file
import React, { Component } from 'react';
class MyInput extends Component {
isValid(){
return true;
}
render() {
return (
<div>
Name :
<input type="text" />
</div>
);
}
}
export default MyInput;
It works fine, console displays true when I click on MyForm button. But as soon as I add a function just before exporting my Component, errors are thrown. As example, I add a translation via react-i18n
MyInput.js file with export using a function
class MyInput extends Component {
isValid(){
return true;
}
render() {
const {t} = this.props;
return (
<div>
{t("Name")}
<input type="text" />
</div>
);
}
}
export default translate()(MyInput); // <=== This line is changing
Now, when I click on button, an error is thrown:
TypeError: this.myInput.current.isValid is not a function
The error disappear when I remove translate() in the last line.
I understood that the ref has been destroyed by the new component returned by translate function. It's an HOC. I read the Forwarding ref chapter, but I don't understand how to forward ref to the component returned by translate() function.
I have this problem as soon as I use translate from reacti18next and with the result of connect function from redux
I found a solution using onRef props and ComponentDidMount, but some contributors thinks this is an antipattern and I would like to avoid this.
Is there a way to create a wrapper that catch the HOC result of translate() or connect() and add ref to this HOC result ?

Related

How to pass a value from a function to a class in React?

Goal
I am aiming to get the transcript value, from the function Dictaphone and pass it into to the SearchBar class, and finally set the state term to transcript.
Current code
import React from 'react';
import SpeechRecognition, { useSpeechRecognition } from 'react-speech-recognition';
const Dictaphone = () => {
const { transcript } = useSpeechRecognition()
if (!SpeechRecognition.browserSupportsSpeechRecognition()) {
return null
}
return (
<div>
<button onClick={SpeechRecognition.startListening}>Start</button>
<p>{transcript}</p>
</div>
)
}
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
term: ''
}
this.handleTermChange = this.handleTermChange.bind(this);
}
handleTermChange(event) {
this.setState({ term: event.target.value });
}
render() {
return (
<div className="SearchBar">
<input onChange={this.handleTermChange} placeholder="Enter some text..." />
<Dictaphone />
</div>
)
}
}
export { SearchBar };
Problem
I can render the component <Dictaphone /> within my SearchBar. The only use of that is it renders a button and the transcript. But that's not use for me.
What I need to do is, get the Transcript value and set it to this.state.term so my input field within my SearchBar changes.
What I have tried
I tried creating an object within my SearchBar component and called it handleSpeech..
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
term: ''
}
this.handleTermChange = this.handleTermChange.bind(this);
}
handleTermChange(event) {
this.setState({ term: event.target.value });
}
handleSpeech() {
const { transcript } = useSpeechRecognition()
if (!SpeechRecognition.browserSupportsSpeechRecognition()) {
return null
}
SpeechRecognition.startListening();
this.setState({ term: transcript});
}
render() {
return (
<div className="SearchBar">
<input onChange={this.handleTermChange} placeholder="Enter some text..." />
<button onClick={this.handleSpeech}>Start</button>
</div>
)
}
}
Error
But I get this error:
React Hook "useSpeechRecognition" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
React Hooks must be called in a React function component or a custom React Hook function
Well, the error is pretty clear. You're trying to use a hook in a class component, and you can't do that.
Option 1 - Change SearchBar to a Function Component
If this is feasible, it would be my suggested solution as the library you're using appears to be built with that in mind.
Option 2
Communicate between Class Component <=> Function Component.
I'm basing this off your "current code".
import React, { useEffect } from 'react';
import SpeechRecognition, { useSpeechRecognition } from 'react-speech-recognition';
const Dictaphone = ({ onTranscriptChange }) => {
const { transcript } = useSpeechRecognition();
// When `transcript` changes, invoke a function that will act as a callback to the parent (SearchBar)
// Note of caution: this code may not work perfectly as-is. Invoking `onTranscriptChange` would cause the parent's state to change and therefore Dictaphone would re-render, potentially causing infinite re-renders. You'll need to understand the hook's behavior to mitigate appropriately.
useEffect(() => {
onTranscriptChange(transcript);
}, [transcript]);
if (!SpeechRecognition.browserSupportsSpeechRecognition()) {
return null
}
return (
<div>
<button onClick={SpeechRecognition.startListening}>Start</button>
<p>{transcript}</p>
</div>
)
}
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
transcript: ''
}
this.onTranscriptChange = this.onTranscriptChange.bind(this);
}
onTranscriptChange(transcript){
this.setState({ transcript });
}
render() {
return (
<div className="SearchBar">
<input onChange={this.handleTermChange} placeholder="Enter some text..." />
<Dictaphone onTranscriptChange={onTranscriptChange} />
</div>
)
}
}
useSpeechRecognition is a React hook, which is a special type of function that only works in specific situations. You can't use hooks inside a class-based component; they only work in function-based components, or in custom hooks. See the rules of hooks for all the limitations.
Since this hook is provided by a 3rd party library, you have a couple of options. One is to rewrite your search bar component to be a function. This may take some time if you're unfamiliar with hooks.
You can also see if the react-speech-recognition library provides any utilities that are intended to work with class-based components.

OnClick is not working, but no errors appear in the console

As I am currently learning React, I am creating a joke generator. I created a local file and was able to pull the data (jokes) and display it on the browser. Now, I am trying to create a button that when you click, it displays a random joke. I can see the joke and button, but no action is being triggered. I check the console and there are no errors. If I use onClick = {randomJoke}, then I get an error saying listener was expecting a function, not an object. Can someone point help me out and point out what is wrong?
This is how I currently have it set up:
import React from 'react'
import SportsJokesData from './SportsJokesData';
class SportsJokesApi extends React.Component {
constructor(props){
super(props);
this.getRandomJoke = this.getRandomJoke.bind(this);
}
getRandomJoke(){
return SportsJokesData[(SportsJokesData.length * Math.random()) << 0]
}
render() {
const randomJoke = this.getRandomJoke()
return (
<React.Fragment>
<p> {randomJoke.question}</p>
<p>{randomJoke.answer}</p>
<button onClick={this.getRandomJoke}>
click here
</button>
</React.Fragment>
)
}
}
export default SportsJokesApi;
This is how the file was originally scripted prior to adding the button.
import React from 'react'
import SportsJokesData from './SportsJokesData';
class SportsJokesApi extends React.Component {
getRandomJoke(){
return SportsJokesData[(SportsJokesData.length * Math.random()) << 0]
}
render() {
const randomJoke = this.getRandomJoke()
return (
<React.Fragment>
<p>{randomJoke.question}</p>
<h1>{randomJoke.answer}</h1>
</React.Fragment>
)
}
}
export default SportsJokesApi;
You cannot just return a new component on onClick event. You need to set some state first. This guide is useful: https://flaviocopes.com/react-show-different-component-on-click/
You need to re-render your React component, when there is a new joke. To do this store the randomJoke in the state of your React Component.
When you call this.getRandomJoke you should update the state along with it, so that a re-render of the React component is triggered. When this happens the UI will be updated with the latest value of the randomJoke
import React from 'react'
import SportsJokesData from './SportsJokesData';
const initialState = {
randomJoke: null
};
class SportsJokesApi extends React.Component {
constructor(props) {
super(props);
this.getRandomJoke = this.getRandomJoke.bind(this);
this.state = initialState;
}
getRandomJoke() {
this.setState({
randomJoke: SportsJokesData[(SportsJokesData.length * Math.random()) << 0]
});
}
render() {
const { randomJoke } = this.state;
return (
<React.Fragment >
<p> {randomJoke.question} </p>
<p> {randomJoke.answer} </p>
<button onClick = {this.getRandomJoke}>click here </button>
</React.Fragment >
);
}
}
export default SportsJokesApi;

Importing New React Component Compiles But Does Not Render

So I've been having problems with this code that i am working on
Dependencies: React, Redux, Eslinter, PropTypes, BreadCrumb
There's a view page that imports various components from other files
The Existing Structure Goes:
import Component from './Component.js';
...
let var = '';
...
if (this.state.value !=== null) {
var = <Component/>
}
...
render() {
return(
<div>
SomeContent
{var}
</ div>
)
}
When i try to import my created PureComponent the code Compiles.
Component Dependencies: { Button, Modal }React-Bootstrap, React, PropTypes
However the page does not render, and i cant figure out the reason why it would not render when it is introduced in the same manner as the existing structure Above
Update: I have tried making a bare minimum component returning just a simple Div & got the same result
RESOLVED:
There was a depreciated reference in my component to Modal from 'react-bootstrap' which still contained the node_module reference title but not the corresponding JS file which they have since moved to 'react-modal'
import Component from './Component.js';
let entity = '';
if (this.state.value !== null) {
entity = <Component />
}
render() {
return (
<div>
SomeContent
{entity}
</ div>
)
}
Firstly, You can not use var keyword for naming a variable and also, please check your condition fulfillment.
When dealing with state and conditional rendering, you should put conditions within the render method (or in a class field):
Working example:
import React from "react";
import OtherComponent from "../OtherComponent";
class Example extends React.Component {
constructor() {
super();
this.state = {
value: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
return (
<div>
SomeContent
{this.state.value && <OtherComponent />}
<br />
<input
value={this.state.value}
placeholder="Type something..."
onChange={this.handleChange}
/>
</div>
);
}
}
export default Example;

React-Chat-Widget props not forwarded

I am using the react-chat-widget and trying to call a function in the base class of my application from a custom component rendered by the renderCustomComponent function of the widget.
Here is the code for the base class:
import React, { Component } from 'react';
import { Widget, handleNewUserMessage, addResponseMessage, addUserMessage, renderCustomComponent } from 'react-chat-widget';
import 'react-chat-widget/lib/styles.css';
import Reply from './Reply.js';
class App extends Component {
handleNewUserMessage = (newMessage) => {
renderCustomComponent(Reply, this.correct);
}
correct = () => {
console.log("success");
}
render() {
return (
<div className="App">
<Background />
<Widget
handleNewUserMessage={this.handleNewUserMessage}
/>
</div>
);
}
}
export default App;
And here is the code for the custom component Reply:
import React, { Component } from 'react';
import { Widget, addResponseMessage, renderCustomComponent, addUserMessage } from 'react-chat-widget';
class Reply extends Component {
constructor(props) {
super(props);
}
sendQuickReply = (reply) => {
console.log(this.props); //returns empty object
//this.props.correct(); <-- should be called
};
render() {
return (
<div className="message">
<div key="x" className={"response"}onClick={this.sendQuickReply.bind(this, "xx")}>xx</div>
</div>)
}
}
export default Reply;
According to ReactJS call parent method this should work. However, when I print the this.props object it is empty, although the documentation of the renderCustomComponent method states that the second argument of the component to render are the props that the component needs (in this case the parent class function).
Where have I gone wrong?
The second parameter is considered as props, but it is expected to be an object. you would pass it like
handleNewUserMessage = (newMessage) => {
renderCustomComponent(Reply, {correct: this.correct});
}

Call class method of default import in react

I'm wondering whether its possible to call a method on a component that I import from another file. Basically, my situation is that I have two react classes. One of them is a Sudoku puzzle, which I call Game, and which includes the updateArray() method:
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {arr: [[5,0,4,9,0,0,0,0,2],
[9,0,0,0,0,2,8,0,0],
[0,0,6,7,0,0,0,0,9],
[0,0,5,0,0,6,0,0,3],
[3,0,0,0,7,0,0,0,1],
[4,0,0,1,0,0,9,0,0],
[2,0,0,0,0,9,7,0,0],
[0,0,8,4,0,0,0,0,6],
[6,0,0,0,0,3,4,0,8]]};
this.handleSubmit = this.handleSubmit.bind(this);
this.updateArray = this.updateArray.bind(this);
}
componentWillReceiveProps(nextProps) {
if(nextProps.arr != this.props.arr){
this.setState({arr: nextProps.value });
}
}
updateArray(str_arr) {
this.setState({arr: str_arr});
}
handleSubmit(event) {
...
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className = "game">
<div className = "game-board">
<Board value = {this.state.arr} />
</div>
<div className = "game-info">
<div></div>
</div>
</div>
<input type="submit" value="Submit" />
</form>
);
}
}
export default Game;
And then I have a second class that gets a image of a sudoku puzzle and makes a corresponding 9x9 array using computer vision methods. I then try to send the array back to Game using its updateArray function:
import Game from './Sudoku';
export default class ImageInput extends React.Component {
constructor(props) {
super(props);
this.state = {
uploadedFile: ''
};
}
onImageDrop(files) {
this.setState({uploadedFile: files[0]});
this.handleImageUpload(files[0]);
}
handleImageUpload(file) {
var upload = request.post('/')
.field('file', file)
upload.end((err, response) => {
if (err) {
console.error(err);
}
else {
console.log(response);
console.log(Game);
//ERROR HAPPENING HERE
Game.updateArray(response.text);
}
});
}
render() {
return (
<div>
<Dropzone
multiple = {false}
accept = "image/jpg, image/png"
onDrop={this.onImageDrop.bind(this)}>
<p>Drop an image or click to select file to upload</p>
</Dropzone>
);
}
}
However, when I try to send the array to Game's method, I get a Uncaught TypeError:
Uncaught TypeError: _Sudoku2.default.updateArray is not a function
at eval (image_input.js?8ad4:43)
at Request.callback (client.js?8e7e:609)
at Request.eval (client.js?8e7e:436)
at Request.Emitter.emit (index.js?5abe:133)
at XMLHttpRequest.xhr.onreadystatechange (client.js?8e7e:703)
I want the updateArray() method to update the Game from a separate file, which will then cause the Game to re-render. Is this possible? I've spent a lot of time reading documentation, and it seems as though what I'm suggesting is not the typical workflow of react. Is it dangerous, and if so, can someone explain why?
Also, both classes are rendered in a separate file that looks like this:
import Game from './Sudoku';
import ImageUpload from './image_input';
document.addEventListener('DOMContentLoaded', function() {
ReactDOM.render(
React.createElement(ImageUpload),
document.getElementById('image-upload'),
);
ReactDOM.render(
React.createElement(Game),
document.getElementById('sudoku_game'),
);
});
First of all, in your separate file (the one rendering both Game and ImageInput components):
Make it render only one component. This could have a original name like App for instance. Like this:
import App from './App';
document.addEventListener('DOMContentLoaded', function() {
ReactDOM.render(
React.createElement(App),
document.getElementById('root'),
);
});
You would only have to change the imports and name of the root element as needed of course.
Then, for the App component:
import React from 'react';
import Game from './Sudoku';
import ImageUpload from './image_input';
class App extends React.Component {
constructor() {
super();
this.state = {
sudokuArray = [];
}
}
updateArray(newArray) {
this.setState({sudokuArray: newArray})
}
render() {
<div>
<Game sudokuArray={this.state.sudokuArray} />
<ImageUpload updateArray={this.updateArray.bind(this)} />
</div>
}
}
export default App;
And inside your ImageInput component you would call the update method like:
this.props.updateArray(response.text).
Also, inside your Game component, change the render function, specifically the part with the Board component to: <Board value = {this.props.sudokuArray} />.
This is a rather common situation when you are learning React. You find yourself trying to pass some prop or run some method inside a component that is not "below" the component you are currently working with. In these cases, maybe the prop you want to pass or the method you want to run should belong to a parent component. Which is what I suggested with my answer. You could also make Game as a child of ImageInput or vice-versa.

Categories

Resources