Learning React: how to useRef in custom component? - javascript

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}
/>
)
}

Related

Using handleChange inside a default function instead of a class

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

Problem when pass data in ReactJS with props and state

I have the problem when I try to pass the props through the function component .In parent component I have a state of currentRow with return an array with object inside, and I pass it to child component. It return a new object with an array inside it. What can I do to avoid it and receive exact my currentRow array.
there is example of the way I do it
Parent component
import React, { useState } from "react";
import ToolBar from "./Toolbar";
function Manage() {
const [currentRow, setCurrentRow] = useState();
console.log("from manage", currentRow);
return (
<div>
<ToolBar currentRow={currentRow} />
</div>
);
}
export default Manage;
Child Componet
import React from 'react'
function ToolBar(currentRow) {
console.log("from toolbar", currentRow);
return(
<div></div>
);
}
export default ToolBar
And this is my Log
enter image description here
Try accessing it like below:
import React from 'react'
function ToolBar({currentRow}) {
console.log("from toolbar", currentRow);
return(
<div></div>
);
}
export default ToolBar
A React component's props is always an object. The reason for this is that otherwise it would be impossible to access the properties of a component which received multiple props.
example:
<SomeComponent prop1={prop1} prop2={prop2} />
---
const SomeComponent = (props) => {
console.log(props.prop1);
console.log(props.prop2);
}
So in order to resolve your issue, you could destructure the props object in your ToolBar component like this:
const ToolBar = ({ currentRows }) => {
...
}
Just keep in mind that a component will always receive its props as an object. There is no way to change that as of right now.

How to display JSX returned from other function inside return?

I'm trying to build a weather application using openweathermap api. As you can see below in the code, I use a boolean state to check when the form is summitted so I can pass that value to the <Result> component(which I checked with hard code and it works). I want the function changeCity(in the App) to return the <Result> component with the value of city passed and in the same time to change the cityEmpty state. But there I got the problem when I pass that in the return() {(cityEmpty) ? changeCity() : null}
import React, {useState} from 'react';
import Result from "./components/Result";
import Search from "./components/Search";
import './App.css';
function App() {
const [city, setCity] = useState ("");
const [cityEmpty, setCityEmpty] = useState(false);
const changeCity = () => {
setCityEmpty(false);
return (<Result city={city}/>);
}
return (
<div className="App">
<Search city={city} setCity={setCity} cityEmpty={cityEmpty} setCityEmpty={setCityEmpty}
/>
{(cityEmpty) ? changeCity() : null}
</div>
);
}
export default App;
import React from "react"
function Search({city, setCity, cityEmpty, setCityEmpty}){
const handleInputChange = (e) => {
setCity(e.target.value);
}
const handleSumbit = (e) => {
e.preventDefault();
console.log(cityEmpty);
setCityEmpty(true);
console.log(cityEmpty);
setCity("");
}
return(
<div>
<form onSubmit={handleSumbit}>
<input
type="text"
placeholder="Insert city"
value={city}
onChange = {handleInputChange}
>
</input>
</form>
</div>
);
}
export default Search
You can't call a state update inside the render of a component. Remember that whenever state is updated, it will re render the component. This will cause an infinite loop in your component.
Component renders
changeCity is called
Inside changeCity, setCityEmpty is called
Go back to step 1 for rendering.
Instead, consider checking if city is empty in your handleInputChange and calling setCityEmpty inside that function.
EDIT: To clarify a function that returns a component is completely fine, this is all components are really. Functions (or in previous react versions: classes) returning other components.
you don't have to return JSX from function. In your case it is pretty straightforward to use.
import React, {useState} from 'react';
import Result from "./components/Result";
import Search from "./components/Search";
import './App.css';
function App() {
const [city, setCity] = useState ("");
const [cityEmpty, setCityEmpty] = useState(false);
return (
<div className="App">
<Search city={city} setCity={setCity} cityEmpty={cityEmpty} setCityEmpty={setCityEmpty}
/>
{cityEmpty && <Result city={city}/>}
</div>
);
}
export default App;
import React from "react"
function Search({city, setCity, cityEmpty, setCityEmpty}){
const handleInputChange = (e) => {
setCity(e.target.value);
}
const handleSumbit = (e) => {
e.preventDefault();
console.log(cityEmpty);
setCityEmpty(true);
console.log(cityEmpty);
setCity("");
}
return(
<div>
<form onSubmit={handleSumbit}>
<input
type="text"
placeholder="Insert city"
value={city}
onChange = {handleInputChange}
>
</input>
</form>
</div>
);
}
export default Search
Not sure what the problem you're seeing is but I noticed you're setting state inside of a render function, which is a bad pattern. Any state changes will trigger a re-rendering of the component and if you set state within a render function then you'd have an infinite loop of re-renderings (but.
Try removing setCityEmpty(false) in changeCity.
const changeCity = () => {
return (<Result city={city}/>);
}
So how would you update cityEmpty? It's not clear what the end goal is here. With more info, we can find a better implementation.

