wrap children for react component which has children restricted to certain types - flowtype

I have a component that I restricted to have certain types of children using flow types. However, now I have several situations where it is convenient to wrap those components in other component that just returns one of the valid components but with some defaults attached. Even grouping some of those valid components using react fragments gives me weird errors that I am unable to debug.
Here is a piece of sample code (that you can run on flow playground):
//#flow
import React from 'react'
import type { Node, Element, ChildrenArray } from 'react'
type ItemType = Element<typeof ListItem | typeof ListHeader>
type Props = {
children: ChildrenArray<ItemType>,
}
function List({ children }: Props) {
return <div>{children}</div>
}
type ItemProps = {
children: Node,
onClick?: () => void,
}
function ListItem({ children, onClick }: ItemProps) {
return <div onClick={onClick}>{children}</div>
}
type HeaderProps = {
children: Node,
}
function ListHeader({ children }: HeaderProps) {
return <div>{children}</div>
}
const Row = ({ left, right }: { left: string, right: Node }): Node => {
return (
<ListItem>
{left}
<span>{right}</span>
</ListItem>
)
}
const x = () => (
<List>
<Row left="gender" right="${gender}" />
<Row left="birthDate" right="xxx" />
<Row left="Number" right="xxx" />
<Row left="sendEnrollmentLetter" right="stuff" />
</List>
)
What is the correct way of following such pattern?
On flow playground you will not see errors because, if you open the console you will see that it just blows up.
This is the concrete error I'm getting on my project:
Cannot create `List` element because in array element of property `children`:
Either property `children` is missing in object type [1] but exists in `HeaderProps` [2] in property `props`.
Or property `children` is missing in object type [1] but exists in `ItemProps` [3] in property `props`.

Running your code snippet on a project gave the following errors for the child components inside x
Cannot create `Parent` element because Could not decide which case to select, since case 1 [1] may work but if it doesn't case 2 [2] looks promising too. To fix add a type annotation to return [3].Flow(incompatible-type)
Assuming these are the errors you're getting, flow can't infer what the type of your components should be, so it wants you to manually annotate them. Adding return type of Node should do it
const Wrapper = (): Node => (
<>
<Foo disabled />
<Bar />
</>
);
const Defaults = (): Node => <Foo disabled={false} />;
const x = (
<Parent>
<Wrapper />
<Wrapper />
<Defaults />
</Parent>
);
Though as a side note I'd strongly recommend you move to using import * as React from 'react'; so you can use React.Node instead. Otherwise Node from react will conflict with usage of html Node in the future.

Related

Scroll Sync mapped components

I'm trying to scroll sync my mapped components in react.
I have tried couple scroll sync libraries but I don't think they work on mapped components.(I would appreciate it if someone could help me out using scroll sync libraries. I have tried placing scrollSyncNode / scrollSyncPane inside the map function wrapping the component but it doesn't work.)
The below are two functions which I made where ref1 and ref2 scroll at the same time. I have tested the function with two independent components(not mapped) and they work fine.
I'm not completely sure if its possible but I want all the mapped components to equally share ref2.
the onScroll event will trigger the function.
I would really appreciate it if someone could give me a thorough guide to how I could scroll sync an independent component with the mapped components.
below is my parent component's code where both the independent component and mapped components are located:
const refOne = useRef();
const refTwo = useRef();
const handleScrollFirst = scroll => {
refTwo.current.scrollLeft = scroll.target.scrollLeft;
};
const handleScrollSecond = scroll => {
refOne.current.scrollLeft = scroll.target.scrollLeft;
};
return (
<ContentsWrapper>
<ThisIsRefOneDiv
style={{
width: '5vw',
overflowX: 'scroll',
}}
onScroll={handleScrollFirst}
ref={div1}
>
testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
</ThisIsRefOneDiv>
{data.map((group, idx) => (
<Card key={idx} color={group.color}>
<Objective data={group} />
{state && (
<Content>
<GanttData
data={group}
**onScroll={handleScrollSecond}
ref={refTwo}
/>
</Content>
)}
</Card>
))}
</ContentsWrapper>
);
}

