Not able to render a React element in React-BootStrap-Table - javascript

I want to render buttons in react-bootstrap-table. However, If I pass a React component as the content, the table is render with [object Object].
Here's the code I've tried so far:
// Imports
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { BootstrapTable, TableHeaderColumn } from "react-bootstrap-table";
import "../../../node_modules/react-bootstrap-table/css/react-bootstrap-table.css";
// Exports
export default class Table extends Component {
constructor(props) {
super(props);
// Defaults
this.props.options.search = this.props.options.search ? true : false;
this.props.options.pagination = this.props.options.pagination ? true : false;
}
// Option Buttons
optionButtons = (cell, row) => {
return cell.map(item => {
let key = Object.keys(item)[0];
return (
<Link to={item[key]} className="btn btn-sm">
{key}
</Link>
);
});
};
// This works however
// optionButtons = (cell, row) => {
// return <Link to="/some/route" className="btn btn-sm">Action</Link>;
// };
render() {
let headings = this.props.columns.map((heading, index) => {
let key = Object.keys(heading)[0];
if (index === 0) {
return (
<TableHeaderColumn
key={heading[key]}
dataSort={heading.sortable ? true : false}
dataField={key}
width={
heading.width && !isNaN(heading.width)
? heading.width.toString()
: null
}
isKey
>
{heading[key]}
</TableHeaderColumn>
);
}
if (key === "options") {
return (
<TableHeaderColumn
key={heading[key]}
dataFormat={this.optionButtons}
dataField={key}
width={
heading.width && !isNaN(heading.width)
? heading.width.toString()
: null
}
>
{heading[key]}
</TableHeaderColumn>
);
}
return (
<TableHeaderColumn
key={heading[key]}
dataSort={heading.sortable ? true : false}
dataField={key}
width={
heading.width && !isNaN(heading.width)
? heading.width.toString()
: null
}
>
{heading[key]}
</TableHeaderColumn>
);
});
return (
<BootstrapTable
data={this.props.data}
search={this.props.options.search}
pagination={this.props.options.pagination}
>
{headings}
</BootstrapTable>
);
}
}
The data I am passing to the options key is an array with multiple objects. The problem is in rendering the option buttons. The idea is to be able to pass the number of buttons/link I want from a component and they will be rendered.
This is the data being passed to the options key:
options: [
{ Edit: `/item/${item.id}/edit` },
{ Delete: `/item/${item.id}/delete` }
]

Looks like dataFormat expects a single value, wrap your buttons into a root element (div for example), or into a fragment if supported.

Related

Problem with React Joyride on production Cannot read properties of undefined (reading '0')

