Consuming ReactContext (specifically) Consumers across multiple packages - reactjs

Context
I created a Context and exported both Producer and Consumer. I am now wrapping my app level example with Producer. I want to be able to consume ContextConsumer inside a module which is served by a different package.
However, when I try to use ContextConsumer inside such module, it throws out the error, cannot call function of undefined.
Code Structure
The below is how I have my code structured.
Context-v1 package
Context.js
import React, { Component, createContext } from 'react';
import PropTypes from 'prop-types';
import ZIndexUtils from './zIndexUtils';
const { node } = PropTypes;
const { Provider, Consumer: IDSContextConsumer } = createContext();
class IDSContextProvider extends Component {
static propTypes = {
children: node.isRequired
};
state = {
topZIndex: ZIndexUtils.getTopZIndex(),
incrementTopZIndex: ZIndexUtils.incrementTopZIndex
};
render() {
return (
<Provider
value={{
topZIndex: this.state.topZIndex,
incrementTopZIndex: this.state.incrementTopZIndex
}}
>
{this.props.children}
</Provider>
);
}
}
export { IDSContextProvider };
export default IDSContextConsumer;
index.js
import IDSContextConsumer, { IDSContextProvider } from './Context';
export {
IDSContextProvider,
IDSContextConsumer
};
Dropdown-v1 package
This component makes use of another component called as Menu-v1 which is where I am trying to use Consumer to access the increment function that I am passing down from app level example.
import { IDSContextConsumer } from '#ids/context-v1';
...
...
render() {
return (
<IDSContextConsumer>
{
(context) => (
<ZIndex assignZIndex getTopZindex={context.incrementTopZIndex}>
<MenuList
className={className}
autoFocus={autoFocus}
aria-label={this.props['aria-label']}
onKeyDown={this.handleKeyDown}
onBlur={this.handleMenuBlur}
reference={reference}
ref={refReactDom}
style={style}
>
{items}
</MenuList>
</ZIndex>
)
}
</IDSContextConsumer>
)
}
App example
Finally, I am trying to use this Dropdown module into my app. Which is where I expect to see the incrementer function be passed in via context.
import { IDSContextProvider } from '#ids/context-v1';
import { Dropdown, MenuItem } from '#ids/dropdown';
render() {
return (
<div>
<IDSContextProvider>
<Modal
open={this.state.openModalDialog}
onCloseModalDialog={this.handleModalClosing}
title="Modal with Dropdown"
>
<Dropdown
onBlur={() => console.log('Dropdown blurrrrr')}
label="Name"
value={this.state.value}
onChange={this.handleDropdownChange}
>
<MenuItem value="1">Banana</MenuItem>
<MenuItem value="2">Apple</MenuItem>
<MenuItem value="3">Guava</MenuItem>
<MenuItem value="4">Mango</MenuItem>
</Dropdown>
</Modal>
</IDSContextProvider>
<button className="divRenderButton" onClick={this.handleModalOpening}>Open Modal</button>
</div>
);
}
Modal component in my example
<Portal>
<Transition
enterClassName={`${prefix}--slideIn`}
transition={open ? transitions.enter : transitions.exit}
onEntered={onShow}
onExited={onClose}
exitClassName={`${prefix}--slideOut`}
unmountOnExit
>
<IDSContextConsumer>
{
(context) => (
<ZIndex
assignZIndex={open}
underlay
getTopZindex={context.incrementTopZIndex}
>
<div
className={open ? 'divContainerWithUnderlay' : ''}
>
{
open &&
<div>
<h1 className="divContent">{title}</h1>
{
this.props.children ? this.props.children : null
}
<button className="divRenderButton" onClick={this.closeModalDialog}>Close Modal</button>
</div>
}
</div>
</ZIndex>
)
}
</IDSContextConsumer>
</Transition>
</Portal>
Requesting earnest help on this. I have tried the solutions from this forum before and cannot find any approach where the modules from different packages share the context.

Related

React transition and React reveal error on enter component

