Unexpected token in PropTypes (expected ",") - javascript

In the following code:
import React from 'react';
import {
Avatar, Menu, MenuItem, Tooltip, Box,
} from '#mui/material';
import PropTypes from 'prop-types';
// eslint-disable-next-line no-unused-vars
import { positions } from '#mui/system';
const menuItems = ['Test', 'Yes', 'No'];
function PositionedMenu(props) {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const { boxStyle: PropTypes.object} = props;
...
I am getting the following error in the specific position - PropTypes.object (where I validate boxStyles):
Parsing error: Unexpected token, expected ",".
Why is this happening? I'm using React with eslint set to airbnb options.

You can pass boxStyle parent to child component and use it.
// Parent component
function ParentComponent(props) {
render(
<childComponent boxStyle={{ background: 'red'}} />
)
}
// Child Component
function childComponent(props) {
const { boxStyle } = props;
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
}
childComponent.propTypes = {
boxStyle: PropTypes.object
};

You are destructuring on that line, if you want to set default value, you need to use = char and not :
const { boxStyle = PropTypes.object} = props;
And yes, this is not the way to set propTypes. Here's example from docs:
Class based:
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
Function based:
import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
export default HelloWorldComponent
Sometimes when setting up props we think that prop is only of type object. But due to a lot of factors (for example, how we fetch data and set props) a prop can be undefined for an initial period of time (very short) and then be defined as an object once the data is loaded.
To avoid you can either:
Change the way the data is supplied and therefore make sure that the prop is always an object
Allow in propTypes for both values: undefined (for initial moment) and then object as well once it loads. Here's how to specify multiple propTypes (without the use of default props):
// An object that could be one of many types
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),

Related

Pass component as a prop in Reactjs

