ReactJS - Component with optional props inside React Context - javascript

In my app, there is a bottom sheet in the top level of my navigation system. As it is showed/hidden when the user interacts with the elements of other deeper screens inside the navigation system, I have had to wrap it inside a React Context. Take a look at this idea.
Now, imagine that this context is rendering my custom bottom sheet component:
function MessageOptions({ onDeleteButtonPress, onReportButtonPress, ... }) {
...
const handleOnDeleteButtonPress = () => {
...
onDeleteButtonPress?.();
}
const handleOnReportButtonPress = () => {
...
onReportButtonPress?.();
}
...
return (
<View style={globalStyles.overlayed} pointerEvents="box-none">
<OptionsBottomSheet
ref={menuRef}
snapPoints={[155, 0]}
initialSnap={1}
topItems={getTopItems()}
bottomItems={getBottomItems()}
onCloseEnd={onCloseEnd}
/>
</View>
);
}
As you can see, it is receiving two optional props, "onDeleteButtonPress" and "onReportButtonPress".
My idea is to consume the context which renders this custom bottom sheet component, in all my chat bubbles, just to open the bottom sheet when they are long pressed.
This is the context provider:
import React, { createContext, useRef } from "react";
import useStateWithCallback from "../../hooks/useStateWithCallback";
import MessageOptions from "../../components/Messaging/Options/MessageOptions";
const MessageOptionsContext = createContext(null);
export default MessageOptionsContext;
export function MessageOptionsProvider({ children }) {
...
return (
<MessageOptionsContext.Provider
value={{
open,
close,
}}
>
{children}
<MessageOptions
ref={messageOptionsRef}
message={message}
// onReportButtonPress={onReportButtonPress}
// onDeleteButtonPress={onDeleteButtonPress}
onCloseEnd={handleOnCloseEnd}
/>
</MessageOptionsContext.Provider>
);
}
When the user presses the delete button, I will be removing the specific message from the screen's messages list state.
How can I pass the methods from the parent (ChatScreen) of the consumer (Bubble) to the provider (MessageOptionsProvider)?
This is the flow of my app:
ChatScreen --> (contains the messages list (array) state)
|
|
---> BubbleList
|
|
---> Bubble (consumes the bottom sheet context)
I am doing this to avoid repeating my self in different routes of my app, but for being transparent, I am a little stuck in this use case.
Any ideas? It seems to be impossible?

Related

React - Passing callbacks from React Context consumers to providers

I have the following context
import React, { createContext, useRef } from "react";
const ExampleContext = createContext(null);
export default ExampleContext;
export function ExampleProvider({ children }) {
const myMethod = () => {
};
return (
<ExampleContext.Provider
value={{
myMethod,
}}
>
{children}
<SomeCustomComponent
/* callback={callbackPassedFromConsumer} */
/>
</ExampleContext.Provider>
);
}
As you can see, it renders a custom component which receive a method as prop. This method is defined in a specific screen, which consumes this context.
How can I pass it from the screen to the provider?
This is how I consume the context (with a HOC):
import React from "react";
import ExampleContext from "../../../contexts/ExampleContext";
const withExample = (Component) => (props) =>
(
<ExampleContext.Consumer>
{(example) => (
<Component {...props} example={example} />
)}
</ExampleContext.Consumer>
);
export default withExample;
And this is the screen where I have the method which I need to pass to the context provider
function MyScreen({example}) {
const [data, setData] = useState([]);
const myMethodThatINeedToPass = () => {
...
setData([]);
...
}
return (<View>
...
</View>);
}
export default withExample(MyScreen);
Update:
I am trying to do this because in my real provider I have a BottomSheet component which renders two buttons "Delete" and "Report". This component is reusable, so, in order to avoid repeating myself, I am using a context provider.
See: https://github.com/gorhom/react-native-bottom-sheet/issues/259
Then, as the bottom sheet component which is rendered in the provider can receive optional props "onReportButtonPress" or "onDeleteButtonPress", I need a way to pass the method which manipulates my stateful data inside the screen (the consumer) to the provider.
You can't, in React the data only flows down.
This is commonly called a “top-down” or “unidirectional” data flow. Any state is always owned by some specific component, and any data or UI derived from that state can only affect components “below” them in the tree.
Your callbacks ("onReportButtonPress", "onDeleteButtonPress") must be available at provider's scope.
<ExampleContext.Provider
value={{
onReportButtonPress,
onDeleteButtonPress,
}}
>
{children}
</ExampleContext.Provider>;
Render SomeCustomComponent in Consumer component. This is the React way of doing things :)

