Using handleChange inside a default function instead of a class - javascript

I would like to use a Dropdown module with Semantic UI and react. The issue is all examples provided online use a default class App extends Component however I want to export default function App(). When I do this the I get a parsing error for the render() section, requiring a semicolon.
The below works very well but how can I implement it if the export was a default function instead?
import React, { useState, Component } from 'react'
import { Dropdown, Grid, Segment } from 'semantic-ui-react'
export default class DropdownExampleControlled extends Component {
state = {}
handleChange = (e, { value }) => this.setState({ value })
render() {
const { value } = this.state
return (
<Dropdown
onChange={this.handleChange}
options={options}
placeholder='Choose an option'
selection
value={value}
/>
)
}
}
Writing const infront of the handleChange did not fix anything, it just made the "value" undefined later on. I am very unsure of how to use this because I am new to JS. Any help is greatly appreciated.

When using function components rather than class components you have to utilise the hooks in React.
Heres how your code would look in a function component
export default const DropdownExampleControlled = () => {
const [yourState, setYourState] = useState({});
// yourState being the name of the state and {} being your initial state
const handleChange = (e, {value}) => {setYourState(value)}
return (
<Dropdown
onChange={handleChange}
options={options}
placeholder='Choose an option'
selection
value={yourState}
/>
)
}
Hooks are the newer way of persisting data, handling re-renders & more in modern React. If you want to learn more about how you can use hooks, here's a link to the docs https://reactjs.org/docs/hooks-intro.html

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.

Use imported variable as a dinamic prop in React

Sorry for the noob question, but I'm a noob in React and I am strugling with this.
I have a file that exports a variable that is being mutated over time. Let's say something like this (not the real code, the variable is changing correctly):
// variable.js
let myVar = 0;
setInterval(() => myVar++, 3000);
export { myVar };
and a react component that has to display the current value:
import React, { Component, Fragment } from "react";
import { myVar } from './variable.js';
export default class myComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Fragment>
<div>{myVar}</div>
</Fragment>
);
}
}
What would be the best approach to get the variable displayed correctly while they change? I have tryied to set is as a state, as a prop and rendering it directly, but I am missing something.
I can not export a getter function, as I don't know from the component when the variable is going to change, but maybe I can change the approach? maybe throwing an event in each change?
Try this, It won't work like the real-time update. But you can access like below
You can create a custom hook, that will update real-time
export default function useUpdate() {
const [myVar, setState] = useState(0)
setTimeout(function () {
setState(myVar++);
}, 3000);
return [myVar, setState];
}
import React, { Component, Fragment } from "react";
import { useUpdate } from './variable.js';
export default () => {
const [myVar] = useUpdate();
return (
<Fragment>
<div>{myVar}</div>
</Fragment>
);
}

Learning React: how to useRef in custom component?

I'm learning React and I don't think I understand the concept of useRef properly. Basically, I want to include some tags in tagify input field when a user clicks on a chip that is rendered outside the input box.
My idea is to do something like this (App.js):
import Chip from '#material-ui/core/Chip';
import Tagify from "./Tagify"
...
class App extends React.Component {
...
const { error, isLoaded, quote, tags } = this.state; //tags comes from the server
var tagify = <Tagify tags={tags} />
const addTagOnChipClick = (tag) => {
tagify.addTag(tag)
};
const chips = tags.map(tag => (
<span key={tag.name} className="chips">
<Chip
label={tag.name}
variant="outlined"
onClick={addTagOnChipClick(tag)}
clickable
/>
</span>
))
...
}
The tagify documentation says that
To gain full access to Tagify's (instance) inner methods, A custom ref can be used: <Tags tagifyRef={tagifyRef} ... />
My attempt to gain access to these inner methods was to use useRef (Tagify.js):
import Tags from '#yaireo/tagify/dist/react.tagify'
import '#yaireo/tagify/dist/tagify.css'
export default function Tagify(tags) {
const tagifyRef = useRef()
return (
<Tags
tagifyRef={tagifyRef}
placeholder='Filter by tags...'
whitelist={tags.tags}
/>
)
}
However, tagifyRef.current is undefined. What I'm doing wrong? There's another way to access the inner methods?
Thank you very much!
When are you accessing the ref? Make sure you access the ref only after the component has mounted i.e. in a useEffect:
import Tags from '#yaireo/tagify/dist/react.tagify'
import '#yaireo/tagify/dist/tagify.css'
export default function Tagify(tags) {
const tagifyRef = useRef()
React.useEffect(() => {
console.log(tagifyRef.current)
}, [])
return (
<Tags
tagifyRef={tagifyRef}
placeholder='Filter by tags...'
whitelist={tags.tags}
/>
)
}