I got an article showing how to pass a component as a prop, but I could not make it works, can anyone help me on it?
This is the example I got.
import React from "react";
import Foo from "./components/Foo";
import Bar from "./components/Bar";
const Components = {
foo: Foo,
bar: Bar
};
export default block => {
// component does exist
if (typeof Components[block.component] !== "undefined") {
return React.createElement(Components[block.component], {
key: block._uid,
block: block
});
}
}
And this is my code
I have one file called routes.js, that has the state called routes.
var routes = [
{
path: "/user-profile",
name: "Quem Somos",
icon: "FaIdBadge",
component: UserProfile,
layout: "/admin"
}
And another component called Sidebar, where I receive routes and need to change the icon based in what is configured at the 'routes' prop.
const Components = {
fa:FaIdBadge
}
<i>{prop => Components(prop.icon)}</i>
But the property with the icon is not recognized.
You're pretty close.
Choosing the Type as Runtime
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
So more specific to your example
// component map in the file for lookup
const components = {
fa: FaIdBadge,
}
...
// In the render function
// fetch the component, i.e. props.icon === 'fa'
const IconComponent = components[props.icon];
...
<IconComponent />

react native - assigning state to const

I'm starting with react native, and when using a library called react native paper, I've come across a statement where the state is being assigned to a const as shown below.
import * as React from 'react';
import { Searchbar } from 'react-native-paper';
export default class MyComponent extends React.Component {
state = {
firstQuery: '',
};
render() {
const { firstQuery } = this.state;
return (
<Searchbar
placeholder="Search"
onChangeText={query => { this.setState({ firstQuery: query }); }}
value={firstQuery}
/>
);
}
}
Beginning of the 'Render' method, you could see const { firstQuery } = this.state;
Could someone please explain why the state is being assigned to a const named 'firstQuery', and even if it have a reason, how will the assignment correctly map the property 'firstQuery' inside the state object to the const ?
Thanks in advance. The code sample is from https://callstack.github.io/react-native-paper/searchbar.html#value
That syntax is not React nor React Native. It's just Javascript's syntax, called destructuring.
const { firstQuery } = this.state;
is equivalent to
const firstQuery = this.state.firstQuery;
just a short-hand shortcut syntax, you see 2 firstQuerys? People just don't want duplication in code, so they invented it.
See the vanilla javascript snippet below:
const object = {
name: 'Aby',
age: 100,
}
const { name, age } = object;
// instead of
// const name = object.name;
console.log(name, age);
console.log(object.name, object.age);
//=========================================
// imagine:
const obj = {
veryLongPropertyNameToType: 420
}
const { veryLongPropertyNameToType } = obj;
// instead of
// const veryLongPropertyNameToType = obj.veryLongPropertyNameToType;
Like another answer mentioned, it's just JavaScript syntax aka destructuring. If you're feeling confused and wished to just use the "vanilla" JavaScript syntax, you can take a look at below.
import * as React from 'react';
import { Searchbar } from 'react-native-paper';
export default class MyComponent extends React.Component {
state = {
firstQuery: '',
};
render() {
return (
<Searchbar
placeholder="Search"
onChangeText={query => { this.setState({ firstQuery: query }); }}
value={this.state.firstQuery} // <<<<<<<<<<< LOOK HERE
/>
);
}
}

Rules of Hooks Where is the top level

I have the following component in React
import React from 'react'
import { Row, Col, Form, Input, Button, Card } from 'antd';
import { FormComponentProps } from 'antd/lib/form/Form';
import Particles from 'react-particles-js';
import { useTranslation } from "react-i18next";
import { connect } from 'react-redux';
import { RootState } from '../../services/store/rootReducer';
import { UsersActions } from '../../services/store';
interface LoginProps extends FormComponentProps {
rootState: RootState
}
class Login extends React.Component<LoginProps> {
state = { email: '', password: ''};
changeHandler = (e: any, name: any) => {
var value = e.target.value;
this.setState({[name]: value})
}
loginUser = () => {
try {
UsersActions.loginRequestAsync(this.state, (token: any) => {
console.log(token);
});
} catch(exception)
{
console.log(exception)
}
}
render() {
const { t } = useTranslation();
const { getFieldDecorator } = this.props.form;
return (
<div>
///blabla
</div>
)
}
}
const mapStateToProps = (state: RootState) => ({
rootState: state
});
const mapDispatchToProps = {}
const createdForm = Form.create()(Login);
export default connect(
mapStateToProps,
mapDispatchToProps
)(createdForm);
When I add the line
const { t } = useTranslation();
The app do not compile with
×
Invalid hook call. Hooks can only be called inside of the body of a
function component. This could happen for one of the following
reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
fix this problem.
Now, I tried to understand the rule, hooks must be called only on top level of a component in order for react to always load the component the same way. But where is my top level ?
I tried to put outside of render and as a property of the component, I still have the same loading error.
You broke the rules of Hooks, namely: No Hooks in classes.
That should really be the trick here. Try to rewrite it to something like the following:
function Login(props: LoginProps) {
const [data, setData] = useState({ email: '', password: '' });
const { t } = useTranslation();
const loginUser = () => { /* ... */ };
render() {
return <div>
{/*...*/ }
</div>
}
}
On the document pages, there is even a page only on Hook Errors/Warnings: Invalid Hook Call Warning
In Breaking the Rules of Hooks it states:
🔴 Do not call Hooks in class components.
🔴 Do not call in event handlers.
🔴 Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect.
Hooks are used in functional components, here you have a class component that's why it's throwing an error here, error is saying it
Hooks can only be called inside of the body of a function component.
Hope it helps

React: TypeError: _useContext is undefined

I'm trying my hand at TypeScript and React. I have a functional component (code below) that is supposed to consume a context with useContext, but it is showing me this weird error that I cannot find a solution to.
If I do not use TS, and go with JSX, it works just fine.
Edit: Screenshot>
Code:
AppProvider.tsx
import React, { useState, useEffect } from "react";
// Application's context (for general application-wide usage)
const AppContext: any = React.createContext(null);
// this will be used below in the componet we will export
export const AppContextProvider = AppContext.Provider;
export const AppProvider: React.FC = (props: any) => {
const [appName, setAppName] = useState("Blood Donation");
const [appUser, setAppUser]: any = useState(null);
const [appInfoBusy, setAppInfoBusy] = useState(false); // working to get or set data
useEffect(() => {
getAppInfo();
}, []);
const getAppInfo = () => {
setTimeout(() => {
setAppName("Test");
setAppUser({
name: "Admin",
email: "test#test.com",
role_id: 100
});
}, 3000);
};
return (
<AppContextProvider
value={{
appName: appName,
appInfoBusy: appInfoBusy,
appUser: appUser
}}
>
{props.children}
</AppContextProvider>
);
};
Consumer: Login.tsx
import React, { useState, useEffect, useContext } from "react";
import {
Button,
Card,
Elevation,
FormGroup,
InputGroup,
Drawer,
Classes,
H4,
Callout,
H5
} from "#blueprintjs/core";
//#ts-ignore
import ReCAPTCHA from "react-google-recaptcha";
import logo from "../../assets/images/logo.png";
import "../../scss/Login.scss";
import { RecaptchaKey } from "../../shared/Info";
import { AppContextProvider } from "../../shared/context/AppProvider";
const Login: React.FC = props => {
const [email, setEmail]: React.ComponentState = useState();
const [password, setPassword]: any = useState();
const [isOpen, setIsOpen]: any = useState();
const [resetEmail, setResetEmail]: any = useState();
const [emailSent, setEmailSent]: any = useState();
const [captchaOk, setCaptchaOk]: any = useState(false);
const [working, setWorking]: any = useState(false);
// context
const { appName, appUser, appInfoBusy } = useContext(AppContextProvider);
/**
* Handles lifecycle hooks
*/
useEffect(() => {
// when component is mounted
}, []);
/**
* Handles Captcha change
* #param value
*/
const recaptchaChange = (value: any) => {
setCaptchaOk(value ? true : false);
};
const handleRecoverySubmit = () => {
setWorking(true);
setTimeout(() => {
setEmailSent(true);
setWorking(false);
}, 3000);
};
return (
<div id="loginPage">
... removed for brevity ...
</div>
);
};
export default Login;
Any help is gratefully thanked. React and dependencies are all latest as of date.
I was using the context provider instead of the context itself inside useContext(), I should have used useContext(AppContext) instead.
Commentary removed because stackoverflow.
The error is _useContext not defined. The issue is different than what it is actually referring to.
you created a context called as AppContext
and then you export this as
export const AppContextProvider = AppContext.Provider;
You have done correct till this stage.
The problem lies at consumer part i.e. login.tsx file.
you are importing a name file inside a curly braces which is not correct, because the context is exported as a name variable. You simply need to write
import AppContextProvider from "../../shared/context/AppProvider";
That's it, and when you are calling this context using useContext hooks, then the actual state that you are looking for get accessed and no issue will further persist.
Note: Don't use {} for importing named exports.
reference: When should I use curly braces for ES6 import?

How to pass in an instance variable from a React component to its HOC?

I typically use component composition to reuse logic the React way. For example, here is a simplified version on how I would add interaction logic to a component. In this case I would make CanvasElement selectable:
CanvasElement.js
import React, { Component } from 'react'
import Selectable from './Selectable'
import './CanvasElement.css'
export default class CanvasElement extends Component {
constructor(props) {
super(props)
this.state = {
selected: false
}
this.interactionElRef = React.createRef()
}
onSelected = (selected) => {
this.setState({ selected})
}
render() {
return (
<Selectable
iElRef={this.interactionElRef}
onSelected={this.onSelected}>
<div ref={this.interactionElRef} className={'canvas-element ' + (this.state.selected ? 'selected' : '')}>
Select me
</div>
</Selectable>
)
}
}
Selectable.js
import { Component } from 'react'
import PropTypes from 'prop-types'
export default class Selectable extends Component {
static propTypes = {
iElRef: PropTypes.shape({
current: PropTypes.instanceOf(Element)
}).isRequired,
onSelected: PropTypes.func.isRequired
}
constructor(props) {
super(props)
this.state = {
selected: false
}
}
onClick = (e) => {
const selected = !this.state.selected
this.setState({ selected })
this.props.onSelected(selected)
}
componentDidMount() {
this.props.iElRef.current.addEventListener('click', this.onClick)
}
componentWillUnmount() {
this.props.iElRef.current.removeEventListener('click', this.onClick)
}
render() {
return this.props.children
}
}
Works well enough. The Selectable wrapper does not need to create a new div because its parent provides it with a reference to another element that is to become selectable.
However, I've been recommended on numerous occasions to stop using such Wrapper composition and instead achieve reusability through Higher Order Components. Willing to experiment with HoCs, I gave it a try but did not come further than this:
CanvasElement.js
import React, { Component } from 'react'
import Selectable from '../enhancers/Selectable'
import flow from 'lodash.flow'
import './CanvasElement.css'
class CanvasElement extends Component {
constructor(props) {
super(props)
this.interactionElRef = React.createRef()
}
render() {
return (
<div ref={this.interactionElRef}>
Select me
</div>
)
}
}
export default flow(
Selectable()
)(CanvasElement)
Selectable.js
import React, { Component } from 'react'
export default function makeSelectable() {
return function decorateComponent(WrappedComponent) {
return class Selectable extends Component {
componentDidMount() {
// attach to interaction element reference here
}
render() {
return (
<WrappedComponent {...this.props} />
)
}
}
}
}
The problem is that there appears to be no obvious way to connect the enhanced component's reference (an instance variable) to the higher order component (the enhancer).
How would I "pass in" the instance variable (the interactionElRef) from the CanvasElement to its HOC?
I came up with a different strategy. It acts roughly like the Redux connect function, providing props that the wrapped component isn't responsible for creating, but the child is responsible for using them as they see fit:
CanvasElement.js
import React, { Component } from "react";
import makeSelectable from "./Selectable";
class CanvasElement extends Component {
constructor(props) {
super(props);
}
render() {
const { onClick, selected } = this.props;
return <div onClick={onClick}>{`Selected: ${selected}`}</div>;
}
}
CanvasElement.propTypes = {
onClick: PropTypes.func,
selected: PropTypes.bool,
};
CanvasElement.defaultProps = {
onClick: () => {},
selected: false,
};
export default makeSelectable()(CanvasElement);
Selectable.js
import React, { Component } from "react";
export default makeSelectable = () => WrappedComponent => {
const selectableFactory = React.createFactory(WrappedComponent);
return class Selectable extends Component {
state = {
isSelected: false
};
handleClick = () => {
this.setState({
isSelected: !this.state.isSelected
});
};
render() {
return selectableFactory({
...this.props,
onClick: this.handleClick,
selected: this.state.isSelected
});
}
}
};
https://codesandbox.io/s/7zwwxw5y41
I know that doesn't answer your question. I think you're trying to let the child get away without any knowledge of the parent.
The ref route feels wrong, though. I like the idea of connecting the tools to the child. You can respond to the click in either one.
Let me know what you think.
Just as you did on DOM element for CanvasElement, Ref can be attached to class component as well, checkout the doc for Adding a Ref to a Class Component
export default function makeSelectable() {
return function decorateComponent(WrappedComponent) {
return class Selectable extends Component {
canvasElement = React.createRef()
componentDidMount() {
// attach to interaction element reference here
console.log(this.canvasElement.current.interactionElRef)
}
render() {
return (
<WrappedComponent ref={this.canvasElement} {...this.props} />
)
}
}
}
}
Also, do checkout Ref forwarding if you need child instance reference in ancestors that's multiple levels higher in the render tree. All those solutions are based on assumptions that you're on react 16.3+.
Some caveats:
In rare cases, you might want to have access to a child’s DOM node from a parent component. This is generally not recommended because it breaks component encapsulation, but it can occasionally be useful for triggering focus or measuring the size or position of a child DOM node.
While you could add a ref to the child component, this is not an ideal solution, as you would only get a component instance rather than a DOM node. Additionally, this wouldn’t work with functional components. https://reactjs.org/docs/forwarding-refs.html
I've now come up with an opinionated solution where the HoC injects two callback functions into the enhanced component, one to register the dom reference and another to register a callback that is called when an element is selected or deselected:
makeElementSelectable.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import movementIsStationary from '../lib/movement-is-stationary';
/*
This enhancer injects the following props into your component:
- setInteractableRef(node) - a function to register a React reference to the DOM element that should become selectable
- registerOnToggleSelected(cb(bool)) - a function to register a callback that should be called once the element is selected or deselected
*/
export default function makeElementSelectable() {
return function decorateComponent(WrappedComponent) {
return class Selectable extends Component {
static propTypes = {
selectable: PropTypes.bool.isRequired,
selected: PropTypes.bool
}
eventsAdded = false
state = {
selected: this.props.selected || false,
lastDownX: null,
lastDownY: null
}
setInteractableRef = (ref) => {
this.ref = ref
if (!this.eventsAdded && this.ref.current) {
this.addEventListeners(this.ref.current)
}
// other HoCs may set interactable references too
this.props.setInteractableRef && this.props.setInteractableRef(ref)
}
registerOnToggleSelected = (cb) => {
this.onToggleSelected = cb
}
componentDidMount() {
if (!this.eventsAdded && this.ref && this.ref.current) {
this.addEventListeners(this.ref.current)
}
}
componentWillUnmount() {
if (this.eventsAdded && this.ref && this.ref.current) {
this.removeEventListeners(this.ref.current)
}
}
/*
keep track of where the mouse was last pressed down
*/
onMouseDown = (e) => {
const lastDownX = e.clientX
const lastDownY = e.clientY
this.setState({
lastDownX, lastDownY
})
}
/*
toggle selected if there was a stationary click
only consider clicks on the exact element we are making interactable
*/
onClick = (e) => {
if (
this.props.selectable
&& e.target === this.ref.current
&& movementIsStationary(this.state.lastDownX, this.state.lastDownY, e.clientX, e.clientY)
) {
const selected = !this.state.selected
this.onToggleSelected && this.onToggleSelected(selected, e)
this.setState({ selected })
}
}
addEventListeners = (node) => {
node.addEventListener('click', this.onClick)
node.addEventListener('mousedown', this.onMouseDown)
this.eventsAdded = true
}
removeEventListeners = (node) => {
node.removeEventListener('click', this.onClick)
node.removeEventListener('mousedown', this.onMouseDown)
this.eventsAdded = false
}
render() {
return (
<WrappedComponent
{...this.props}
setInteractableRef={this.setInteractableRef}
registerOnToggleSelected={this.registerOnToggleSelected} />
)
}
}
}
}
CanvasElement.js
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import PropTypes from 'prop-types'
import flowRight from 'lodash.flowright'
import { moveSelectedElements } from '../actions/canvas'
import makeElementSelectable from '../enhancers/makeElementSelectable'
class CanvasElement extends PureComponent {
static propTypes = {
setInteractableRef: PropTypes.func.isRequired,
registerOnToggleSelected: PropTypes.func
}
interactionRef = React.createRef()
componentDidMount() {
this.props.setInteractableRef(this.interactionRef)
this.props.registerOnToggleSelected(this.onToggleSelected)
}
onToggleSelected = async (selected) => {
await this.props.selectElement(this.props.id, selected)
}
render() {
return (
<div ref={this.interactionRef}>
Select me
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
const {
canvas: {
selectedElements
}
} = state
const selected = !!selectedElements[ownProps.id]
return {
selected
}
}
const mapDispatchToProps = dispatch => ({
selectElement: bindActionCreators(selectElement, dispatch)
})
const ComposedCanvasElement = flowRight(
connect(mapStateToProps, mapDispatchToProps),
makeElementSelectable()
)(CanvasElement)
export default ComposedCanvasElement
This works, but I can think of at least one significant issue: the HoC injects 2 props into the enhanced component; but the enhanced component has no way of declaratively defining which props are injected and just needs to "trust" that these props are magically available
Would appreciate feedback / thoughts on this approach. Perhaps there is a better way, e.g. by passing in a "mapProps" object to makeElementSelectable to explicitly define which props are being injected?

Categories