Delete images if the user is leaving the component React

I have form with a drag and drop component where I can upload images, after this I send these pictures with axios to my backend and save it on server side and then re-render it in a preview mode. My problem is, that if a user uploads some pictures and after that he switches to another page without submitting the form the added pictures are staying on my server side for no reason.
So the question: I want to check inside my component if a user is leaving, show a prompt and if he clicks on the OK button I want to delete all the added pictures from my backend before leaving. How can I detect this?
My simplified snippet:
function MyComp(props) {
const [Images,setImages] = useState([]) // in this array I store the recieved image's URL
const [isBlocking,setIsBlocking] = useState(true) // used for prompt
const handleSubmit = (event) => {
event.preventDefault();
setIsBlocking(false)
}
return(
<Grid container className={classes.mainGrid} direction="row" spacing={2}>
<Grid item xs={4} xl={4}>
<Prompt when={isBlocking} message="There are unsaved datas. Are you sure you want to leave?"/>
<form className={classes.form} onSubmit={handleSubmit}>
... somecode
</Grid>
</Grid>
)
}
export default MyComp
Thanks in advance
Inside React Function Component you can call the prompt when the user is trying to leave , i.e when the component is unmounting
In Class Component of React you can use React componentWillUnmount() and in Function Component You can use useEffect cleanup function
Code for Function Component
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
export default function Home(props) {
useEffect(() => {
return function cleanup() {
alert("unmounting");
//call api and execute your code here
};
}, []);
return (
<div>
<Link to="/home">
On Click I will unmount this component and go to /home
</Link>
</div>
</Link>
);
}
Code for Class Component
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
export default class Test extends Component {
componentWillUnmount() {
alert('unmounting component');
//call your code here
}
render() {
return (
<div>
<Link to='/home'>
On Click I will unmount this component and go to /home
</Link>
</div>
);
}
}
You can check this codesandbox if you want any ref
When you leave the page, the component method componentWillUnmount() fires. I don't recall how this behaves if you were to simply close the browser window as opposed to just navigating away, nor do I recall how you can escape it and stay on the component, but that should at least be a good starting point for you. Obviously you'd have to do a class extending React.Component for this one instead of a straight function.

React Testing Library, Component Unit Tests