We decided to implement a simple onboarding tour on our application, everything was going well on local but once we deploy it to production its crashing showing the next error on devtools:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '0')
it's kind of hard to debug this and see what is the problem because it's caused on production and its like debugging with the eyes closed.
the bug/error that the console show it's and undefined and by logic will crash too on local, but this it's not the case
The error is caused after we pass the first step like you can see on this video(also you can se how it's working locally:
VIDEO WITH THE ISSUE
They way the joyride its implemented consists on two big keys
the first one its the joyride wrapper:
import { useEffect, useState } from 'react';
import Joyride, { CallBackProps, STATUS, Step } from 'react-joyride';
import { Tooltip } from './components/tool-tip';
import { OnboardingTourJoyrideProps, ValidSteps } from '../../types/onboarding-tour';
import { useGuidedTour } from '../../hooks/use-guided-tour';
export function OnboardingTourJoyride({ start, stepToCheck }: OnboardingTourJoyrideProps): JSX.Element {
const { dashBoardSteps, terminalAppSteps, cloudAppSteps } = useGuidedTour();
const [steps, setSteps] = useState<Step[]>(dashBoardSteps);
const [run, setRun] = useState<boolean>(false);
const [resetTour, setResetTour] = useState<boolean>(false);
const [stepToInitRestart, setStepToInitRestart] = useState<number>(0);
function handleSteps() {
switch (stepToCheck) {
case ValidSteps.DASHBOARD_STEPS:
setSteps(dashBoardSteps);
setStepToInitRestart(dashBoardSteps.length - 1);
break;
case ValidSteps.TERMINAL_APP_STEPS:
setSteps(terminalAppSteps);
setStepToInitRestart(terminalAppSteps.length - 1);
break;
case ValidSteps.CLOUD_APP_STEPS:
setSteps(cloudAppSteps);
setStepToInitRestart(cloudAppSteps.length - 1);
}
}
useEffect(() => {
const onboardinIsCompletedOrSkipped = handleCheckIfTourIsComplete();
if (!onboardinIsCompletedOrSkipped) {
handleSteps();
if (start) setRun(true);
}
}, [start]);
useEffect(() => {
if (resetTour) {
setRun(true);
setResetTour(false);
}
}, [resetTour]);
function handlePropertyToSaveOnLocalStorage() {
switch (stepToCheck) {
case ValidSteps.DASHBOARD_STEPS:
localStorage.setItem('ONBOARDING_TOUR_DASHBOARD', 'true');
break;
case ValidSteps.TERMINAL_APP_STEPS:
localStorage.setItem('ONBOARDING_TOUR_TERMINAL_APP', 'true');
break;
case ValidSteps.CLOUD_APP_STEPS:
localStorage.setItem('ONBOARDING_TOUR_CLOUD_APP', 'true');
break;
}
}
function handleCheckIfTourIsComplete() {
if (stepToCheck === ValidSteps.DASHBOARD_STEPS) {
return localStorage.getItem('ONBOARDING_TOUR_DASHBOARD');
} else if (stepToCheck === ValidSteps.TERMINAL_APP_STEPS) {
return localStorage.getItem('ONBOARDING_TOUR_TERMINAL_APP');
} else if (stepToCheck === ValidSteps.CLOUD_APP_STEPS) {
return localStorage.getItem('ONBOARDING_TOUR_CLOUD_APP');
}
}
function handleJoyrideCallback(data: CallBackProps) {
const { status, action, index } = data;
console.log(data);
const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED];
if (index === stepToInitRestart && status === STATUS.SKIPPED) {
setRun(false);
setResetTour(true);
}
if (finishedStatuses.includes(status)) {
if (action === 'skip') {
setRun(false);
handlePropertyToSaveOnLocalStorage();
}
setRun(false);
handlePropertyToSaveOnLocalStorage();
}
}
return (
<Joyride
tooltipComponent={Tooltip}
disableScrolling={true}
callback={handleJoyrideCallback}
continuous
hideCloseButton
run={run}
scrollToFirstStep
showProgress={false}
showSkipButton={true}
steps={steps}
styles={{
options: {
zIndex: 10000,
},
}}
/>
);
}
which its a simple wrapper that contains all the logic to rehuse the same component across the app
and the other one it's the custom tooltip component:
import { Box, CloseButton, Flex, Text } from '#chakra-ui/react';
import { TooltipRenderProps } from 'react-joyride';
import { Button } from '../../button';
import { useIntl } from 'react-intl';
export function Tooltip({
backProps,
continuous,
index,
isLastStep,
primaryProps,
skipProps,
step,
tooltipProps,
size,
}: TooltipRenderProps): JSX.Element {
const intl = useIntl();
const steps = {
currentStep: index,
stepsLength: size - 2,
};
function handleResetOrBackStep(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
if (isLastStep) {
skipProps.onClick(e);
} else {
backProps.onClick(e);
}
}
function handleCloseButton(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
if (isLastStep) {
primaryProps.onClick(e);
} else {
skipProps.onClick(e);
}
}
function handleFowardButton() {
return continuous
? index !== 0 && !isLastStep
? intl.formatMessage({ id: 'buttons.next' })
: isLastStep
? intl.formatMessage({ id: 'buttons.finish' })
: intl.formatMessage({ id: 'buttons.startTour' })
: null;
}
function handleShowStepsCounter() {
return index !== 0 && !isLastStep ? (
<Text fontSize="16px">
{intl.formatMessage(
{ id: 'onboardingTour.toolTip.stepsCounter' },
{
currentStep: steps.currentStep,
totalSteps: steps.stepsLength,
},
)}
</Text>
) : (
!isLastStep && (
<Button variant="outline" {...skipProps}>
{intl.formatMessage({ id: 'buttons.skipTour' })}
</Button>
)
);
}
function handleBackwardButton() {
return isLastStep
? intl.formatMessage({ id: 'buttons.restartTour' })
: intl.formatMessage({ id: 'buttons.previous' });
}
return (
<Box {...tooltipProps} background="white" p="48px" w="100%" borderRadius="6px">
<Box padding="md">
<Flex justifyContent="space-between" align="baseline" marginBottom="15px">
{step.title && (
<Text fontSize="20px" fontWeight="700">
{step.title}
</Text>
)}
<CloseButton onClick={e => handleCloseButton(e)} />
</Flex>
{step.content && (
<Box>
<Text w="504px" fontSize="16px" fontWeight="400" lineHeight="26px" marginBottom="48px">
{step.content}
</Text>
</Box>
)}
</Box>
<Box>
<Flex justifyContent="space-between" align="center">
{handleShowStepsCounter()}
<Flex w={isLastStep ? '100%' : ''} justifyContent="space-between">
{index !== 0 && (
<Button marginRight="12px" variant="outline" onClick={e => handleResetOrBackStep(e)}>
{handleBackwardButton()}
</Button>
)}
<Button h="35px" {...primaryProps}>
{handleFowardButton()}
</Button>
</Flex>
</Flex>
</Box>
</Box>
);
}
I don't know if anyone have experience with a similar issue, I google it and search all across SO and the issues on the library repository.
Also it's weird because on the pass I worked with react-joyride and deployed to production and there is no any problem.
To put you all in context, we are using next with a framework called Gasket.
Had the same issue.
The problem is with the experimental SWC minification using the swcMinify config. It tries to optimize too much, leading to the offset variable not being used before it is declared.
It's not a problem with the version of popper.js or any other dependency. To fix the problem you just need to set swcMinify: false in your next config (also updating nextjs to 13.1.1 fixes the problem in my case).
Here is discussion about: https://github.com/gilbarbara/react-joyride/issues/857