React - How to let this functional component change div styles?

I am a bit of a react newbie so please be gentle on me. I am trying to build an education app, and code is to render multiple choice answer boxes:
export default function AnswerBox(props: any) {
return (
<div className="answer-container">
<ul>
{props.answers.map((value: any) => {
return (
<li className="answer-box" key={value.letter} id={value.letter}>
<input className="answer-textbox" type="checkbox" onChange={() => console.log('selected: ', value.letter)}></input>
<span className="answer-letter"><b>{value.letter})</b></span>
{value.answer}
</li>)
})
}
</ul>
</div>
)
}
As you can see, the function takes a object of arrays and iterates through arrays to display the Question Letter (e.x. 'A') and Question Answer in an unordered list.
So all of this is good, but I'd like the list element to be highlighted or have the answerbox div changed when it is actually selected. And I havent found a good way to do that other than to change the component into a stateful component and use a state var to track which box is ticked.
But when I changed it to a stateful component last night, I really struggled to pass in the list of objects to render.
How can I have a stateful class that also accepts props like a regular functional component?
To begin, you pass props to all types of components in a similar fashion, regardless if it's stateful or not.
<Component prop={someValue}/>
The only difference is how you would access them.
For class-based components you would access them through the props property of the class this.props. i.e.
class Component extends React.Component {
constructor(props) {
// you need to call super(props) otherwise
// this.props will be underfined
super(props);
}
...
someFunction = (...) => {
const value = this.props.prop;
}
}
If you're using TypeScript, you need to describe to it the structure of your props and state like this
interface iComponentProps {
prop: string;
};
interface iComponentState { ... };
export default class Component extends React.Component<iComponentProps, iComponentState> {
...
}
if your component takes in props and/or state and you're unsure of their structure, pass in any for the one you're unsure of.
On the other hand, if I understood your question correctly, you could do something like this:
I also made a demo of the simple app I made to address your other question.
In summary, you can have your AnswerBox component maintain an array of indexes that pertain to each of its choices and have it updated every time a choice is selected (or clicked) by using setState
You can also check out the useState hook to make your functional component stateful.
App.js
import React from "react";
import "./styles.css";
import Question from "./Question";
export default function App() {
const questionData = [
{
question: "Some Question that needs to be answered",
choices: ["Letter A", "Letter B", "Letter C"]
},
{
question: "Another Question that needs to be answered",
choices: ["Letter A", "Letter B", "Letter C"]
}
];
return (
<div className="App">
{questionData.map(question => (
<Question questionText={question.question} choices={question.choices} />
))}
</div>
);
}
Question.js
import React from "react";
import AnswerBox from "./AnswerBox";
const Question = ({ questionText, choices }) => {
return (
<div className={"question-container"}>
<p className={"question-text"}>{questionText}</p>
<AnswerBox choices={choices} />
</div>
);
};
export default Question;
QuestionChoice.js
import React from "react";
import clsx from "clsx";
const QuestionChoice = ({ letter, content, isSelected, handleClick }) => {
return (
<li
className={clsx("question-choice-container", {
"selected-choice": isSelected
})}
>
<input type={"checkbox"} value={content} onClick={handleClick} />
<label for={content} className={"question-choice-label"}>
<strong>{letter.toUpperCase()}. </strong>
<span>{content}</span>
</label>
</li>
);
};
export default QuestionChoice;
AnswerBox.js
import React, { PureComponent } from "react";
import QuestionChoice from "./QuestionChoice";
export default class AnswerBox extends PureComponent {
constructor(props) {
super(props);
this.choiceLetters = Array.from("abcdefghijklmnopqrstuvwxyz");
this.state = { activeChoices: [] };
}
_updateActiveChoices = index => {
let updatedList = [].concat(this.state.activeChoices);
if (this.state.activeChoices.indexOf(index) !== -1) {
updatedList.splice(updatedList.indexOf(index), 1);
} else {
updatedList.push(index);
}
return updatedList;
};
_handleChoiceSelect = choiceIndex => () => {
// an update to your component's state will
// make it re-run its render method
this.setState({ activeChoices: this._updateActiveChoices(choiceIndex) });
};
render() {
return (
<ul class={"answer-box"}>
{this.props.choices.map((choice, index) => (
<QuestionChoice
letter={this.choiceLetters[index]}
content={choice}
isSelected={this.state.activeChoices.indexOf(index) != -1}
handleClick={this._handleChoiceSelect(index)}
/>
))}
</ul>
);
}
}
For this type of interaction you can use the input checkbox's checked attribute to show that its checked. The check state should be derived from somewhere in your state. In the onClick function, you can look at the event for the name and checked state to update your state. Note you could add any attribute you want if name is too generic for you.
The interaction could look like this:
https://codesandbox.io/s/optimistic-archimedes-ozr72?file=/src/App.js