I am trying to build a test unit for my simple React Application using React Testing Library. I readed all docs and get stuck in it.
API was created by create React app. One of the feature is that user can change theme. There is setTheme hook that going to change theme "dark" and "light".
App.js
const App = () => {
const [theme, setTheme] = useState('dark');
return ( <div>
<Header theme={theme} setTheme={setTheme} />
</div>)
};
Header.js
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
const Header = props => {
return (
<header data-testid="header">
<h1><span className="highlight">Github Users</span></h1>
{props.theme === "dark" ?
<FontAwesomeIcon data-testid="button" icon="sun" size="2x" color="#dcba31" onClick={ () => props.setTheme('light') }/>
: <FontAwesomeIcon icon="moon" size="2x" color="#1c132d" onClick={ () => props.setTheme('dark') }/>}
</header>
);
}
export default Header;
In Header component I added arrow function that changes color of theme.
Now I am trying to write a test that's gonna test Header Component.
Expected result is that after first render Header component shall render icon "sun".
After user click on it header shall return icon "moon".
There is something that i try but it's not working as I mention.
Header.test.js
import React from 'react';
import { render, cleanup } from "#testing-library/react"
import '#testing-library/jest-dom/extend-expect';
import { act } from "react-dom/test-utils";
import Header from '../components/Header';
afterEach(cleanup);
describe("Header Component", () => {
it("first render should return a sun icon", () => {
const {getByTestId } = render(<Header />)
expect(getByTestId("header"). // What method uses here? To check if it is icon sun or moon ? )
})
it("after mouse click event should return a moon icon", () => {
const button = document.querySelector("svg"); // it is correct way to add a svg element as a button ?
act( () => {
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
})
expect(getByTestId("header"). // again what to write here to test it after click ?
})
})
I am sure that there is some other way to check first render and then after click what's Header component rendering. I think that problem is that there is another Component that is rendered conditionaly. If it is text there is no problem, but after render there is svg element with some attributes like icon="sun" / icon="moon".
Live version of project
Github Repo Link
Questions:
How to properly test that Header component ?
How to pass props in test for example I want to use that setTheme hook in test how to do it ?
There's many ways to do this and I can recommend the articles here https://kentcdodds.com/blog/?q=test to get you started. As for your current set-up I'd change some stuff that I find helpful writing unit tests:
Use data-testid to find elements, e.g. "first render should return a sun icon" can be confirmed by expect(getByTestId('svg-sun')).toBeDefined(), which is a pattern I like
Structure your it's like stubbing-rendering-assertions and only test one thing in each test, for instance, in your second it you're lacking a rendering part of the test
Regarding your question regarding passing the props, you can pass it as render(<Header theme={theme} setTheme={setThemeStub}/>) where const setThemeStub = jest.fn(). This allows you to make assertions as expect(setThemeStub).toBeCalledWith(...)

How to add a watcher for context change?

I am working on adding an analytics tracker to my react app. I want to primarily capture 2 things:
1) All click events.
2) All page change events.
I was trying to figure out how to approach this problem and found some help on SO with this:
How can I create a wrapper component for entire app?
The above post basically had me creating a parent wrapper and using the React Context API to pass data to the nested elements. The idea is great, but I'm still missing a few pieces here after reading the context API.
Heres what I have following that pattern.
Tracker.js
import PropTypes from "prop-types"
import * as React from "react"
import { connect } from "react-redux"
import TrackingManager from './TrackingManager'
import ScriptManager from "./ScriptManager"
import { isLeftClickEvent } from "../utils/Utils"
const trackingManager = new TrackingManager()
export const TrackerProvider = React.createContext()
/**
* Tracking container which wraps the supplied Application component.
* #param Application
* #param beforeAction
* #param overrides
* #returns {object}
*/
class Tracker extends React.Component {
constructor(props) {
super(props)
this.state = {
pageName: ''
}
}
componentDidMount() {
this._addClickListener()
this._addSubmitListener()
}
componentWillUnmount() {
// prevent side effects by removing listeners upon unmount
this._removeClickListener()
this._removeSubmitListener()
}
componentDidUpdate() {
console.log('TRACKER UPDATE')
}
pageLoad = pageName => {
console.log('LOADING PAGE')
this.setState({ pagename }, trackingManager.page(this.state))
}
/**
* Add global event listener for click events.
*/
_addClickListener = () => document.body.addEventListener("click", this._handleClick)
/**
* Remove global event listern for click events.
*/
_removeClickListener = () => document.body.removeEventListener("click", this._handleClick)
/**
* Add global event listener for submit events.
*/
_addSubmitListener = () => document.body.addEventListener("submit", this._handleSubmit)
/**
* Remove global event listern for click events.
*/
_removeSubmitListener = () => document.body.removeEventListener("submit", this._handleSubmit)
_handleSubmit = event => {
console.log(event.target.name)
}
_handleClick = event => {
// ensure the mouse click is an event we're interested in processing,
// we have discussed limiting to external links which go outside the
// react application and forcing implementers to use redux actions for
// interal links, however the app is not implemented like that in
// places, eg: Used Search List. so we're not enforcing that restriction
if (!isLeftClickEvent(event)) {
return
}
// Track only events when triggered from a element that has
// the `analytics` data attribute.
if (event.target.dataset.analytics !== undefined) {
let analyticsTag = event.target.dataset.analytics
console.log("Analytics:", analyticsTag)
trackingManager.event("eventAction", {"eventName": analyticsTag, "pageName": "Something"})
}
}
/**
* Return tracking script.
*/
_renderTrackingScript() {
/**
* If utag is already loaded on the page we don't want to load it again
*/
if (window.utag !== undefined) return
/**
* Load utag script.
*/
return (
<ScriptManager
account={process.env.ANALYTICS_TAG_ACCOUNT}
profile={process.env.ANALYTICS_TAG_PROFILE}
environment={process.env.ANALYTICS_TAG_ENV}
/>
)
}
render() {
return (
<TrackerProvider.Provider value={
{
state: this.state,
loadPage: this.pageLoad
}
}>
{this.props.children}
{this._renderTrackingScript()}
</TrackerProvider.Provider>
)
}
}
export default Tracker
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Switch, Route } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './lib/store'
import history from './lib/history'
import MyComp from './containers/components/MyComp'
import Tracker from './lib/tracking/Tracker'
import './assets/stylesheets/bootstrap.scss'
import './bootstrap-ds.css'
import './index.css'
import './assets/stylesheets/scenes.scss'
ReactDOM.render((
<Tracker>
<Provider store={store}>
<Router history={history}>
<Switch>
<Route path={'/analytics'} component={MyComp} />
</Switch>
</Router>
</Provider>
</Tracker>
), document.getElementById('root'))
MyComp.js
import React from 'react
import { TrackerProvider } from '../../lib/tracking/Tracker
const MyComp = () => {
return (
<TrackerProvider.Consumer>
{context =>
<>
<div>This is my test page for track events for analytics</div>
<button data-analytics="TEST_BUTTON">Test Analytics</button>
</>
}
</TrackerProvider.Consumer>
)
}
export default MyComp
Here's what I'm struggling with a little bit:
1. When I load a nested child component that consumes the context, how do I notify the Parent (<Tracker />) to trigger some function? Similar to componentDidUpdate.
In essence a user navigates to the MyComp page and the pageLoad function is fired in the Tracker.2. How do I update the Context from MyComp without depending on some click event in the render method to run a funciton. So maybe in componentDidUpdate I can update the context.
I noticed you had connect from react-redux. Redux already provides its state to all the components in your app, so if you're already using Redux, you don't need to mess with the context API directly.
It's possible to create a higher-order component (a component that takes a component and returns a component) and attach event listeners to that capable of catching all the click events in your app.
A click disptaching HOC might look something like this:
import React from 'react';
import { useDispatch } from 'react-redux';
import logClick from '../path/to/log/clicks.js';
const ClickLogger = Component => (...props) => {
const dispatch = useDispatch();
return <div onClick={e => dispatch(logClick(e))}>
<Component {...props } />
</div>;
};
logClick will be a Redux action creator. Once you've got your log actions dispatching to Redux, you can use redux middleware to handle your log actions. If you want to hit a tracking pixel on a server or something, you could use redux-saga to trigger the logging effects.
If you want to track every page load, you can create a higher-order component which uses the useEffect hook with an empty array ([]) as the second argument. This will fire an effect on the first render, but no subsequent renders.