Change parent component background on hover in reactJS

I have following React code
Code
What I would like is to when I hover my "E-commerce" picture App component background should change on "E-commerce" picture background.
So respectively and for other pictures.
I will be very grateful if you help me solve this problem.
Context, according to the React docs, should be used only for truly global state like current user or theme. Using context for components makes them less reusable.
updated code
Your component tree is App -> SolutionBox -> SolutionItem.
You want to "react" to an event in SolutionItem in App but there is SolutionBox inbetween them so you have to thread the event thru SolutionBox to App.
Step 1
Add a prop to SolutionItem called on OnHover, this will be a function call back that any parent component can use to react to changes.
function SolutionsSectionBoxItem({ solutionIMG, onHover }) {
let callOnHover = state => {
if (_.isFunction(onHover)) {
onHover(state);
}
};
return (
<div className="solutions-section-item-box">
<img
src={solutionIMG}
alt=""
onMouseEnter={() => {
callOnHover(true);
}}
onMouseLeave={() => {
callOnHover(false);
}}
className="solutions-section-item-img"
/>
</div>
);
}
Step 2
Add a prop to SolutionBoxItem called on BGChanged, this will again be a function call back that will be called when any solutionitem onhover happens. This function will take a menuName string and pass either the current menu name or default.
function SolutionsSectionBox({ onBGChanged }) {
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
return (
<div className="solutions-section-box-box">
<SolutionItem
solutionIMG={Ecommerce}
onHover={state => {
callBGChanged(state === true ? "Ecommerce" : "default");
}}
/>
<SolutionItem
solutionIMG={SalesMarketing}
onHover={state => {
callBGChanged(state === true ? "SalesMarketing" : "default");
}}
/>
<SolutionItem
solutionIMG={Analytics}
onHover={state => {
callBGChanged(state === true ? "Analytics" : "default");
}}
/>
<SolutionItem
solutionIMG={Middleware}
onHover={state => {
callBGChanged(state === true ? "Middleware" : "default");
}}
/>
</div>
);
}
Step 3
In the App component listen for the changes. In here we now set state when ever the mouse enters or leaves a solution item. From here you have to change the background, you are using css to control the background url, this will be harder since you now need css class for each background type. You could use the bgImage state value to change the name of the extra css className like 'AppSalesMarketing', 'AppEcommerce', etc.
export default function App() {
const [bgImage, setbgImage] = useState(E);
const onBGChanged = menuName => {
setbgImage(menuName);
};
return (
<div className={`App ${bgImage === "default" ? "" : `App${bgImage}`}`}>
<SolutionBox onBGChanged={onBGChanged} />
</div>
);
}
In CSS
Leave the original App class but based on the bgImage value add an additional one using the name of the bgImage + App like below to cascade down the updated background-image value.
.AppEcommerce {
background-image: url(https://placekitten.com/600/600);
}
.AppSalesMarketing {
background-image: url(https://placekitten.com/500/800);
}
.AppAnalytics {
background-image: url(https://placekitten.com/800/500);
}
.AppMiddleware {
background-image: url(https://placekitten.com/700/700);
}
Extra
I added lodash to test that the incoming props are functions before I call them, it is good to do defensive programming because you never know who may use your component in the future.
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
Two ways to solve the problem. One is passing down a function to update state, the other is to useContext. In this case it makes sense to use context because you are passing down a function through multiple components that do not care about the function.
First thing to do is make the background image dynamic in the div's style and use context:
// Put this outside the component
export const BackgroundContext = React.createContext(null);
// -- snip
const [backgroundImage, setBackgroundImage] = useState(Ecommerce);
const updateBackgroundImage = newImage => setBackgroundImage(newImage);
// -- snip
<BackgroundContext.Provider value={updateBackgroundImage}>
<div className="App" style={{ backgroundImage: `url(${backgroundImage})` }}>
{/* -- snip */}
</BackgroundContext.Provider>
Now in your SolutionsSectionBoxItem component you can import the background context:
import BackgroundContext from "../App";
Then using that context and react's mouseover api, update the selected background image:
const setBackgroundImage = useContext(BackgroundContext);
// -- snip
<img onMouseOver={() => setBackgroundImage(solutionIMG)} {/* -- snip -- */} />
You can read more here: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down

React JS: How to parse JSX in string?

I have a component Example which accepts a string as a prop and styles it by wrapping it in a container having some CSS classes.
This is what Example looks like:
interface Props {
text: string;
}
const Example: React.FC<Props> = ({ text, }) => (
<span className="class-a class-b class-c">
{text}
</span>
);
I'm using Example in Info Component as follows:
const Info: React.FC = () => (
<div className="class-x class-y class-z">
<p>
I am a simple statement
<Example text=" but I need some styling" />
!!!!
</p>
</div>
);
Now, this Info component is used in Parent component as follows:
const Parent: React.FC = () => (
<Info />
);
Challenge:
I need to make this Info component re-usable as follows:
const myJSON = {
val1: "I am a simple statement <Example text=" but I need some styling"
/>!!!!",
val2: "Another statement <Example text=" but I need some styling"
/>!!!!",
}
const Parent: React.FC = () => (
<Info data={myJSON.va1l} />
<Info data={myJSON.va2l} />
Now, what happening is that React is unable to parse Example as a component in the prop and displays it as follows in the browser:
I am a simple statement <Example text=" but I need some styling" />!!!!
Some other statement <Example text=" with this styling" />!!!!
Another again <Example text=" another styling" />!!!!
Is there any way or library to make React parse the as a component?
Thanks!
You could change your approach to use children props
const Parent: React.FC = () => (
<Info>I am a simple statement <Example text=" but I need some styling" />!!!!"</Info>
<Info>"Some other statement <Example text=" with this styling" />!!!!"</Info>
<Info>"Another again <Example text=" another styling" />!!!!" </Info>
);
const Example: React.FC<Props> = ({ children }) => (
<span className="class-a class-b class-c">
{children}
</span>
);
const Info: React.FC = () => (
<div className="class-x class-y class-z">
<p>
I am a simple statement
<Example>{children}</Example>
!!!!
</p>
</div>
);
This is exactly what the children prop is for.
When putting elements within a components JSX like so :
const Parent: React.FC = () => (
<Info>
I am a simple statement
<Example text=' but I need some styling' />
!!!!
</Info>
);
The content will be sent to the children prop automatically by React, which can be used within your component :
const Info: React.FC = ({ children }) => (
<div className='class-x class-y class-z'>
<p>{children}</p>
</div>
);
You can use ReactDomServer.renderToString() method. It converts React components to string. You can find the documentation here.
The implementation isn't perfect but it does render the content in the innermost child.
Parent Component
export default function App() {
let content = ReactDOMServer.renderToString(
<Example text="but I need some styling" />
);
content += " This is extra string";
return <Info content={JSON.stringify(content)} />;
}
Here you can convert the React component into a string and concatenate any string you may want to. If you get the component already inside a string, then I suggest you use Regex to extract the component out.
Child Component
const Info = ({ content }) => {
return <Example text={content} />;
};
Once you have the component stringified you can pass it as a prop.
Inner most Child
const Example = ({ text }) => {
return (
<span
className="class-a class-b class-c"
dangerouslySetInnerHTML={{ __html: text }}
/>
);
};
In this component you can use the dangerouslySetInnerHTML method to set HTML via code. Check out the docs here
As already suggested this is really not a good approach and will leave you vulnerable to cross-site scripting (XXS) attacks. I suggest that you change the JSON response you are getting if you have control over the backend.

Pagination shows extra item when at start or end of items

My pagination component is showing an extra, undesired item when the user is at or near either the beginning or the end of the item list. I believe I am matching my props exactly to what I am inputting in the documentation 'options' example, so I'm not sure where I'm going wrong.
Current behavior GIF
Desired behavior GIF
class MyPagination extends React.Component<PaginationProps> {
static defaultProps = {
boundaryRange: 0,
defaultActivePage: 1,
showEllipsis: false,
siblingRange: 2,
};
render() {
const {
boundaryRange,
defaultActivePage,
showEllipsis,
siblingRange,
totalPages,
} = this.props;
return (
<Pagination
boundaryRange={boundaryRange}
defaultActivePage={defaultActivePage}
ellipsisItem={showEllipsis ? undefined : null}
siblingRange={siblingRange}
totalPages={totalPages}
/>
);
}
}
export default MyPagination;
I'm instantiating like this:
<MyPagination totalPages={50} />
And my props are coming out as expected:
This issue is a bug and was fixed in semantic-ui-react#0.84.0 by https://github.com/Semantic-Org/Semantic-UI-React/pull/3271.

How to resolve FOUC in React.js

I have built react.js site from create-react-app.
But in production mode, there is FOUC because styles are loaded after html is rendered.
Is there any way to resolve this? I have been searching google for answers, but haven't found proper one yet.
FOUC
FOUC - so called Flash of Unstyled Content can be as very problematic as so many tries of solving this issue.
To the point
Let's consider following configuration of routing (react-router):
...
<PageLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/example' component={Example} />
<Switch>
</PageLayout>
...
where PageLayout is a simple hoc, containing div wrapper with page-layout class and returning it's children.
Now, let's focus on the component rendering based on route. Usually you would use as component prop a React Compoment. But in our case we need to get it dynamically, to apply feature which helps us to avoid FOUC. So our code will look like this:
import asyncRoute from './asyncRoute'
const Home = asyncRoute(() => import('./Home'))
const Example = asyncRoute(() => import('./Example'))
...
<PageLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/example' component={Example} />
<Switch>
</PageLayout>
...
to clarify let's also show how asyncRoute.js module looks like:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Loader from 'components/Loader'
class AsyncImport extends Component {
static propTypes = {
load: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
}
state = {
component: null
}
toggleFoucClass () {
const root = document.getElementById('react-app')
if (root.hasClass('fouc')) {
root.removeClass('fouc')
} else {
root.addClass('fouc')
}
}
componentWillMount () {
this.toggleFoucClass()
}
componentDidMount () {
this.props.load()
.then((component) => {
setTimeout(() => this.toggleFoucClass(), 0)
this.setState(() => ({
component: component.default
}))
})
}
render () {
return this.props.children(this.state.component)
}
}
const asyncRoute = (importFunc) =>
(props) => (
<AsyncImport load={importFunc}>
{(Component) => {
return Component === null
? <Loader loading />
: <Component {...props} />
}}
</AsyncImport>
)
export default asyncRoute
hasClass, addClass, removeClass are polyfills which operates on DOM class attribute.
Loader is a custom component which shows spinner.
Why setTimeout?
Just because we need to remove fouc class in the second tick. Otherwise it would happen in the same as rendering the Component. So it won't work.
As you can see in the AsyncImport component we modify react root container by adding fouc class. So HTML for clarity:
<html lang="en">
<head></head>
<body>
<div id="react-app"></div>
</body>
</html>
and another piece of puzzle:
#react-app.fouc
.page-layout *
visibility: hidden
sass to apply when importing of specific component (ie.: Home, Example) takes place.
Why not display: none?
Because we want to have all components which rely on parent width, height or any other css rule to be properly rendered.
How it works?
The main assumption was to hide all elements until compoment gets ready to show us rendered content. First it fires asyncRoute function which shows us Loader until Component mounts and renders. In the meantime in AsyncImport we switch visibility of content by using a class fouc on react root DOM element. When everything loads, it's time to show everything up, so we remove that class.
Hope that helps!
Thanks to
This article, which idea of dynamic import has been taken (I think) from react-loadable.
Source
https://turkus.github.io/2018/06/06/fouc-react/

Resources