Testing react component's number of children

I am writing a test in React for the first time, and I am wondering is there a way to test the number of children components of a children component inside the parent component, so to make it clear, this is how to component looks like, I have cut it down to the parts that are relevant to this question:
<Modal>
<RadioGroupField direction="vertical" name={`${fieldId}.resultat`} bredde="M">
<RadioOption label={{ id: 'UttakInfoPanel.SykdomSkadenDokumentertAngiAvklartPeriode' }} value />
<RadioOption label={{ id: 'UttakInfoPanel.SykdomSkadenIkkeDokumentert' }} value={false} />
</RadioGroupField>
</Modal>
So, when I am writing a test for the Modal component I would like to check if there is a correct number of children components of the RadioGroupField component:
import React from 'react';
import { expect } from 'chai';
import { shallowWithIntl } from 'testHelpers/intl-enzyme-test-helper';
import { Modal} from './Modal';
const periode = {
fom: '2018-01-01',
tom: '2018-03-01',
};
it('should show modal component', () => {
const wrapper = shallowWithIntl(<Modal
fieldId="periode[0]"
resultat={undefined}
periode={periode}
/>);
const radioGroupField = wrapper.find('RadioGroupField');
expect(radioGroupField).to.have.length(1);
});
How can I write that test?
Update
I have tried with using name and not string for the component and dive method, but all of a sudden I get an error:
ReferenceError: RadioGroupField is not defined
This is my test:
it('skal vise sykdom og skade periode', () => {
const wrapper = shallowWithIntl(<Modal
fieldId="periode[0]"
resultat={undefined}
periode={periode}
/>);
const textAreaField = wrapper.find('TextAreaField');
const undertekst = wrapper.find('Undertekst');
const radioGroupField = wrapper.find('RadioGroupField');
const fieldArray = wrapper.find('FieldArray');
const hovedknapp = wrapper.find('Hovedknapp');
const knapp = wrapper.find('Knapp');
const radioGroupFieldComponent = wrapper.find(RadioGroupField).dive();
expect(radioGroupFieldComponent.children()).to.have.length(2);
expect(textAreaField).to.have.length(1);
expect(undertekst).to.have.length(1);
expect(radioGroupField).to.have.length(1);
expect(fieldArray).to.have.length(1);
expect(hovedknapp).to.have.length(1);
expect(knapp).to.have.length(1);
});
In order to refer to components, use their name not a String as documented:
wrapper.find(Foo) // Foo component, not 'Foo'
I would like to check if there is a correct number of children components of the RadioGroupField component:
Use children() as documented in the enzyme API
const radioGroupField = wrapper.find(RadioGroupField).dive()
expect(radioGroupField.children()).to.have.length(2)
References:
enzyme find() api
enzyme children() api
EDIT:
add dive() as this may be needed to render components (non-DOM nodes)
Update
I have tried with using name and not string for the component and dive
method, but all of a sudden I get an error:
ReferenceError: RadioGroupField is not defined
Any components used in your tests need to be imported.
Here's an example:
Foo.js:
import React from 'react'
export const Bar = (props) => {
return (
<div>
{props.children}
</div>
)
}
export const Baz = () => (
<div>Foo</div>
)
export const Foo = () => (
<Bar>
<Baz />
<Baz />
</Bar>
)
export default Foo
Foo.test.js
import React from 'react'
import { shallow } from 'enzyme'
import Foo, { Bar, Baz } from './Foo'
it('Foos', () => {
let wrapper = shallow(<Foo />)
expect(wrapper.find(Bar)).toHaveLength(1) // jest syntax
})
This also works:
const radioOption = wrapper.find(RadioGroupField).dive().find(RadioOption);

Functions are not valid as a React child. This may happen if you return a Component instead of from render

