I'm using a react hook component with antd. When setting up columns for a table, the render function is giving me an ESLint error:
ESLint: Component definition is missing displayName
(react/display-name)
I've tried adding displayName to the object but this doesn't work.
This is how the error looks:
This is the code:
const columns_payment_summary_table = [
{
title: FooConstants.LABEL_QUANTITY_SELECTED,
dataIndex: 'group',
key: 'group',
render: text => (
<span>{getCountForCountry(text)}</span>
),
}
]
Can anyone help?
Here is full component code (well just the relevant bits)
import * as FooConstants from './constants'
import {connect} from 'react-redux'
import React, {useState, useEffect} from 'react'
import {Card, Table} from 'antd'
import PropTypes from 'prop-types'
const propTypes = {
foos: PropTypes.object.isRequired,
}
function Foos(props) {
const [selectedFooRows, setSelectedFooRows] = useState([])
useEffect(() => {
getFooDetails()
}, [])
function getFooDetails() {
props.dispatch({
type: FooConstants.GET_FOO_PAYMENT_SUMMARIES,
params: {
'group_by': 'country_code',
'type': FooConstants.CLAIM_FOO,
}
})
props.dispatch({
type: FooConstants.GET_FOO_PAYMENTS,
params: {'type': FooConstants.CLAIM_FOO, }
})
}
const columns_payment_summary_table = [
{
title: FooConstants.LABEL_QUANTITY_SELECTED,
dataIndex: 'group',
key: 'group',
render: text => (
<span>{getCountForCountry(text)}</span>
),
}
]
function getCountForCountry(country_code){
let selected_country = selectedFooRows.filter(function(row){
return row.group === country_code
})
if(selected_country && selected_country.length > 0){
return selected_country[0].ids.length
} else {
return 0
}
}
return (
<div>
<Card
title={FooConstants.LABEL_FOO_SUMMARY}>
<Table
columns={columns_payment_summary_table}
bordered={true}
dataSource={props.foos.foo_payment_summaries}
loading={props.foos.foo_payment_summaries_pending && !props.foos.foo_payment_summaries}
rowKey={record => record.group}
/>
</Card>
</div>
)
}
Foos.propTypes = propTypes
const mapStateToProps = (state) => {
return {foos: state.foosReducer}
}
export default connect(
mapStateToProps,
)(Foos)
ESLint thinks you are defining a new component without setting any name to it.
This is explained because ESLint cannot recognize the render prop pattern because you are not directly writing this render prop into a component, but into an object.
You can either put the render prop directly into your jsx implementation of the <Column> component,
const columns_payment_summary_table_render = text => (<span>{getCountForCountry(text)}</span>);
const columns_payment_summary_table = [{
title: SettlementConstants.LABEL_QUANTITY_SELECTED,
dataIndex: "group",
key: "group",
// eslint-disable-next-line react/display-name
render: columns_payment_summary_table_render
}];
or shut down the ESLint's error by doing this :
const columns_payment_summary_table = [
{
title: SettlementConstants.LABEL_QUANTITY_SELECTED,
dataIndex: 'group',
key: 'group',
// eslint-disable-next-line react/display-name
render: text => (
<span>{getCountForCountry(text)}</span>
),
}
]
I hope it helped ;)
If anyone needs to avoid this in all the files, add below to the rules section of .eslintrc.js file,
{
...
"rules": {
"react/display-name": "off"
}
}
Using a normal function for the render key will also remove the ESLint warning without any need for disabling the warning.
const columns_payment_summary_table = [
{
title: SettlementConstants.LABEL_QUANTITY_SELECTED,
dataIndex: 'group',
key: 'group',
render: function countForCountry(text) {
return <span>{getCountForCountry(text)}</span>
},
}
]
Sometimes we can bypass rules if we have an error in only one or two places. What if we have the same use case in multiple places. Each time We have to disable rules.
Instead, we can bypass this error by assigning function to render property.
const getMyHTML = (text) => <span>{getCountForCountry(text)}</span>
const columns_payment_summary_table = [
{
title: SettlementConstants.LABEL_QUANTITY_SELECTED,
dataIndex: 'group',
key: 'group',
render: getMyHTML,
}
]
Using anonymous functions and arrow functions will keep ESLint: Component definition is missing displayName (react/display-name) coming out, I guess this is because that when we are rendering a component through a function, we are giving them a displayName by naming the render function as well.
But using the normal function isn't enough, you might still meet the following warning:
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.
You have to execute the normal function just right after render it. I passed the two rules by using the following code.
render: () => {
return (function Actions() {
return (
<Button>
View
</Button>
);
})();
},
This is covered pretty thoroughly in this ESLint issue.
As Loïc suggested, suppressing the lint error is the simplest option here.
However, the lint error exists for a reason -- displayName can be helpful for debugging, particularly in React DevTools' Component view. The easiest way to assign your function a displayName is to use a named function declaration, and hoist the definition:
function renderTable(text) {
return (<span>{getCountForCountry(text)}</span>);
}
const columns_payment_summary_table = [
{
title: SettlementConstants.LABEL_QUANTITY_SELECTED,
dataIndex: 'group',
key: 'group',
render: text => (
<span>{getCountForCountry(text)}</span>
),
}
]
That's obviously more verbose, though, and if you don't anticipate needing to find the rendered component by name in DevTools, it's simplest to just suppress the error.
Related
Having the following component:
import { useMemo, useState, useEffect, useCallback } from 'react';
import { CellProps } from 'react-table';
import {
useDataTable,
DataTable,
useOnRowClick,
useRowHighlight,
IconMenu
} from '../ui-components';
import { useGetTriggers } from '../../hooks';
import { ICalculationEngine } from '../../types';
export function MyComponent() {
const [selectedIndex, setSelectedIndex] = useState<null | number>(null);
const { data } = useGetTriggers();
const [isEditMode, setEditMode] = useState(false);
const [tableData, setTableData] = useState(data);
useEffect(() => {
setSelectedIndex(selectedIndex);
}, [selectedIndex, setSelectedIndex]);
const onRowClick = (triggerIndex: number) => {
setEditMode(false);
setSelectedIndex(triggerIndex);
};
const testFun = useCallback(() => {
setEditMode(!isEditMode);
}, [isEditMode]);
console.log('isEditMode! ', isEditMode);
const tableState = useDataTable({
columns: useMemo(
() => [
{
Header: 'Name',
accessor: 'name'
},
{
id: '1',
Cell: ({ cell }: CellProps<ICalculationEngine['Trigger']>) => {
if (cell.row.index === selectedIndex) {
return (
<IconMenu
items={[
{
title: 'edit',
onClick: testFun
}
]}
/>
);
}
return <div></div>;
}
}
],
[selectedIndex, testFun]
),
data: tableData,
options: {
onRowClick: onRowClick
},
plugins: [useOnRowClick, useRowHighlight]
});
useEffect(() => setTableData(data), [data]);
return <DataTable dataTableInstance={tableState} />;
}
export default MyComponent;
It is building a table with some data in DataTable which receives as props the content of the table, the columns: tableState. It only has 2 columns, one called Name and a clickable one.
When that one is clicked it must change that state of isEditMode from false to true.
There is a function called testFun which must to that but it doesn't work, the state isn't updating.
If the state is set to true by other actions, when the user clicks another row however, the state updates to false, in onRowClick function.
What is wrong with testFun that it doesn't update it? It is because of useCallback wrapping it? If I remove the wrapper, VS Code will say that:
The 'testFun' function makes the dependencies of useMemo Hook (at line
66) change on every render. Move it inside the useMemo callback.
Alternatively, wrap the definition of 'testFun' in its own
useCallback() Hook
and still it doesn't work, it doesn't update the state.
I have a product page which consists of a searchbar component and a grid component containing the ag-grid import and setup.
Simplified the product page looks something like this:
// Dynamic import for client side rendering
const ProductGrid = dynamic(() => import('../modules/products/gridComponent'), {
ssr: false,
loading: () => <p>...</p>
})
const Searchbar = dynamic(() => import('../modules/common/Searchbar'), {
ssr: false,
loading: () => <p>...</p>
})
const Products = () => {
// State
const containerStyle = useMemo(() => ({ width: '100%', height: '100%' }), []);
const [searchQuery, setSearchQuery] = useState('');
const [searchLimit, setSearchLimit] = useState(5);
// Custom hook to fetch from API (has useEffect with searchQuery and searchLimit as dependencies)
const [ rowData, gridRef, error ] = useDolibarrProducts(searchQuery, searchLimit);
const updateProducts = useCallback((event) => {
// Updates product list
event.preventDefault();
const query = event.target.elements.search.value;
setSearchQuery(query);
setSearchLimit(100);
}, []);
return (
<Fragment>
<Searchbar handleSubmit={(event) => updateProducts(event)} />
<div style={containerStyle} className="ag-theme-alpine-dark">
<ProductGrid gridRef={gridRef} rowData={rowData} />
</div>
</Fragment>
);
}
export default Products;
And the grid component looks like this:
import {AgGridReact} from "ag-grid-react";
import dynamic from "next/dynamic";
// I'm guessing using dynamic to import loadingComponent (a simple custom loading screen) serves no purpose here since gridComponent is already imported dynamically, but I've tried every scenario just in case
const loadingComponent = dynamic( () => import('./loadingComponent'), {
ssr: false,
loading: () => <p>...</p>
})
const GridComponent = (props) => {
const defaultColDef = useMemo(() => {
return {
flex: 1,
// make every column use 'text' filter by default
filter: 'agTextColumnFilter',
// enable floating filters by default
floatingFilter: true,
// make columns resizable
resizable: true,
// enable sorting
sortable: true,
};
}, []);
const [columnDefs] = useState([
{ field: "label" },
{ field: "ref", headerName: "Reference" },
{
field: "price",
headerName: "Price (€)",
filter: 'agNumberColumnFilter',
filterParams: floatFilter,
valueFormatter: priceFormatter,
},
{
field: "stock",
filter: 'agNumberColumnFilter',
cellRenderer: stockRenderer
},
{
field: "action",
cellRenderer: actionRender,
floatingFilter: false,
sortable: false,
filter: false,
}
]);
const loadingOverlayComponent = useMemo(() => {
return loadingComponent;
}, []);
return (
<Fragment>
<AgGridReact
ref={props.gridRef}
rowData={props.rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
loadingOverlayComponent={loadingOverlayComponent}
/>
</Fragment>
);
};
export default GridComponent;
Problem:
When using this same structure without next.js (client side, without the dynamic imports), the page and components work fine.
However when I try to move to next.js the ag-grid-community library leads to an Unhandled Runtime Error - TypeError: ResizeObserver.observe: Argument 1 is not an object.
Now ResizeObserver seems to be a browser function which would explain why ag-grid doesn't work with server side rendering, so that's why I imported the grid component with dynamic so that it would render on the client side, yet the issue remains. I'm not sure what I might've missed, but my components seem to work on the client side without next.js but not on the client side when using next.js
Solution: Turns out the issue was not ag-grid conflicting with Next.js but rather ag-grid conflicting with React 18.
Turns out React 18 introduced a new render syntax, my issue stem from npx create-react-app using the old syntax when I used it to build a quick project for testing.
When the old syntax is used, React will act as if it's running React 17, that is the reason why the conflicts between ag-grid and React 18 were not showing when I was not using Next.
As per https://github.com/ag-grid/ag-grid/issues/5090 it seems the issue is now being fixed.
I am building a small project using the
react-flow-maker library. This library makes you able to create your own flow diagram with objects. A object can have it's own fields like textboxes, switches and dropdowns.
How does this library work?
The library has the following react component.
<FlowMaker
logic={{
introComponents: [],
components: [],
}}
onChange={data => localStorage.setItem('flowMakerExample', JSON.stringify(data))}
flow={JSON.parse(localStorage.getItem('flowMakerExample'))}
/>
Where the props used in this component have the following function:
logic -> Logic discribes the blocks and the inputs they have. It expects a object with the following properties for example.
let logic = {
introComponents: [
'hello-world'
]
components: [
{
name: 'hello-world'
title: 'Hello World'
inputs: [
{
name: 'options',
title: 'Options',
type: 'dropdown',
default: 'c',
options: [
{title: 'A', value: 'a'},
{title: 'B', value: 'b'},
{title: 'C', value: 'c'},
{title: 'D', value: 'd'},
{title: 'E', value: 'e'},
]
}
],
next: 'hello-world'
}
]
}
onChange -> This returns a the flow data from when a user changes something
flow -> Here you can add a flow to show when the drawing gets mounted, handy if you remove the component from the screen or when the drawing needs to be persistent.
My goal:
Create a block with a dropdown, fetch by API a list of items and put them in the dropdown as title and value
If the user changes something in the diagram, do a new fetch and update the options of the dropdown.
I've implemented a GET request that returns the following JSON list:
[
{"name":"name_0","sid":"0"},
{"name":"name_1","sid":"1"},
{"name":"name_2","sid":"2"},
{"name":"name_3","sid":"3"}
]
Logic.js this file contains the logic used in the FlowMaker component. Here I map the applications to right format for the options used in the dorpdown.
const Logic = async (applications, ..., ...) => {
return {
introComponents: [
'hello-world'
],
components: [
{
name: 'hello-world',
title: 'hello world',
tooltip: 'This is hello',
inputs: [
...
{
name: 'applicationName',
title: 'Application name',
type: 'dropdown',
options: [
...applications.map(app => (
{title: app.name, value: app.name + ' - ' + app.sid})
)
]
},
...
],
next: 'hello-world'
},
...
]
}
}
export default Logic;
drawerReducer.js my reducer where I initailize the new state for this drawer.
const initialState = {
logic: null,
data: null,
applications: [],
...
}
const drawerReducer = (state = initialState, action) => {
switch(action.type) {
case LOGIC:
return {
...state,
logic: action.payload
}
case DATA:
return {
...state,
data: action.payload
}
case APPLICATIONS:
return {
...state,
applications: action.payload
}
...
default:
return state;
}
}
export default drawerReducer;
drawerAction.js contains my actions where fetch the new applications, set the new data and logic.
...
import Logic from '../utils/Logic'
import { LOGIC, APPLICATIONS, ..., ..., DATA } from './types'
export const setLogic = (applications, ..., ...) => dispatch => {
Logic(applications, ..., ...)
.then(newLogic => dispatch({
type: LOGIC,
payload: newLogic
}))
}
export const setData = (newData) => dispatch => {
dispatch({
type: DATA,
payload: newData
})
}
export const setApplications = () => dispatch => {
ApplicationList()
.then(newApplications => dispatch({
type: APPLICATIONS,
payload: newApplications
}))
}
...
drawing.js here I've put the FlowMaker component and get everything together. You can see that I am using a useEffect hook to update the applications and then update the logic when the data prop changes.
import React, {useEffect} from 'react'
import FlowMaker from 'flowmaker'
import '../styles/flowmaker.css'
import Loader from '../utils/Loader'
import {setApplications, setData, setLogic } from '../actions/drawerAction'
import { connect } from 'react-redux'
const Drawer = ({logic, data, applications, doSetLogic, doSetData, doSetApplications}) => {
useEffect(() => {
doSetApplications() //dispatch new applications
doSetLogic(applications) //dispatch to set the new logic with the newly fetched applications
return () => {
//cleanup
}
}, [data])
return (
<div className='drawer-canvas'>
{ logic ?
<>
<ButtonGroup />
<FlowMaker
logic={logic} //intial state of the diagramoptions
onChange={newData => doSetData(newData)}
flow={data}
/>
</>
: <Loader />
}
</div>
)
}
const mapStateToProps = state => ({
logic: state.drawer.logic,
data: state.drawer.data,
applications: state.drawer.applications,
...
})
const mapDispatchToProps = {
doSetLogic: setLogic,
doSetData: setData,
doSetApplications: setApplications,
...
}
export default connect(mapStateToProps, mapDispatchToProps)(Drawer)
My problem
My problem is that when the useEffect data depenceny is hit. The diagram is not re-rendering the new applications options in my diagram as the new options while the logic state in Redux did change.
This is my logic state before a do a data onchange action. You can see that the options are a empty list.
Now I've added a new block in my diagram. That means that the data action will fire and the newData will be set as data, next the useEffect is triggered due the depenency [data] and the logic is set with the new logic, which means that applicationName dropdown must be filled with new options and that is true.
Now with a new redux logic action done I expect that the options are there, but they are not and that is weird because in the second picture you can see that the logic DOES update.
To conclude; my question is how can I re-render this component with the new set Redux state? I thougth when you are changing the redux state a re-render is automatily triggered like setState. Any thougths on this problem?
I know this is a lot of text / code / picture and sorry for that, i've just didnt had any better idea how to do it otherwise.
Since this week there is a new update of the package that I was using. This update makes it possible to re-render component items on specific data changes using a getInputs function. In the example main.js file there is a example logic on this.
{
name: 'frontend',
tooltip: 'Information about the proxy/load balancing server',
title: 'Frontend',
getInputs(info) {
const isHttpsInputs = info.inputs.https ? [
{
name: 'sslCert',
title: 'Add ssl cert',
tooltip: 'Add a ssl certificate',
type: 'switch',
default: true,
}
] : [];
return [
{
name: 'server',
title: 'Server',
type: 'text',
tooltip: 'The address of the proxy/load balancing server',
validation: domainCheck,
}, {
name: 'https',
title: 'The server traffic is https',
type: 'switch',
default: true,
},
...isHttpsInputs,
{
name: 'port',
title: 'Web server port',
type: 'number',
default: 443,
validation: portCheck,
}
]
},
next: 'backend'
},
The getInputs will check if the info.inputs.https is checked, if true then a extra field will be added (or updated) based on that.
In your useEffect, you seem to be dispatching the action which wants to use the updated state, however state updated are not immediate and are affected by closures.
You would need to split that logic into another useEffect
useEffect(() => {
doSetApplications() //dispatch new applications
}, [data]);
useEffect(() => {
if(applications) {
doSetLogic(applications) //dispatch to set the new logic with the newly fetched applications
}
}, [applications]);
I am coding a simple search input component for an app that will eventually become larger, but I am at a loss for why the onChange prop associated with it isn't being called. Here I will show my search input component and the app component into which I import it:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class SearchInput extends Component {
static defaultProps = {
onChange: () => Promise.resolve(),
}
static propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
}
render() {
const { value } = this.props;
return (
<input className="search-input" type='text' onChange={this.handleChange} value={value}/>
)
}
handeChange = (e) => {
const { onChange } = this.props;
onChange(e);
}
}
And then here's my main app component (very simple still, and keep in mind that I have list-rendering functionality, but that isn't where my issue lies). I'm pretty sure the issue lies somewhere in the handleSearchDidChange method that I wrote up and tacked onto the onChange prop for the SearchInput component:
import React, { Component } from 'react';
import Container from './components/container'
import List from './components/list'
import SearchInput from './components/search-input';
// Styles
import './App.css';
export default class App extends Component {
constructor(props){
super(props)
this.state = {
searchValue: undefined,
isSearching: false,
}
// this.handleSearchDidChange = this.handleSearchDidChange.bind(this);
}
render() {
// in the main render, we render the container component (yet to be styled)
// and then call renderList inside of it. We need "this" because this is
// a class-based component, and we need to tell the component that we are
// using the method associated with this class
return (
<div className="App">
<Container>
{this.renderSearchInput()}
{this.renderList()}
</Container>
</div>
);
}
renderSearchInput = () => {
const { searchValue } = this.state;
return (<SearchInput onChange={this.handleSearchDidChange} value={searchValue}/>)
}
renderList = () => {
// return the list component, passing in the fetchData method call as the data prop
// since this prop type is an array and data is an array-type prop, this is
// acceptable
return <List data={this.fetchData()}/>
}
// possibly something wrong with this method?
handleSearchDidChange = (e) => {
const { target } = e;
const { value } = target;
this.setState({
searchValue: value,
isSearching: true,
});
console.log('value: ', value);
console.log('searchValue: ', this.state.searchValue);
console.log('-------------------------')
}
fetchData = () => {
// initialize a list of items
// still wondering why we cannot put the listItems constant and the
// return statement inside of a self-closing setTimeout function in
// order to simulate an API call
const listItems = [
{title: 'Make a transfer'},
{title: 'Wire money'},
{title: 'Set a travel notice'},
{title: 'Pop money'},
{title: 'Edit travel notice'},
{title: 'test money things'},
{title: 'more test money things'},
{title: 'bananas'},
{title: 'apples to eat'},
{title: 'I like CocaCola'},
{title: 'Christmas cookies'},
{title: 'Santa Claus'},
{title: 'iPhones'},
{title: 'Technology is amazing'},
{title: 'Technology'},
{title: 'React is the best'},
];
// return it
return listItems;
}
You have a typo! Missing the "l" in handleChange :)
handleChange = (e) => {
const { onChange } = this.props;
onChange(e);
}
i run your code in sandBox:
https://codesandbox.io/s/onchange-problem-37c4i
there is no issue with your functionality as far as i can see.
but in this case if onChange dose not work for you is because maybe inside of < SearchInput /> component you don't pass the value up to the parent element.
check the sandBox and notice to the SearchInput1 and SearchInput2
Im new to the community and VueJs so please have mercy :) If the answer to the question is obvious, and thanks for your effort up-front, I really appreciate it!
I have this component:
<script>
export default {
name: 'InputElement',
functional: true,
render(createElement, context) {
const { validation, name, field } = context.props || {}
const { listeners } = context
// debugger
return (
<input id={name}
v-validate={validation}
type={field.type}
placeholder={field.placeholder}
name={name}
onInput={ listeners.event_is_nice('sadf')}
class="e-form__input"/>
)
}
}
</script>
as you can see it's not standard VueJs syntax, I am trying to return the input element and onInput I am trying to emit "event_is_nice" event.
When I try this, I get:
"listeners.event_is_nice" is not a function (I guess its not registered).
When I use createElement (standard JSX Vue syntax) or I Use
then it works, but I just have no luck figuring this method out..'/
A solution would be:
export default {
name: 'InputElement',
functional: true,
render(createElement, context) {
const { validation, name, field } = context.props || {}
const { listeners } = context
let emitEvent = listeners['event_is_nice'] //ADDED
// debugger
return (
<input id={name}
v-validate={validation}
type={field.type}
placeholder={field.placeholder}
name={name}
onInput={ () => emitEvent("sadf")} // MODIFIED
class="e-form__input"/>
)
}
}
So in your code I added: let emitEvent = listeners['event_is_nice']
and on input handler onInput={ () => emitEvent("sadf")}
So The answer from roli roli is working, I tried in the sendbox,
Which led me to realize that I didn't address the problem well.
This functional component is wrapped in parent "factory" component,
which looks like this:
<script>
import BaseLabel from './elements/BaseLabel'
import BaseInput from './elements/BaseInput'
import BaseMessage from './elements/BaseMessage'
export default {
functional: true,
components: {
BaseInput,
BaseLabel,
BaseMessage
},
props: {
field: {
type: Object,
default: () => {}
},
validation: {
type: String
},
name: {
type: String
},
errorMsg: {
type: String
}
},
render(createElement, { props, listeners }) {
function appropriateElementComponent() {
switch (props.field.type) {
case 'checkbox':
return BaseInput // TODO: Replace with Base Checkbox
default:
return BaseInput
}
}
const label = createElement(BaseLabel, { props })
const input = createElement(appropriateElementComponent(), { props })
const message = createElement(BaseMessage, { props })
// debugger
return [label, input, message]
}
}
</script>
So The parent wrapper component is the one which is not really receiving and passing up the event to the parent component...