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]);
Related
I have some state variables which will change at some point in time. I want to know what is the proper way of handling such a huge amount of input variables. I had used the same code in Vue.js and the two-way data-binding works nicely for me. What will be the appropriate way to handle the below variables?
data() {
return {
valid: false,
showDialog: true,
showFullLoading: false,
isImage: false,
product: {
name_en: "",
name_ar: "",
category: "",
subcategory: "",
description_en: "",
description_ar: "",
meta_title: "",
meta_description: "",
meta_keywords: "",
price: 0.0,
showSale: false,
sale: 0.0,
image: "",
saleAfterStock: false,
stock: 10
},
images: [
{ image: null, isImage: false, id: shortid.generate() },
{ image: null, isImage: false, id: shortid.generate() },
{ image: null, isImage: false, id: shortid.generate() },
{ image: null, isImage: false, id: shortid.generate() }
],
attributes: [],
defaultVariants: [],
dataWeightClass: ["Gram", "Kilo Gram", "Pound"],
dataDimensionClass: ["Centimeter", "Inch"],
showAttributeDialog: false,
sizeGuide: false,
sizeGuides: [],
attribute: {
title_en: "",
title_ar: "",
description_en: "",
description_ar: "",
image: null,
isImage: false
},
subcategories: [],
options: [],
variantsHeaders: [
{ text: "Variant", value: "name" },
{ text: "Price", value: "price" },
{ text: "Stock", value: "quantity" },
{ text: "Visibility", value: "visibility" }
],
defaultVariantId: "",
defaultPreviewId: ""
};
},
This is the data object in vue. I want to convert these data objects to state in react.
Thanks.
You've said you're going to write a function component. In function components, the usual ways (in React itself) to store state are useState and useReducer:
useState -
Returns a stateful value, and a function to update it.
useReducer -
Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method. (If you’re familiar with Redux, you already know how this works.)
Outside React, there are tools like Redux (with React bindings), Mobx, and others that provide advanced state management.
Sticking with what's in React, if the various parts of your state object change in relation to one another, you probably want useReducer. If they're fairly distinct, you probably want useState (though some people never use useReducer, and others never use useState). From the useReducer documentation:
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
useState
Using useState, you can have a megalithic state object like your data object, or you can break it up into individual parts. I would default to breaking it up into parts and only use a megalithic object if you have to, not least because updates are simpler. (Although that said, your data object is relatively flat, which means it's not that hard to update.)
Here's what that might look like:
function MyComponent(props) {
const [valid, setValid] = useState(false);
const [showDialog, setShowDialog] = useState(true);
// ...
const [product, setProduct] = useState({
name_en: "",
name_ar: "",
category: "",
subcategory: "",
description_en: "",
description_ar: "",
meta_title: "",
meta_description: "",
meta_keywords: "",
price: 0.0,
showSale: false,
sale: 0.0,
image: "",
saleAfterStock: false,
stock: 10,
});
// ...
const [images, setImages] = useState([
{ image: null, isImage: false, id: shortid.generate() },
{ image: null, isImage: false, id: shortid.generate() },
{ image: null, isImage: false, id: shortid.generate() },
{ image: null, isImage: false, id: shortid.generate() },
]);
// ...
}
You might define those initial values outside the component so you don't recreate objects and arrays unnecessarily, and for code clarity. For instance, if you had your existing data object as defaults, the above might be:
const defaults = { /*...*/ };
function MyComponent(props) {
const [valid, setValid] = useState(defaults.valid);
const [showDialog, setShowDialog] = useState(defaults.showDialog);
// ...
const [product, setProduct] = useState(defaults.product);
// ...
const [images, setImages] = useState(defaults.images);
// ...
}
Either way, you then update those state members with calls to their setters. For valid and other simple primitives, it's very straight-forward, either:
setValid(newValue);
...or, if you're using the old value when setting the new one (like toggling), use the callback form so you're sure to get up-to-date state information:
setValid((oldValue) => !oldValue);
For objects and arrays, it's more complicated. You have to create a new object or array with the changes you want. For instance, to update product's category:
setProduct((oldProduct) => { ...oldProduct, category: "new category" });
Notice how we make a copy of the old product object, then apply our update to it. The copy can be shallow (which is what spread syntax does).
To update images using an id variable that says which image to update, and (let's say) you want to set isImage to true:
setImages((oldImages) => oldImages.map((image) => {
if (image.id === id) {
return { ...image, isImage: true }; // Or whatever change it is
}
return image;
}));
Again, notice we didn't just assign a new image object to the existing array, we created a new array (in this case via map).
If you used a megalithic state object, like this:
function MyComponent(props) {
const [state, setState] = useState(defaults);
// ...
}
...then updating (say) images becomes a bit more difficult, although again your state object is fairly shallow so it's not that bad:
setState((oldState) => ({
...oldState,
images: oldState.images.map((image) => {
if (image.id === id) {
return { ...image, isImage: true }; // Or whatever change it is
}
return image;
})
}));
The key thing either way (individual or megalithic) is that any object or array that's a container for what you're changing (directly or indirectly) has to be copied. It's just that with a megalithic structure, you end up doing more copies since everything is inside a single container.
useReducer
With useReducer, you again have the option of a single megalithic state object that the dispatch function updates (via your reducer function), or individual ones, each with their own dispatch and (probably) reducer functions. With useReducer, it's more common to have large state objects because one of the primary use cases is large structures where different parts may be updated by a single action. That might mean megalithic, or just larger chunks, etc.
The main difference is that your code in the component sends "actions" via dispatch, and then it's code in the dispatch function you write that does the updates to the state object and returns the new one.
Suppose you used useReducer with your megalithic object:
function MyComponent(props) {
const [state, dispatch] = useReducer(reducer, defaults);
// ...
}
Your reducer might look like this:
function reducer(state, action) {
const { type, ...actionParams } = action;
switch (type) {
case "update-image":
const { id, ...updates } = actionParams;
return {
...state,
images: state.images.map((image) => {
if (image.id === id) {
return { ...image, ...updates };
}
return image;
})
};
// ...other actions...
}
}
and then in your component, instead of the setImages call shown in the useState section, you might do:
dispatch({ type: "update-image", id, isImage: true });
or similar (there are lots of different ways to write reducer functions).
The same rule applies to the state updates your reducer returns as to useState setters: Any object or array containing what you're updating has to be copied. So in this example copy the overall state object, the images array, and the image object we're updating. As with useState, if you used more individual useReducers, you'd do a bit less copying.
If you are using react's functional approach you can use useReducer hooker in order to locally (in the componente level) manage with state, or use redux to globally (app level) do the same.
You create an myreducer.js file and put something like this
export const myReducer = (state, action) => {
switch (action.type) {
case "UPDATE_PRICE": {
return {
...state,
product: {
...state.product,
price: action.product.price
}
};
}
};
export const myReducer = (state, action) => {
switch (action.type) {
case "UPDATE_PRICE": {
state.product.price = action.product.price;
return state;
}
}
};
in your component you can use useReducer hook (if you just want to manage state locally)
import { useReducer } from 'react'
import { myReducer } from 'myReducer'
export const MyComponent = () => {
const [myState, dispatchMyState] = useReducer(myReducer, {})
...
//where you need to update you can use this
const handleAction = () => {
dispatchMyState({
type: "UPDATE_PRICE",
dataToUpdate
});
}
return <div onClick={handleAction}> anything</div>
}
I wanted to create 'add to cart' functionality using Redux, so I have defined a reducer function which accepts a state and action, where state is provided a default value.
export const defaultValue= {
products: [
{
id: "",
title: "",
description: "",
image: "",
price: 0,
amount: 0,
},
]
};
export const manageCart = (
state = defaultValue,
action: { type: string; payload: any}
) => {
switch (action.type) {
case actionConstants.ADD_TO_CARTS:
{
// ADD to card
}
break;
case actionConstants.REMOVE_FROM_CART: {
// Remove from card
}
default:
return state;
}
}
So by default I'm seeing this blank product on my cart which is messing up my logic moving further. Since it is mandatory to provide initial value to the state, what should i do differently to get rid of this issue.
So by default I'm seeing this blank product on my cart which is messing up my logic moving further.
Because you are passing that blank product in the default state!
Just pass an empty array instead
export const defaultValue= {
products: []
};
I am using React Router, Redux, Redux Sagas, antd in my project.
My main goal is to click on a cell from the table of items, go to the next page, which is Details page for that item, and fetch details from server using ID that I pass in from the Link. It is not working right now.
I have a component, component A, which has column like this:
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
render: (id: number) => <Link to={{ pathname: `/pages/my-page/${id}`, state: {id}}}>{id}</Link>,
},
It goes to the next component, Component B, with the param id passed in. Now, I want to use that id to do further fetching of details for that particular item, using redux and redux saga and render it on the next page, Component B, which I do using constructor. I tried using this below code in componentDidMount() as well, but that doesn't work either.
constructor(props: any) {
super(props);
const {onDetailsSearch, state} = props;
const {router} = state;
const {location} = router;
const {state: newState}: {state: any} = location;
onDetailsSearch({id: (newState || {}).id});
}
This is what is in the reducer:
import {DetailsState} from 'state/'
import actions, {DetailsAction} from './actions'
export interface DetailsState {
loading: boolean
id: number
items: Details[]
}
const initialState: DetailsState = {
loading: false,
id: 0,
items: [],
}
export default function DetailsReducer(state: DetailsState = initialState, action: DetailsAction): DetailsState {
switch (action.type) {
case actions.SET_STATE:
return { ...state, ...action.payload }
default:
return state
}
}
This is the Redux saga code:
import {all, call, put, takeEvery} from 'redux-saga/effects'
import getDetails from "services/getDetails";
import actions from './actions'
type Params = {
type: typeof actions.LOAD_DETAILS
payload: object
}
export function* LOAD_DETAILS({ payload }: Params) {
yield put({
type: 'details/SET_STATE',
payload: {
loading: true,
},
})
const { response, error } = yield call(getDetails, payload)
console.log('response:', response);
if (!error) {
yield put({
type: 'details/SET_STATE',
payload: {
details: response.data,
},
})
}
else {
// TODO: handle error
}
yield put({
type: 'details/SET_STATE',
payload: {
loading: false,
},
})
}
export default function* rootSaga() {
yield all([
takeEvery(actions.LOAD_DETAILS, LOAD_DETAILS),
])
}
This is how I call the dispatch function onDetailsSearch, which in turn should call the saga and set the results in the redux store.
But its not doing that. What is wrong here?
I found out what the problem was. I have actions stored as constants in a actionConstants.ts file.
The action constant for this particular action was 'details/LOAD_DETAILS', and the action string in dispatch I was using was 'LOAD_DETAILS'
Hello, I am new to redux and I am struggling with a problem. I am trying to access and map over the comments within my post array. However, I am not sure how to do this. So far, I've tried changing the actions and reducers in order to solve this issue. I think the problem is within the react and redux. I can't tell if my mapStateToProps is working correctly. Also, the state is being fetched from my express server and it seems to be working properly as you can see in the picture.
My getPost action:
export const getPost = (group_id, post_id) => async dispatch => {
try {
const res = await axios.get(`/api/groups/${group_id}/${post_id}`);
dispatch({
type: GET_POST,
payload: res.data
});
} catch (error) {
dispatch({
type: POST_ERROR,
payload: { msg: error.response.statusText, status: error.response.status }
});
}
};
The initial state:
const initialState = {
groups: [],
group: [],
loading: true,
error: {}
};
The reducer:
case GET_POST:
return {
...state,
post: payload,
loading: false
};
Where I'm trying to map over the comments:
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getPost } from '../../../redux/actions/group';
const Post = ({ getPost, post, match }) => {
useEffect(() => {
getPost(match.params.group_id, match.params.post_id);
}, [getPost, match.params.group_id, match.params.post_id]);
// I want to map over the comments here
return (
{post.comments.map(comment => ({ comment }))}
);
};
Post.propTypes = {
getPost: PropTypes.func.isRequired,
group: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPost })(Post);
You can access nested object with some tricks using redux, we have use this way in our prod env for some time.
First the reducer (you can make this reducer even more complex)
const LocalStorageReducer = createReducer<Store['localStorage']>(
new LocalStorage(),
{
saveLocalStorageItem(state: LocalStorage, action: any) {
return {...state, [action.payload.item]: action.payload.value}; // <= here
},
}
);
For Actions
export const actions = {
saveLocalStorageItem: (payload: InputAction) => ({type: 'saveLocalStorageItem', payload}),
};
For the type InputAction
export class InputAction {
item: string;
value: string | Array<string> | null | boolean;
constructor() {
this.item = '';
this.value = null;
}
}
For the handler in component
this.props.saveLocalStorage({ item: 'loading', value: false });
In this way you can go one way done to the nested redux store.
For complex (4-5 levels) and multiple (> 2 times) data structure, there are other ways, but in most situations, it's good enough.
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.