I have written a Higher Order Component:
import React from 'react';
const NewHOC = (PassedComponent) => {
return class extends React.Component {
render(){
return (
<div>
<PassedComponent {...this.props}/>
</div>
)
}
}
}
export default NewHOC;
I am using the above in my App.js:
import React from 'react';
import Movie from './movie/Movie';
import MyHOC from './hoc/MyHOC';
import NewHOC from './hoc/NewHOC';
export default class App extends React.Component {
render() {
return (
<div>
Hello From React!!
<NewHOC>
<Movie name="Blade Runner"></Movie>
</NewHOC>
</div>
);
}
}
But, the warning I am getting is:
Warning: Functions are not valid as a React child. This may happen if
you return a Component instead of <Component /> from render. Or maybe
you meant to call this function rather than return it.
in NewHOC (created by App)
in div (created by App)
in App
The Movie.js file is:
import React from "react";
export default class Movie extends React.Component{
render() {
return <div>
Hello from Movie {this.props.name}
{this.props.children}</div>
}
}
What am I doing wrong?
I did encounter this error too because I didn't use the correct snytax at routing. This was in my App.js under the <Routes> section:
False:
<Route path="/movies/list" exact element={ MoviesList } />
Correct:
<Route path="/movies/list" exact element={ <MoviesList/> } />
So now the MoviesList is recognized as a component.
You are using it as a regular component, but it's actually a function that returns a component.
Try doing something like this:
const NewComponent = NewHOC(Movie)
And you will use it like this:
<NewComponent someProp="someValue" />
Here is a running example:
const NewHOC = (PassedComponent) => {
return class extends React.Component {
render() {
return (
<div>
<PassedComponent {...this.props} />
</div>
)
}
}
}
const Movie = ({name}) => <div>{name}</div>
const NewComponent = NewHOC(Movie);
function App() {
return (
<div>
<NewComponent name="Kill Bill" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"/>
So basically NewHOC is just a function that accepts a component and returns a new component that renders the component passed in. We usually use this pattern to enhance components and share logic or data.
You can read about HOCS in the docs and I also recommend reading about the difference between react elements and components
I wrote an article about the different ways and patterns of sharing logic in react.
In my case i forgot to add the () after the function name inside the render function of a react component
public render() {
let ctrl = (
<>
<div className="aaa">
{this.renderView}
</div>
</>
);
return ctrl;
};
private renderView() : JSX.Element {
// some html
};
Changing the render method, as it states in the error message to
<div className="aaa">
{this.renderView()}
</div>
fixed the problem
I encountered this error while following the instructions here: https://reactjs.org/docs/add-react-to-a-website.html
Here is what I had:
ReactDOM.render(Header, headerContainer);
It should be:
ReactDOM.render(React.createElement(Header), headerContainer);
I had this error too. The problem was how to call the function.
Wrong Code:
const Component = () => {
const id = ({match}) => <h2>Test1: {match.params.id}</h2>
return <h1>{id}</h1>;
};
Whereas id is a function, So:
Correct code:
return <h1>{id()}</h1>;
Adding to sagiv's answer, we should create the parent component in such a way that it can consist all children components rather than returning the child components in the way you were trying to return.
Try to intentiate the parent component and pass the props inside it so that all children can use it like below
const NewComponent = NewHOC(Movie);
Here NewHOC is the parent component and all its child are going to use movie as props.
But any way, you guyd6 have solved a problem for new react developers as this might be a problem that can come too and here is where they can find the solution for that.
I was able to resolve this by using my calling my high order component before exporting the class component. My problem was specifically using react-i18next and its withTranslation method, but here was the solution:
export default withTranslation()(Header);
And then I was able to call the class Component as originally I had hoped:
<Header someProp={someValue} />
it also happens when you call a function from jsx directly rather than in an event. like
it will show the error if you write like
<h1>{this.myFunc}<h2>
it will go if you write:
<h1 onClick={this.myFunc}>Hit Me</h1>
I was getting this from webpack lazy loading like this
import Loader from 'some-loader-component';
const WishlistPageComponent = loadable(() => import(/* webpackChunkName: 'WishlistPage' */'../components/WishlistView/WishlistPage'), {
fallback: Loader, // warning
});
render() {
return <WishlistPageComponent />;
}
// changed to this then it's suddenly fine
const WishlistPageComponent = loadable(() => import(/* webpackChunkName: 'WishlistPage' */'../components/WishlistView/WishlistPage'), {
fallback: '', // all good
});
In my case, I was transport class component from parent and use it inside as a prop var, using typescript and Formik, and run well like this:
Parent 1
import Parent2 from './../components/Parent2/parent2'
import Parent3 from './../components/Parent3/parent3'
export default class Parent1 extends React.Component {
render(){
<React.Fragment>
<Parent2 componentToFormik={Parent3} />
</React.Fragment>
}
}
Parent 2
export default class Parent2 extends React.Component{
render(){
const { componentToFormik } = this.props
return(
<Formik
render={(formikProps) => {
return(
<React.fragment>
{(new componentToFormik(formikProps)).render()}
</React.fragment>
)
}}
/>
)
}
}
What would be wrong with doing;
<div className="" key={index}>
{i.title}
</div>
[/*Use IIFE */]
{(function () {
if (child.children && child.children.length !== 0) {
let menu = createMenu(child.children);
console.log("nested menu", menu);
return menu;
}
})()}
In my case I forgot to remove this part '() =>'. Stupid ctrl+c+v mistake.
const Account = () => ({ name }) => {
So it should be like this:
const Account = ({ name }) => {
In my case
<Link key={uuid()} to="#" className="tag">
{post.department_name.toString}
</Link>
changed with
<Link key={uuid()} to="#" className="tag">
{post.department_name.toString()}
</Link>
You should use
const FunctionName = function (){
return (
`<div>
hello world
<div/>
`
)
};
if you use Es6 shorthand function it will give error use regular old javascript function.

Categories

Resources