NotFoundError: Failed to execute 'removeChild' on 'Node' on React with Flickity

I'm building a react application, and on my HomePage, I have the component 'CategoriesList'.
When I'm on the HomePage, the 'Categories List' works well, but when I navigated to ProductDetails page with react-router-dom, I found this error:
"NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node."
'CategoriesList' uses Flickty. I tried to remove Flickity, and... Works well. But I need to use Flickity.
Can anyone help me?
CategoryList Component:
const CategoryList = ({list, popupOpen, refreshProductList}) => {
return (
<Container>
<Slider
options={{
cellAlign: 'center',
draggable: true,
groupCells: true,
contain: false,
pageDots: false,
}}
style={ popupOpen ? ({opacity: 0.05}) : null}
>
{list.map((category, index) => (
<Category key={index}>{category}</Category>
))}
</Slider>
</Container>
);
}
Flickty Slider Component:
import Flickity from 'flickity';
import 'flickity/dist/flickity.min.css';
export default class Slider extends Component {
constructor(props) {
super(props);
this.state = {
flickityReady: false,
};
this.refreshFlickity = this.refreshFlickity.bind(this);
}
componentDidMount() {
this.flickity = new Flickity(this.flickityNode, this.props.options || {});
this.setState({
flickityReady: true,
});
}
refreshFlickity() {
this.flickity.reloadCells();
this.flickity.resize();
this.flickity.updateDraggable();
}
componentWillUnmount() {
this.flickity.destroy();
}
componentDidUpdate(prevProps, prevState) {
const flickityDidBecomeActive = !prevState.flickityReady && this.state.flickityReady;
const childrenDidChange = prevProps.children.length !== this.props.children.length;
if (flickityDidBecomeActive || childrenDidChange) {
this.refreshFlickity();
}
}
renderPortal() {
if (!this.flickityNode) {
return null;
}
const mountNode = this.flickityNode.querySelector('.flickity-slider');
if (mountNode) {
return ReactDOM.createPortal(this.props.children, mountNode);
}
}
render() {
return [
<div style={this.props.style} className={'test'} key="flickityBase" ref={node => (this.flickityNode = node)} />,
this.renderPortal(),
].filter(Boolean);
}
}
If you want to use Flickity along with React instead of creating your own component, I highly recommend using react-flickity-component. It's also easy to use:
Tip: if you use wrapAround option in Flickity set
disableImagesLoaded prop ture (default is false).
import Flickity from 'react-flickity-component'
const flickityOptions = {
autoPlay: 4000,
wrapAround: true
}
function Carousel() {
return (
<Flickity
disableImagesLoaded
options={flickityOptions} // Takes flickity options {}
>
<img src="/images/placeholder.png"/>
<img src="/images/placeholder.png"/>
<img src="/images/placeholder.png"/>
</Flickity>
)
}