Understanding React Higher-Order Components

Can someone please explain Higher-order components in React. I have read and re-read the documentation but cannot seem to get a better understanding. According to the documentation, HOCs help remove duplication by creating a primary function that returns a react component, by passing arguments to that function.
I have a few questions on that.
If HOCs create a new enhanced component, can it be possible not to pass in any component as argument at all?
In an example such as this, which is the higher order component, the Button or the EnhancedButton.
I tried creating one HOC like this:
// createSetup.js
import React from 'react';
export default function createSetup(options) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.testFunction = this.testFunction.bind(this);
}
testFunction() {
console.log("This is a test function");
}
render() {
return <p>{options.name}</p>
}
}
}
// main.js
import React from 'react';
import {render} from 'react-dom';
import createSetup from './createSetup';
render((<div>{() => createSetup({name: 'name'})}</div>),
document.getElementById('root'););
Running this does not show the HOC, only the div
Can anyone help out with a better example than the ones given?
A HOC is a function that takes a Component as one of its parameters and enhances that component in some way.
If HOCs create a new enhanced component, can it be possible not to pass in any component as argument at all?
Nope, then it wouldn't be a HOC, because one of the conditions is that they take a component as one of the arguments and they return a new Component that has some added functionality.
In an example such as this, which is the higher order component, the Button or the EnhancedButton.
EnhanceButton is the HOC and FinalButton is the enhanced component.
I tried creating one HOC like this: ... Running this does not show the HOC, only the div
That's because your createSetup function is not a HOC... It's a function that returns a component, yes, but it does not take a component as an argument in order to enhance it.
Let's see an example of a basic HOC:
const renderWhen = (condition, Component) =>
props => condition(props)
? <Component {...props} />
: null
);
And you could use it like this:
const EnhancedLink = renderWhen(({invisible}) => !invisible, 'a');
Now your EnhancedLink will be like a a component but if you pass the property invisible set to true it won't render... So we have enhanced the default behaviour of the a component and you could do that with any other component.
In many cases HOC functions are curried and the Component arg goes last... Like this:
const renderWhen = condition => Component =>
props => condition(props)
? <Component {...props} />
: null
);
Like the connect function of react-redux... That makes composition easier. Have a look at recompose.
In short, If you assume functions are analogues to Components, Closure is analogous to HOC.
Try your createSetup.js with:
const createSetup = options => <p>{options.name}</p>;
and your main.js
const comp = createSetup({ name: 'name' });
render((<div>{comp}</div>),
document.getElementById('root'));
A higher-order component (HOC) is an advanced technique in React for reusing component logic. Concretely, a higher-order component is a function that takes a component and returns a new component.
A HOC is a pure function with zero side-effects.
Example: CONDITIONALLY RENDER COMPONENTS
Suppose we have a component that needs to be rendered only when a user is authenticated — it is a protected component. We can create a HOC named WithAuth() to wrap that protected component, and then do a check in the HOC that will render only that particular component if the user has been authenticated.
A basic withAuth() HOC, according to the example above, can be written as follows:
// withAuth.js
import React from "react";
export function withAuth(Component) {
return class AuthenticatedComponent extends React.Component {
isAuthenticated() {
return this.props.isAuthenticated;
}
/**
* Render
*/
render() {
const loginErrorMessage = (
<div>
Please login in order to view this part of the application.
</div>
);
return (
<div>
{ this.isAuthenticated === true ? <Component {...this.props} /> : loginErrorMessage }
</div>
);
}
};
}
export default withAuth;
The code above is a HOC named withAuth. It basically takes a component and returns a new component, named AuthenticatedComponent, that checks whether the user is authenticated. If the user is not authenticated, it returns the loginErrorMessage component; if the user is authenticated, it returns the wrapped component.
Note: this.props.isAuthenticated has to be set from your application’s
logic. (Or else use react-redux to retrieve it from the global state.)
To make use of our HOC in a protected component, we’d use it like so:
// MyProtectedComponent.js
import React from "react";
import {withAuth} from "./withAuth.js";
export class MyProectedComponent extends React.Component {
/**
* Render
*/
render() {
return (
<div>
This is only viewable by authenticated users.
</div>
);
}
}
// Now wrap MyPrivateComponent with the requireAuthentication function
export default withAuth(MyPrivateComponent);
Here, we create a component that is viewable only by users who are authenticated. We wrap that component in our withAuth HOC to protect the component from users who are not authenticated.
Source
// HIGHER ORDER COMPOENTS IN REACT
// Higher order components are JavaScript functions used for adding
// additional functionalities to the existing component.
// file 1: hoc.js (will write our higher order component logic) -- code start -->
const messageCheckHOC = (OriginalComponent) => {
// OriginalComponent is component passed to HOC
const NewComponent = (props) => {
// business logic of HOC
if (!props.isAllowedToView) {
return <b> Not Allowed To View The MSG </b>;
}
// here we can pass the props to component
return <OriginalComponent {...props} />;
};
// returning new Component with updated Props and UI
return NewComponent;
};
export default messageCheckHOC;
// file 1: hoc.js -- code end -->
// file 2: message.js -- code start -->
// this is the basic component we are wrapping with HOC
// to check the permission isAllowedToView msg if not display fallback UI
import messageCheckHOC from "./hoc";
const MSG = ({ name, msg }) => {
return (
<h3>
{name} - {msg}
</h3>
);
};
export default messageCheckHOC(MSG);
// file 2: message.js -- code end -->
// file 3 : App.js -- code start --->
import MSG from "./message.js";
export default function App() {
return (
<div className="App">
<h3>HOC COMPONENTS </h3>
<MSG name="Mac" msg="Heyy !!! " isAllowedToView={true} />
<MSG name="Robin" msg="Hello ! " isAllowedToView={true} />
<MSG name="Eyann" msg="How are you" isAllowedToView={false} />
</div>
);
}
// file 3 : App.js -- code end --->

Categories

Resources