How to make a generic 'filter' Higher-Order Component in React.js?

I am making a Higher-Order Component in my React.js (+ Redux) app, to abstract the functionality to filter a list of elements with the string received from an input element.
My filtering HOC is,
filter.js
import React, { Component } from 'react'
export default function Filter(FilteredComponent) {
return class FilterComponent extends Component {
constructor(props) {
super(props)
}
generateList() {
if (this.props.searchTerm !== undefined) {
let re = new RegExp(state.searchTerm,'gi')
return this.props.currencyList.filter((c) => c.match(re))
}
else {
return this.props.currencyList
}
}
render() {
return (
<FilteredComponent
filteredList={this.generateList()}
{...this.props}
/>
)
}
}
}
Right now, I am unable to access the filteredList as props.filteredList in the SearchResults component.
The component to display the list is
SearchResults.js
import React from 'react'
const SearchResults = (props) => {
const listData = props.filteredList.map (item => <div>{item}</div>)
return (
<div>
Here are the search results.
<br />
<input
type="text"
value={props.searchTerm}
onChange={props.setSearchTerm}
/>
{listData}
</div> ) }
export default SearchResults
How do I go on about this?
EDIT:
Adding the container component for greater clarity:
SearchContainer.js
import {connect} from 'react-redux'
import SearchResults from '../components/SearchResults'
import * as a from '../actions'
import Filter from '../enhancers/filter'
const getSearchTerm = (state) => (state.searchTerm === undefined) ? '' : state.searchTerm
const mapStateToProps = (state) => {
return {
searchTerm: getSearchTerm(state),
currencyList: state.currencyList
}
}
const mapDispatchToProps = (dispatch) => {
return {
setSearchTerm: (e) => {
dispatch(a.setSearchTerm(e.target.value))
}
}
}
const SearchResultsContainer = connect(
mapStateToProps,
mapDispatchToProps
)(SearchResults)
export default Filter(SearchResultsContainer)
Let’s first think of components as a function that takes a props and returns a Virtual DOM.
Thus the SearchResult component takes these props:
filteredList
searchTerm
setSearchTerm
The higher-order-component created created by connect() provides these props:
searchTerm
currencyList
The Filter() higher-order component:
takes currencyList
provides filteredList
Therefore, you have to wire it like this so that each part receives the props it needs:
connect(...) → Filter → SearchResult
It should look like this:
export default connect(...)(Filter(SearchResult))
Or if you use recompose:
const enhance = compose(connect(...), Filter)
export default enhance(SearchResult)
compose() wraps the components from right to left. Therefore, the leftmost higher-order component becomes the outermost one. This means the props will flow from left to right.
Please note that state.searchTerm in FilterComponent#generateList should be this.props.searchTerm.
What is 'state.searchTerm' in your wrapper function? I have a feeling you mean this.props.searchTerm. Also, you don't need an empty constructor in es6 classes. Also, this is work better done by a selector in your mapstatetoprops on the container.
Edit:
Also, you need to wrap the actual 'dumb' component, not the result of your connect call. That way your redux store is connected to your Filter component and will be rerendered when you're store changes.
generateList() is not reactive. It does not get triggered when the search term is changed.
SearchResults should be stateful and the container component. The list component should respond to change in the search term by receiving the search term as props. generateList should be the functionality of componentWillReceiveProps of the list component.

Categories

Resources