Cannot read property 'appendChild' of null - Tiny Slider React

React newbie here, I am having problems with my TinySlider component. Each time I update in the UI how many posts can appear in the carousel, I get this error every other update which I need to fix:
Uncaught (in promise) TypeError: Cannot read property 'appendChild' of null
If I remove <TinySlider settings={...settings}></Tinyslider> I do not get this error.
I have tried this: { renderProfilesCarousel ? renderProfilesCarousel : '' } inside the <tinySlider> but that does not work.
Any idea what I could do here? Pretty stuck on this now.
// React
import * as React from 'react';
// Styling
import styles from './LinkedIn.module.scss';
// Importing the props
import { ILinkedInProps } from './ILinkedInProps';
// Importing the state
import { ILinkedInState } from './ILinkedInState';
// Removes special characters
import { escape } from '#microsoft/sp-lodash-subset';
// Library for making http requests
import axios from 'axios';
// Library for creating unique ids
import shortid from 'shortid';
// Fabric UI elements
import {
DocumentCard,
DocumentCardPreview,
DocumentCardType,
DocumentCardDetails,
DocumentCardTitle,
IDocumentCardPreviewProps
} from 'office-ui-fabric-react/lib/DocumentCard';
import { ImageFit, Image } from 'office-ui-fabric-react/lib/Image';
// Sort array
import sortBy from 'sort-array';
import TinySlider from "tiny-slider-react";
import { SPComponentLoader } from '#microsoft/sp-loader';
import "./styles.scss";
// LinkedIn Component Class
export default class LinkedIn extends React.Component<ILinkedInProps, ILinkedInState> {
// State needed for the component
constructor(props) {
super(props);
this.state = {
profiles: [],
isLoading: true,
errors: null
};
SPComponentLoader.loadCss('//cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.2/tiny-slider.css');
}
// This function runs when component is first renderd
public componentDidMount() {
this.getProfiles();
}
// This function runs when props that have changed have been passed in
public componentDidUpdate(prevProps) {
if ( prevProps.listName !== this.props.listName || prevProps.postCount ! == this.props.postCount )
{
this.renderProfile();
}
}
// Grabs LinkedIn profiles - This service runs once a day
private getProfiles() {
let companyNameCreate: string;
let backUpImageCreate: string;
axios
.get(
"https://cors-anywhere-spfx.herokuapp.com/" +
"https://cache1.phantombooster.com/YRrbtT9qhg0/KJhwG7zo0zPE5zc9Eehn6Q/result.json"
)
.then(response => {
this.setState({
profiles: response.data.filter(d => d.postContent).map(post => {
if (post.profileUrl == 'https://www.linkedin.com/company/')
{
companyNameCreate = 'company';
backUpImageCreate = 'https://media-exp2.licdn.com/dms/image/C4D0BAQEbfV4VNvsJyg/company-logo_100_100/0?e=1587600000&v=beta&t=CX_s-ekYNn0TnXANeftQkLZ9jIvMW7PtDTLLcHcu9wY'
}
else if (post.profileUrl == 'https://www.linkedin.com/company/1')
{
companyNameCreate = 'company';
backUpImageCreate = 'https://media-exp2.licdn.com/dms/image/C4D0BAQG_Pr1cDaGfdA/company-logo_200_200/0?e=1587600000&v=beta&t=C0fWkjbO2Elth8K4pG4i_kzwlu8dvQvN1Ws-yKGxxP4'
}
else if (post.profileUrl == 'https://www.linkedin.com/company/2')
{
companyNameCreate = 'company';
backUpImageCreate = 'https://media-exp2.licdn.com/dms/image/C4D0BAQHdub-mnNwSNg/company-logo_100_100/0?e=1587600000&v=beta&t=druqo_O5gB5cExttREUlSdGnJhr4Wx2-PCjshJ0K0fI'
}
else if (post.profileUrl == 'https://www.linkedin.com/company/3')
{
companyNameCreate = 'company';
backUpImageCreate = 'https://media-exp2.licdn.com/dms/image/C4D0BAQEUKGn5i1EnQA/company-logo_100_100/0?e=1587600000&v=beta&t=uwE3CUaodiqmW2K3a3Hm57QDIDlMvrmfmoHDvlGnzuY'
}
else if (post.profileUrl =='https://www.linkedin.com/company/4')
{
companyNameCreate = 'company';
backUpImageCreate = 'https://media-exp2.licdn.com/dms/image/C4D0BAQGYqqxF43Rfdg/company-logo_200_200/0?e=1587600000&v=beta&t=4hmLzdbkjk_hL3rwonWgTbUF1V-itkyBEfLBh85G7_k'
}
else if (post.profileUrl == 'https://www.linkedin.com/company/5')
{
companyNameCreate = 'company';
backUpImageCreate = 'https://media-exp2.licdn.com/dms/image/C4E0BAQHsNKLawvW7zg/company-logo_100_100/0?e=1587600000&v=beta&t=26Otro4T3q90GznPxXX6n3oPTYYWIgzodOIask0enu4'
}
return {
...post,
companyName: companyNameCreate,
backUpImage: backUpImageCreate
}
})
});
})
// Error catching
.catch(errors => this.setState({ errors, isLoading: false }));
}
// Creates the renderd layout of the LinkedIn profile
private async renderProfile() {
let filter: string = '';
// Works out which profile to display
switch (this.props.listName) {
case "abfi":
filter = 'https://www.linkedin.com/company/1';
break;
case 'abEnzymes':
filter = 'https://www.linkedin.com/company/2';
break;
case 'abitec':
filter = 'https://www.linkedin.com/company/3';
break;
case 'ohly':
filter = 'https://www.linkedin.com/company/4';
break;
case 'pgpi':
filter = 'https://www.linkedin.com/company/5';
break;
case 'spiPharma':
filter = 'https://www.linkedin.com/company/6';
break;
case 'all':
filter = 'Post';
break;
default:
filter = 'https://www.linkedin.com/company/1';
return filter;
}
// Grabs the array of objects
let { profiles } = this.state;
const slotOrder = [
"", "1h","2h","3h","4h","5h","6h","7h","8h","9h","10h","11h","12h", "13h","14h","15h","16h","17h","18h","19h","20h","21h","22h", "23h", "2d", "3d", "4d", "5d", "6d", "1w", "2w", "3w", "1mo", "2mo", "3mo", "4mo", "5mo", "6mo", "7mo", "8mo", "9mo", "10mo", "11mo", "1yr", "2yr"
];
// Select only the needed objects from the array
let selectedProfile;
// Display all posts
if (this.props.listName !== 'all') {
selectedProfile = profiles.filter(profile => profile.profileUrl == filter);
} else {
selectedProfile = profiles.filter(profile => profile.action == filter);
}
selectedProfile = sortBy(selectedProfile, "postDate", { postDate: slotOrder })
selectedProfile = selectedProfile.splice(0, this.props.postCount)
// Renders the selected profile
let renderProfilesCarousel = selectedProfile.map(profile => {
// If LinkedIn post has no image, then add a fallback!
if (profile.imgUrl == "" || profile.imgUrl == null ) {
profile.imgUrl = profile.backUpImage;
}
// Removes hashtag line from posts
profile.postContent = profile.postContent.replace(/hashtag/g, '');
return(
<div className={styles.linkedInContainerCarousel} style={{ position: "relative" }} key={shortid.generate()}>
<a href={profile.postUrl} target="_blank" data-interception="off">
<DocumentCard
aria-label={profile.postContent}
className={styles.linkedInDocCard}
>
{ profile.imgUrl &&
<Image
src={profile.imgUrl}
imageFit={ImageFit.cover}
height={168}
/>
}
<DocumentCardTitle
title={profile.postContent}
shouldTruncate={true}
/>
<p className={ styles.linkedInCompany}>{profile.companyName}</p>
<p className={ styles.linkedInLikes}>{`Likes: ${profile.likeCount}`}</p>
</DocumentCard>
</a>
</div>
)
});
// Renders the selected profile
let renderProfiles = selectedProfile.map(profile => {
// If LinkedIn post has no image, then add a fallback!
if (profile.imgUrl == "" || profile.imgUrl == null ) {
profile.imgUrl = profile.backUpImage;
}
let previewProps: IDocumentCardPreviewProps = {
previewImages: [
{
name: profile.postContent,
previewImageSrc: profile.imgUrl,
iconSrc: null,
imageFit: ImageFit.cover,
height: 110,
width: 110
}
]
};
return(
<div className={styles.linkedInContainer} style={{ position: "relative" }} key={shortid.generate()}>
<a href={profile.postUrl} target="_blank" data-interception="off">
<DocumentCard
aria-label={profile.postContent}
className={styles.linkedInDocCard}
type={DocumentCardType.compact}
>
{ profile.imgUrl &&
<DocumentCardPreview {...previewProps} />
}
<DocumentCardDetails>
<DocumentCardTitle
title={profile.postContent}
shouldTruncate={true}
/>
<p className={ styles.linkedInCompany}>{profile.companyName}</p>
<p className={ styles.linkedInLikes}>{`Likes: ${profile.likeCount}`}</p>
</DocumentCardDetails>
</DocumentCard>
</a>
</div>
)
});
let settings: any;
if (this.props.toggleInfoHeaderValue == true )
{
settings = {
lazyload: true,
nav: false,
mouseDrag: false,
items: 1,
swipeAngle: false,
speed: 400,
autoplay: false,
axis: "horizontal",
autoHeight: false,
rewind: true,
fixedWidth: false
};
}
else
{
settings = {
lazyload: true,
nav: false,
mouseDrag: false,
items: 3,
swipeAngle: false,
speed: 400,
autoplay: false,
axis: "vertical",
autoHeight: false,
rewind: true,
fixedWidth: false
};
};
if (this.props.toggleInfoScrollValue) {
settings.autoplay = true;
} else {
settings.autoplay = false;
}
if (this.props.toggleInfoHeaderValue == true ) {
return(
<div>
<TinySlider settings={settings}>
{renderProfilesCarousel}
</TinySlider>
</div>
)
} else {
return (
<div className={styles.upArrows}>
<TinySlider settings={settings}>
{renderProfiles}
</TinySlider>
</div>
)
}
}
// Renders to the browser
public render(): React.ReactElement<ILinkedInProps> {
return (
<div className={ styles.linkedIn }>
<div className={ styles.container }>
<p className={ styles.title }>{escape(this.props.description)}</p>
<div>
{ this.renderProfile() }
</div>
</div>
</div>
);
}
}
Error in full:
Counld you try this instead
{ renderProfilesCarousel ? renderProfilesCarousel : <span></span> }
React likes it when it has elements, and I'm not sure how it'd deal with a ''
Edit to edit:
I think you'll want to move the actual JSX.Element out of the renderProfile() method. React doesn't take that as a child element.
So I added two new items to state (I guess you'll want three, one for renderProfilesCarousel too):
settings?: any;
renderProfiles?: JSX.Element[];
Then I did this at the bottom of the renderProfile() method:
/* if (this.props.toggleInfoHeaderValue == true) {
return (
<div>
<TinySlider settings={settings}>
{renderProfilesCarousel}
</TinySlider>
</div>
)
} else {
return (
<div /* className={styles.upArrows} >
<TinySlider settings={settings}>
{renderProfiles}
</TinySlider>
</div>
)
} */
console.log(renderProfiles.length);
this.setState({
settings: settings,
renderProfiles: renderProfiles,
isLoading: false
})
Then, in your return of the actual render to the browser is where I put the actual JSX.Element:
// Renders to the browser
public render(): React.ReactElement<ILinkedInProfilesProps> {
const {settings, renderProfiles} = this.state;
const theRenderProfileJsxElement: JSX.Element =
<div /* className={styles.upArrows} */>
<TinySlider settings={settings}>
{renderProfiles}
</TinySlider>
</div>;
return (
<div /* className={styles.linkedIn} */>
<div /* className={styles.container} */>
<p /* className={styles.title} */>{escape(this.props.description)}</p>
<div>
{this.state.isLoading === false &&
theRenderProfileJsxElement
}
</div>
</div>
</div>
);
}
And I used your state of isLoading to prevent the Carousel from loading prior to finishing all the logic and loading from above.
Also! If you don't have React Dev Tools on your browser, you need it!
I can see the component Carousel, but I didn't do the if logic for either the toggleInfoHeaderValue. Let's see if that works?

object is possibly 'null' in typescript

interface IRoleAddProps {
roles: Array<IRole>
}
interface IRoleAddState {
current: IRole | null
}
class RoleAdd extends React.Component<IRoleAddProps, IRoleAddState> {
state = {
current: null,
}
renderNoneSelect = () => {
return (
<div styleName="empty">
<SvgIcon name="arrow" styleName="icon-arrow" />
<span>Empty</span>
</div>
)
}
onRoleClick = (role: IRole) => {
this.setState({
current: role,
})
}
render() {
const { roles } = this.props
const current = this.state.current
return (
<div styleName="role-add">
<div styleName="role-list">
<div styleName="title">Select role:</div>
<div styleName="list">
{roles.map(role => {
const cls = classNames({
item: true,
active: current && ( current.id === role.id )
})
return (
<div
key={role.id}
styleName={cls}
className="g-text-inline"
onClick={this.onRoleClick.bind(this, role)}
>
<CheckBox />
<span>{role.name}</span>
</div>
)
})}
</div>
</div>
<div styleName="view">
{!current && this.renderNoneSelect()}
{current && 'view'}
</div>
</div>
)
}
}
export default RoleAdd
The code like this, but TS still tells me:
Even I tried:
And "!" also doesn't work
As you can see the "current" object can't be null because i have null check before i use it.
But typescript engine still show me that error.
I'm wondering is that because i initialized current object with null value, but ts can not figure out types from setState, so it takes current always null?
You'll need to assign a type to state, like
state: IRoleAddState = {
current: null
};
Then, state will be of type IRoleAddState and not { current: null }. After that, the methods you tried will work.
Explicitly defining the state in a constructor should solve the issue.
constructor(props) {
super(props);
this.state = {
current: null;
}
}

Using JsonSchemaForm on change to update field's content

I am trying to use JsonSchema-Form component but i ran into a problem while trying to create a form that, after choosing one of the options in the first dropdown a secondary dropdown should appear and give him the user a different set o options to choose depending on what he chose in the first dropdown trough an API call.
The thing is, after reading the documentation and some examples found here and here respectively i still don't know exactly how reference whatever i chose in the first option to affect the second dropdown. Here is an example of what i have right now:
Jsons information that are supposed to be shown in the first and second dropdowns trough api calls:
Groups: [
{id: 1,
name: Group1}
{id: 2,
name: Group2}
]
User: [User1.1,User1.2,User2.1,User2.2,User3.1,User3.2, ....]
If the user selects group one then i must use the following api call to get the user types, which gets me the the USER json.
Component That calls JSonChemaForm
render(){
return(
<JsonSchemaForm
schema={someSchema(GroupOptions)}
formData={this.state.formData}
onChange={{}}
uiSchema={someUiSchema()}
onError={() => {}}
showErrorList={false}
noHtml5Validate
liveValidate
>
)
}
SchemaFile content:
export const someSchema = GroupOptions => ({
type: 'object',
required: [
'groups', 'users',
],
properties: {
groups: {
title: 'Group',
enum: GroupOptions.map(i=> i.id),
enumNames: GroupOptions.map(n => n.name),
},
users: {
title: 'Type',
enum: [],
enumNames: [],
},
},
});
export const someUISchema = () => ({
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
},
types: {
'ui:options': {
size: {
lg: 15,
},
},
},
});
I am not really sure how to proceed with this and hwo to use the Onchange method to do what i want.
I find a solution for your problem.There is a similar demo that can solve it in react-jsonschema-form-layout.
1. define the LayoutField,this is part of the demo in react-jsonschema-form-layout.To make it easier for you,I post the code here.
Create the layoutField.js.:
import React from 'react'
import ObjectField from 'react-jsonschema-form/lib/components/fields/ObjectField'
import { retrieveSchema } from 'react-jsonschema-form/lib/utils'
import { Col } from 'react-bootstrap'
export default class GridField extends ObjectField {
state = { firstName: 'hasldf' }
render() {
const {
uiSchema,
errorSchema,
idSchema,
required,
disabled,
readonly,
onBlur,
formData
} = this.props
const { definitions, fields, formContext } = this.props.registry
const { SchemaField, TitleField, DescriptionField } = fields
const schema = retrieveSchema(this.props.schema, definitions)
const title = (schema.title === undefined) ? '' : schema.title
const layout = uiSchema['ui:layout']
return (
<fieldset>
{title ? <TitleField
id={`${idSchema.$id}__title`}
title={title}
required={required}
formContext={formContext}/> : null}
{schema.description ?
<DescriptionField
id={`${idSchema.$id}__description`}
description={schema.description}
formContext={formContext}/> : null}
{
layout.map((row, index) => {
return (
<div className="row" key={index}>
{
Object.keys(row).map((name, index) => {
const { doShow, ...rowProps } = row[name]
let style = {}
if (doShow && !doShow({ formData })) {
style = { display: 'none' }
}
if (schema.properties[name]) {
return (
<Col {...rowProps} key={index} style={style}>
<SchemaField
name={name}
required={this.isRequired(name)}
schema={schema.properties[name]}
uiSchema={uiSchema[name]}
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
formData={formData[name]}
onChange={this.onPropertyChange(name)}
onBlur={onBlur}
registry={this.props.registry}
disabled={disabled}
readonly={readonly}/>
</Col>
)
} else {
const { render, ...rowProps } = row[name]
let UIComponent = () => null
if (render) {
UIComponent = render
}
return (
<Col {...rowProps} key={index} style={style}>
<UIComponent
name={name}
formData={formData}
errorSchema={errorSchema}
uiSchema={uiSchema}
schema={schema}
registry={this.props.registry}
/>
</Col>
)
}
})
}
</div>
)
})
}</fieldset>
)
}
}
in the file, you can define doShow property to define whether to show another component.
Next.Define the isFilled function in JsonChemaForm
const isFilled = (fieldName) => ({ formData }) => (formData[fieldName] && formData[fieldName].length) ? true : false
Third,after you choose the first dropdown ,the second dropdown will show up
import LayoutField from './layoutField.js'
const fields={
layout: LayoutField
}
const uiSchema={
"ui:field": 'layout',
'ui:layout': [
{
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
}
},
{
users: {
'ui:options': {
size: {
lg: 15,
},
},
doShow: isFilled('groups')
}
}
]
}
...
render() {
return (
<div>
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields}
/>
</div>
)
}

Categories