Importing New React Component Compiles But Does Not Render - javascript

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;

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.

Display a new component with button click

Im making my first react ptoject. Im new in JS, HTML, CSS and even web app programing.
What i try to do, is to display some infomration on button click.
I have an API, that looks like this:
endpoint: https://localhost:44344/api/Projects
My Data from it:
[{"id":1,"name":"Mini Jira","description":"Description for first project in list","tasks":null},{"id":2,"name":"Farm","description":"Description for second one","tasks":null}]
And im fine with that, i can get it easily by axios in my react app.
Now i will show you my Project.js Component:
import React, { Component } from "react";
import { ListGroupItem, Button, ButtonToolbar } from "react-bootstrap";
import ProjectDetails from "./ProjectDetails";
class Project extends Component {
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name}</ListGroupItem>
<Button onClick={Here i want to display new component with details }bsStyle="primary">Details</Button>
</ButtonToolbar>
);
}
}
export default Project;
I have all data from api in project type.
My question is, how to display component that i named ProjectDetails.js on button click? I want to show all data stored in project from my api in separate view (new page or somethig like that).
View looks like this:
Thanks for any advices!
EDIT:
based on #Axnyff answer, i edited Project.js. it works ok. But when i want to (for testing) displat project.name, i get error map of undefined. My ProjectDetails.js:
import React, { Component } from "react";
class ProjectDetails extends Component {
state = {};
render() {
return <li>{this.props.project.name}</li>;
}
}
export default ProjectDetails;
EDIT2:
In Project.js in #Axnyff answet i just edited that line:
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
i passed project by props, now it works like i want too. After click it displays project.name that i clicked on.
You should use state in your React component.
Let's create a field called showDetails in your state.
You can initialize it in your constructor with
constructor(props) {
super(props); // needed in javascript constructors
this.state = {
showDetails: false,
};
}
Then you need to modify the onClick to set that state to true
<Button onClick={() => this.setState({ showDetails : true })} bsStyle="primary">Details</Button>
And then use that state to show or not the ProjectDetails:
{ showDetails && <ProjectDetails /> }
The full component should look like
import React, { Component } from "react";
import { ListGroupItem, Button, ButtonToolbar } from "react-bootstrap";
import ProjectDetails from "./ProjectDetails";
class Project extends Component {
constructor(props) {
super(props); // needed in javascript constructors
this.state = {
showDetails: false,
};
}
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name}</ListGroupItem>
<Button onClick={() => this.setState({ showDetails : true })} bsStyle="primary">Details</Button>
{ this.state.showDetails && <ProjectDetails /> }
</ButtonToolbar>
);
}
}
export default Project;
You can then modify the logic to add a toggling effect etc.
If you haven't done it, you should probably follow the official tutorial
function Bar() {
return <h1>I will be shown on click!</h1>;
}
class Foo extends React.Component {
constructor() {
super();
this.state = { showComponent: false };
}
handleClick = () => {
this.setState({ showComponent: !this.state.showComponent });
};
render() {
return (
<div>
{this.state.showComponent && <Bar />}
<button onClick={this.handleClick}>click</button>
</div>
);
}
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<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>
<div id="root"></div>

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

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 ?

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.

How to render component again at the top level with the new props in React 0.14

In the new React.js 0.14 setProps method was deprecated form React.Component. According to documentation, now we should render component again with new props(using ReactDOM.render())
The question is how can I do this? Let's look at an example.
AddTaskForm.js
import React from 'react';
class AddTaskForm extends React.Component
{
onClick() {
this.setProps({
isVisible: false
});
}
render() {
return (
<form role="form">
<div className="row" className={ this.props.isVisible ? 'show' : 'hidden' }>
<input className="form-control" type="text" />
</div>
<button type="button" className="btn" onClick={this.onClick.bind(this)}>Add task</button>
</form>
);
}
}
export default AddTaskForm;
Todo.js
var React = require('react');
var ReactDOM = require('react-dom');
var AddTaskForm = require('./AddTaskForm');
class Todo extends React.Component
{
render() {
return (
<div>
<AddTaskForm isVisible="false"></AddTaskForm>
</div>
);
}
}
ReactDOM.render(<Todo />, document.getElementById('app'));
Btw. I am using ES6 stage 0, if you find yourself confused with syntax
Ok, I can't call setProps, i had to rerender AddTaskForm component in Todo.js. How can I do this in this case? I've read this article form react's blog but wrapper don't really help me.
You should not use setProps at all. Properties in React must be immutable data, so you can't change them. Looks like isVisible prop is really part of component state, because depends on it component decides to show or not to show itself. The best choice for such situations when after some change of data you need to update component is state. I would recommend to read this doc about state and what should and should not go to state
From your example:
<div>
<AddTaskForm isVisible="false"></AddTaskForm>
</div>
.. how do you know should AddTaskForm be visible? For now it's hardcoded. I suggest doing something like that:
render () {
let shouldFormBeVisible = ...// somehow get to know should it be visible or not
return (
<div>
shouldFormBeVisible ? <AddTaskForm /> : null
</div>
)
}
Here is a big advantage: you even don't need to render AddTaskForm and if isVisible is false then hide it. You don't render component if it isn't needed.
isVisible is a piece of mutable state, so it needs to be stored in state somewhere.
One way is to put it on the AddTaskForm:
import React from 'react';
class AddTaskForm extends React.Component
{
constructor(props, context) {
super(props, context);
this.state = { isVisible: true};
}
onClick() {
this.setState({
isVisible: false
});
}
render() {
return (
<form role="form">
<div className="row" className={ this.state.isVisible ? 'show' : 'hidden' }>
<input className="form-control" type="text" />
</div>
<button type="button" className="btn" onClick={this.onClick.bind(this)}>Add task</button>
</form>
);
}
}
export default AddTaskForm;
Todo.js
var React = require('react');
var ReactDOM = require('react-dom');
var AddTaskForm = require('./AddTaskForm');
class Todo extends React.Component
{
render() {
return (
<div>
<AddTaskForm />
</div>
);
}
}
ReactDOM.render(<Todo />, document.getElementById('app'));
Another way is to store it on the Todo component, but I think you have enough to get started.

Categories

Resources