I'm trying to implement a transition effect in my custom toast. I'm using react-transition-group and react-reveal to do it. The documentation seems very simple and i just followed this steps https://www.react-reveal.com/examples/advanced/todo/
The problem is that it is olny working when the toast is closing. It dosn`t work when appear.
import Toast from ".";
import { useContext } from "react"
import { ToastStateContext } from "./context";
import { TransitionGroup } from "react-transition-group";
import Fade from 'react-reveal/Fade';
export default function ToastContainer() {
const { toasts } = useContext(ToastStateContext);
return (
<>
{toasts && (
<div className="absolute top-24 pt-1 right-2">
<TransitionGroup enter exit>
{
toasts.map((toast) => {
return (
<Fade key={toast.id} top>
<Toast
id={toast.id}
key={toast.id}
type={toast.type}
message={toast.message}
/>
</Fade>
)
})
}
</TransitionGroup>
</div>
)}
</>
);
}

Custom icon wrapper component

I want to use react-feather icons but within a standard size button component I created.
To use a react-feather component I need to create it like so <Camera size={18} /> or <Icon.Camera size={18} /> however, I don't want to keep on repeating the size and color and any other prop that stays the same.
How can I make the icon type dynamic within my Button.
I know this form is invalid but I am looking for a way that is and that is the best I could write to present my thought.
import React, { FC } from "react";
import * as Icons from "react-feather";
interface IconProps {
icon?: Icons.Icon;
}
const IconComponent: FC<IconProps> = (props) => {
return (
<button>
{props.icon && <Icons[props.icon] size={18} />}
{props.children}
</button>
);
};
export default IconComponent;
The IconComponent component below should do. You can see it live in this codesandbox.
// Icon.tsx
import React, { ReactNode } from "react";
import { Icon } from "react-feather";
interface IconComponentProps {
icon?: Icon;
children: ReactNode;
}
const IconComponent = ({ icon: FeatherIcon, children }: IconComponentProps) => {
return (
<button>
{FeatherIcon && <FeatherIcon size={18} />}
{children}
</button>
);
};
export default IconComponent;
There you could use it anywhere:
// App.tsx
import IconComponent from "./Icon";
import { Camera, Airplay } from "react-feather";
import "./styles.css";
export default function App() {
return (
<div className="App">
<IconComponent icon={Camera}>
<h1>Camera</h1>
</IconComponent>
<IconComponent icon={Airplay}>
<h1>Airlpay</h1>
</IconComponent>
<IconComponent>
<h1>No Icon</h1>
</IconComponent>
</div>
);
}

Why I can't use an imported component inside a functional component in React?

I am new to React. For the code readability, instead of in-line styled button, I want to write it as a separate class component. I created a customed button 'addImageButton'and imported it to another .js file. It doesn't render the customer button when I try to use it within a functional component. How can I make the functional component be able to use the imported button? Thanks!
//addImageButton.js
import React, { Component } from "react";
class addImageButton extends Component {
render() {
return (
<div>
<button
style={{
borderStyle: "dotted",
borderRadius: 1,
}}
>
<span>Add Image</span>
<span>Optional</span>
</button>
</div>
);
}
}
export default addImageButton;
//AddNewTaskButton.js
import React, { Component } from "react";
import Modal from "react-modal";
**import addImageButton from "../addImageButton";**
class AddNewTaskButton extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
};
this.setShow = this.setShow.bind(this);
this.closeShow = this.closeShow.bind(this);
this.addTaskModal = this.addTaskModal.bind(this);
}
setShow() {
this.setState({
show: true,
});
}
closeShow() {
this.setState({
show: false,
});
}
addTaskModal = () => {
return (
<div>
<Modal
isOpen={this.state.show}
onRequestClose={() => this.closeShow()}
>
**<addImageButton />**
</Modal>
</div>
);
};
render() {
return (
<div>
<button onClick={() => this.setShow()}>
<img src={addIcon} alt={text}></img>;
<span>text</span>
</button>
<this.addTaskModal className="modal" />
</div>
);
}
}
export default AddNewTaskButton;
Easier way would be to just use functional components. Also, react components should be upper case, like so:
export default function AddImageButton() {
return (
<div>...</div>
)
}
create a different component for Modal
import Modal from './Modal'
import AddImageButton from './AddImageButton'
function AddTaskModal() {
return (
<div>
<Modal> <AddImageButton/> </Modal>
</div>
)
}
then
import AddTaskModal from './AddTaskModal'
function AddNewTaskButton() {
return (
<div>
<AddTaskModal/>
</div>
)
}
I don't know your file directories, so I just put randomly.
as for your question, try to make the AddImageButton as a class and see if it renders then. If it doesn't it might be due to something else. Do you get errors? Also maybe create the AddTaskModal class separately and render it out as a component. Maybe that'll help

Visibility sensor is not working with counterup

I have been trying to implement react counterup along with react-visibility sensor. I wish to show the couterup only after that section is visible in the viewport. So, Using the visibility sensor to load it. But, it's not working and below is the error
"Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object."
Sample code is below
import React from 'react';
import CountUp, { startAnimation } from 'react-countup';
const CounterSection= (props) => {
const VisibilitySensor = require('react-visibility-sensor');
function onChange(isVisible) {
console.log('Element is now %s', isVisible ? 'visible' : 'hidden');
}
return (
<div>
<VisibilitySensor onChange={onChange}>
<div>...content goes here...</div>
<CountUp start={0} end={9350} duration={5} />+
</VisibilitySensor>
</div>
);
};
Assistance on this much appreciated. Thanks
The error was that the contents inside any component should be within a single block. Also, visibility sensor should be imported and removed from required. I added the contents inside div and the error was off.
import CountUp, { startAnimation } from 'react-countup';
import VisibilitySensor from 'react-visibility-sensor';
const CounterSection= (props) => {
function onChange(isVisible) {
console.log('Element is now %s', isVisible ? 'visible' : 'hidden');
}
return (
<div>
<VisibilitySensor onChange={onChange}>
<div>
<div>...content goes here...</div>
<CountUp start={0} end={9350} duration={5} />+
</div>
</VisibilitySensor>
</div>
);
};
As a previous poster pointed out, you're importing the library improperly, import it with an import statement at the top of the component. Using the ES5 require is correct for Node/Express but not for React.
You don't need to manually handle the onChange, that's apparently an older way of getting the two libraries to work with each other. Check out this answer pertaining to using VisibilitySensor and CountUp. I just tested it out and it works for me.
VisibilitySensor does not support the feature to track the first time visibility out of the box. I will put here an example that I have been using.
AppearVisibility.js
import React, { useState } from "react";
import VisibilitySensor from "react-visibility-sensor";
/**
* VisibilitySensor does not implement some kind of funcionality to track first time
* visibility. This component extends VisibilitySensor compoment to provide this
* feature. Just use `hasBeenVisible` render prop instead of `isVisible`.
*
* https://github.com/joshwnj/react-visibility-sensor/issues/117#issuecomment-686365798
*/
const AppearSensor = ({
onChange,
children,
...rest
}) => {
const [hasBeenVisible, setHasBeenVisible] = useState(false);
return (
<VisibilitySensor {...rest} onChange={(isVisible) => {
if (isVisible) setHasBeenVisible(true)
if (onChange) onChange(isVisible)
}}>
{
({
isVisible,
...restRenderProps
}) => {
return children({ isVisible, ...restRenderProps, hasBeenVisible })
}
}
</VisibilitySensor>
);
};
AppearSensor.propTypes = VisibilitySensor.propTypes
AppearSensor.defaultProps = VisibilitySensor.defaultProps
export default AppearSensor;
CounterSection.js
import React from 'react';
import CountUp from 'react-countup';
const CounterSection = (props) => {
return (
<div>
<div>...content goes here...</div>
<AppearSensor>
{({ hasBeenVisible }) =>
hasBeenVisible
? <CountUp
start={0}
end={9350}
duration={5} />
: <span>9350</span>
}
</AppearSensor>
</div>
);
};
export default CounterSection;
import React, {Fragment,Component} from 'react';
import CountUp, { startAnimation } from 'react-countup';
import ReactVisibilitySensor from "react-visibility-sensor";
class Test extends Component {
render() {
return (
<Fragment>
<h1 className={'Countnumber'}>
<CountUp start={0} end={100} delay={0}>
{({ countUpRef,start }) => (
<ReactVisibilitySensor onChange={start} delayedCall={true}>
<span ref={countUpRef} />
</ReactVisibilitySensor>
)}
</CountUp>
</h1>
</Fragment>
);
}
}
export default Test;
This definitely works

reactstrap tooltip dynamic id

I am developing a react application and using reactstrap.
I am using Tooltip Component of reactstrap which requires a target attribute, a value of target element's id. This id is being geneated dynamically and seems reactstrap tooltip doesn't like it.
Component looks like:
MovieCard.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Col, Card, CardImg, CardBody, CardTitle, CardSubtitle, CardText, Button, Tooltip } from 'reactstrap';
import { LimitedTextTitle } from '../custom-styled/CustomStyledComponents';
class MovieCard extends Component {
constructor (props) {
super(props);
this.state = {
open: false
};
this.toggle = this.toggle.bind(this);
}
toggle () {
this.setState({
open: !this.state.open
})
}
render () {
const { imdbID, Title, Year, Rated, Plot, Country, Poster } = this.props.movie;
return (
<Col md="4">
<Card>
<CardImg
top
width="100%"
src={Poster}
alt="blah"
/>
</Card>
<CardBody>
<CardTitle>
<LimitedTextTitle id={imdbID}>
{`${Title} - (${Year})`}
</LimitedTextTitle>
<Tooltip placement='top' target={imdbID} isOpen={this.state.open} toggle={this.toggle}>
{Title}
</Tooltip>
</CardTitle>
<CardSubtitle>{`Rated: ${Rated} Country: ${Country}`}</CardSubtitle>
<CardText>{Plot}</CardText>
<Button>Read More</Button>
</CardBody>
</Col>
);
}
}
MovieCard.propTypes = {
movie: PropTypes.object.isRequired // eslint-disable-line
};
export default MovieCard;
Any suggestions?
react vesion 16.2.0
reactstrap 5.0.0-alpha.4
Was dealing with a similar problem.
Adding the code as an answer because i cannot add a comment above...
Hope it will help you or anyone else who will come across this question.
Description:
Use reactstrap tooltip for elements that are getting generated dynamically.
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Button, Tooltip } from 'reactstrap';
class App extends React.Component {
state = {};
toggle = targetName => {
if (!this.state[targetName]) {
this.setState({
...this.state,
[targetName]: {
tooltipOpen: true
}
});
} else {
this.setState({
...this.state,
[targetName]: {
tooltipOpen: !this.state[targetName].tooltipOpen
}
});
}
};
isToolTipOpen = targetName => {
return this.state[targetName] ? this.state[targetName].tooltipOpen : false;
};
render() {
return (
<div>
{[1, 2, 3, 4, 5, 6].map((x, i) => (
<div key={`div-${i}`}>
<Button color="link" id={`btn-${i}`}>
{x}
</Button>
<Tooltip
placement="right"
isOpen={this.isToolTipOpen(`btn-${i}`)}
target={`btn-${i}`}
toggle={() => this.toggle(`btn-${i}`)}>
Hello world!
</Tooltip>
</div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
react: 16.9.0
reactstrap: 8.0.1
https://codesandbox.io/embed/angry-taussig-fup7i?fontsize=14
EUREKA I GOT IT!!! Building on Meir Keller's answer, there's no need to check if that state for the tooltip already exist. If it doesn't exist, it's false by default...
So long as state is defined, even if it's an empty state, this works.
This is using reactstrap's Popover, but it's the same concept.
import React, { Component, Fragment } from 'react';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css'
import { Container, Row, Col, Input, Button, Popover } from 'reactstrap';
class App extends Component {
state = {};
toggle = (target) => {
// console.log(typeof target) // make sure this is a string
this.setState({
...state,
[target]: !this.state[target]
});
};
render() {
return (
<Container>
{["Hello", "Greetings"].map((name) => (
<Row>
<Fragment>
<Button id={name} type="button">{name}</Button>
<Popover placement="right"
isOpen={this.state[`${name}`]}
target={name}
toggle={() => this.toggle(`${name}`)}>
<PopoverBody>
You've got mail. Did you know?
</PopoverBody>
</Popover>
</Fragment>
</Row>
))}
</Container>
);
}
}
export default App;
Create a new component in modular or component directory and paste this code
import React, { useState } from "react";
import { Tooltip } from "reactstrap";
const TooltipItem = props => {
const { position='top', id } = props;
const [tooltipOpen, setTooltipOpen] = useState(false);
const toggle = () => setTooltipOpen(!tooltipOpen);
return (
<span>
<span id={"tooltip-" + id}>
{props.children}
</span>
<Tooltip
placement={position}
isOpen={tooltipOpen}
target={"tooltip-" + id}
toggle={toggle}
>
{props.title}
</Tooltip>
</span>
);
};
export default TooltipItem;
Now import and use this tooltip component
import TooltipItem from "../Tooltip";
<TooltipItem id={'edit' + data.id} title={'Edit Store'}>
<i className="fas fa-edit pointer" onClick={() => this.onEditClick(data)}/>
</TooltipItem>
I will Like to add an answer for it as already many people have mentioned many ways to deal with the problem.
But reactStrap works perfectly fine, mistakes most of the beginners are doing that while creating id they are using special characters like:
- _ / # and it can even be a space
Just keep the id a very simple combination of chars and numbers reactstrap will work totally fine
New component UncontrolledTooltip will solve the problem. Just use
<UncontrolledTooltip
placement="right"
target={`btn-${i}`}
>
{props.title}
</UncontrolledTooltip>
I tried a lot of solutions and was still having trouble with Reactstrap Tooltip crashing when the target element is not in the Dom.
I combined a couple other solutions that people posted and this is the only way it worked for me. Conditional rendering FTW.
const ElementWithTooltip = ({
dynamicIdentifier, // string, number, w/e
}): ReactElement => {
// Target element state.
const [isTargetReady, setIsTargetReady] = useState(false);
// Target element ref.
const tooltipRef = useRef(null);
// Hook to recognize that the target is ready.
useEffect(() => {
const targetElement = tooltipRef.current;
if (targetElement) {
setIsTargetReady(true);
}
}, [tooltipRef.current]);
// TSX.
return (
<>
<span ref={tooltipRef}>This is the target element</span>
{isTargetReady && <UncontrolledTooltip autohide={false} target={tooltipRef}>
Tooltippy text stuff
</UncontrolledTooltip>}
</>
);
The imdbID most probably is starting with digit i.e. 123abcdefghijklmno1234567890
Remember that tooltips can't work in that case when ID starts with a number i.e. the Tooltip's target cannot start with an integer.
all you need to do here is, change this:
<CardTitle>
<LimitedTextTitle id={imdbID}>
{`${Title} - (${Year})`}
</LimitedTextTitle>
<Tooltip placement='top' target={imdbID} isOpen={this.state.open} toggle={this.toggle}>
{Title}
</Tooltip>
</CardTitle>
to this:
<CardTitle>
<LimitedTextTitle id={`movie-${imdbID}`}>
{`${Title} - (${Year})`}
</LimitedTextTitle>
<Tooltip placement='top' target={`movie-${imdbID}`} isOpen={this.state.open} toggle={this.toggle}>
{Title}
</Tooltip>
</CardTitle>
You can avoid using state by simply switching to UncontrolledTooltip which handles all the toggle itself without asking you to handle that explicitly, like:
<CardTitle>
<LimitedTextTitle id={`movie-${imdbID}`}>
{`${Title} - (${Year})`}
</LimitedTextTitle>
<UncontrolledTooltip placement='top' target={`movie-${imdbID}`}>
{Title}
</UncontrolledTooltip>
</CardTitle>
Rendering dynamic content in tooltip in react js is very simple.
Use ReactTooltip.
For full understanding check below example.
Here I am adding requestId in tooltip as dynamically.
{
completedTransactions.map((item, id) => (
<tr key={id + 1}>
<td>{id + 1}</td>
<td>
<span data-tip={item.requestId} data-for="registerTip">
{item.TransactionId}
</span>
<ReactTooltip id="registerTip" place="top" />
</td>
<td>{item.groupName}</td>
<td>{item.purposeName}</td>
<td>{dateFormat(item.update, "dd-mm-yyyy hh:mm tt")}</td>
</tr>
));
}

Resources