React - How can a functional component use a 'this' property - reactjs

I'm trying to make a page where there are multiple instances of the same functional component inside a parent component. Upon clicking anywhere inside the parent component I want each individual child component to check if the 'event.target' is equal to itself, if it isn't, then I want it to do something.
Maybe I got the wrong idea about this, but is there a way to use a 'this' property inside a function component or a way to mimic it?
edit: some code
Parent component:
import React, { useState } from "react"
import ShopppingListItem from './ShoppingListItem'
function ShopppingList(){
const [list, setList] = useState([{id: 0,text: 'dfdsdf'}, {id: 1,text: 'dfdsdf'},
{id: 2,text: 'dfdsdf'}, {id: 3,text: 'dfdsdf'}]) //shopping list example
const [clickedTarget, setClickedTarget] = useState(null)
const handleClick = (e) => {
setClickedTarget(e.target)
}
return(
<ol
className='shopping-list-container'
onClick={handleClick}
>
{
list.map(item => {
return <ShopppingListItem key={item.id} item={item} clickedTarget={clickedTarget}/>
})
}
</ol>
)
}
export default ShopppingList
Child component:
import React, { useState } from 'react'
function ShoppingListItem(props){
const id = props.item.id
const [text, setText] = useState(props.item.text)
const [editMode, setEditMode] = useState(false)
const clickedTarget = props.clickedTarget
const inputStyle={
width: "15vw",
border:"0px",
font:"400 20px Segoe UI",
paddingLeft:"1px"
}
//////////////////////////////////////
React.useEffect(() => {
if (this != clickedTarget) //the idea behind what im trying to achieve
{ do something }
}, [clickedTarget])
//////////////////////////////////////
const handleTextClick = () => {
setEditMode(!editMode)
}
const handleChange = (e) => {
setText(e.target.value)
}
return(
<div className='shopping-list-item'>
<span style={{paddingRight:"5px"}}>{id}. </span>
{!editMode
? <span onClick={handleTextClick}>{text}</span>
: <input
ref={element}
style={inputStyle}
type="text"
value={text}
placeholder={text}
onChange={handleChange}
/>}
</div>
)
}
export default ShoppingListItem

Related

Cannot close an antd modal using reactJS library

I need your helps, when I do click those 3 buttons on the red circles, It threw an error that's called '... is not a function' though I think closing a modal needs an useState hook which set boolean to close it. Am I wrong? If I'm not wrong so how to solve it, please help me!~~~
Images show error 1
Images show error 2
Here is my code
import {createContext, useState, useContext, useMemo} from 'react'
import { AuthContext } from './AuthProvider'
import useFirestore from '../hooks/useFirestore'
export const AppContext = createContext()
function AppProvider ({children}) {
const [isAddRoomVisible, setIsAddRoomVisible] = useState(false)
const user = useContext(AuthContext)
const {uid} = user
const roomsCondition = useMemo(() => {
return {
fieldName: 'members',
operator: 'array-contains',
value: uid
}
}, [uid])
const rooms = useFirestore('rooms', roomsCondition)
return (
<AppContext.Provider value={[rooms, isAddRoomVisible, setIsAddRoomVisible]}>
{children}
</AppContext.Provider>
)
}
export default AppProvider
import {Modal, Form, Input} from 'antd'
import { useState, useContext } from 'react'
import { AppContext } from '../../Context/AppProvider'
import { AuthContext } from '../../Context/AuthProvider'
import { addDocument } from '../../firebase/service'
export default function AddRoomModal() {
const [isAddRoomVisible, setIsAddRoomVisible] = useContext(AppContext)
const user = useContext(AuthContext)
const {uid} = user;
const [form] = Form.useForm()
const handleOk = () => {
// console.log({
// formData: form.getFieldsValue()
// })
addDocument('rooms', {...form.getFieldsValue(), members: [uid]})
setIsAddRoomVisible(false)
}
const handleCancel = () => {
setIsAddRoomVisible(false)
}
return (
<div>
<Modal
title="Create room"
visible={isAddRoomVisible}
onOk={handleOk}
okCancel={handleCancel}
>
<Form form={form} layout="vertical">
<Form.Item label="Room's name" name="name">
<Input placeholder="Enter room's name here"/>
</Form.Item>
<Form.Item label="Description" name="description">
<Input.TextArea placeholder="Enter description"/>
</Form.Item>
</Form>
</Modal>
</div>
)
}
shouldn't the first row of AddRoomModal be :
const [room, isAddRoomVisible, setIsAddRoomVisible] = useContext(AppContext)
You are destructuring an array not an object, so you cannot skip any element of the value prop of your context
EDIT :
Ok so i'll try to give you more info.
The first thing I notice is you are using a difficult way to handle your modal using a context. it would be easier to include your component in another that will contain the opening button and the modal open state :
const ModalParent = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<>
<button onClick={() => setIsModalOpen(true)}>openModal</button>
<Modal
title="Create room"
visible={isModalOpen}
onOk={(e) => {
setIsModalOpen(false);
}}
okCancel={(e) => {
setIsModalOpen(false);
}}
>
Hi my Modal !
</Modal>
</>
);
};
But you can still use the context approach, it allows you to control your modal from anywhere in your app, which can be pretty useful.
When you are using your context provider, you are passing it an array containing three values :
<AppContext.Provider value={[rooms, isAddRoomVisible, setIsAddRoomVisible]}>
When you are using the useContext hook, you are retrieving this array in your component.
const myAppContextValues = useContext(AppContext)
const rooms = myAppContextValues[0]
const isAddRoomVisible = myAppContextValues[1]
const setIsAddRoomVisible= myAppContextValues[2]
In you're case, you are using a syntax to define more quickly variables from an array called destructuring https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment .
But you are skipping an element of the array.
So if we translate your code in vanilla JS you are actually doing :
// Here you are retrieving the element 0 & 1 and putting them in variable isAddRoomVisible, setIsAddRoomVisible
// const [isAddRoomVisible, setIsAddRoomVisible] = useContext(AppContext)
// Under is the vanilla JS example
const myAppContextValues = useContext(AppContext)
const isAddRoomVisible = myAppContextValues[0]
const setIsAddRoomVisible= myAppContextValues[1]
So what you need to do is to also assign the room element, even if you don't use it
const [room, isAddRoomVisible, setIsAddRoomVisible] = useContext(AppContext)
You could also use an object as value of your context and destructuring it with the object syntax :
// notice that here i'm using the shorthand syntax to create an object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#new_notations_in_ecmascript_2015
<AppContext.Provider value={{rooms, isAddRoomVisible, setIsAddRoomVisible}}>
// and here i'm using the object destructuring
const {isAddRoomVisible, setIsAddRoomVisible} = useContext(AppContext)
Hope it helps you and it is clearer !

I am having with set state in my Shopify App using Node React

I am making a Shopify app using Shopify Polaris.
I used the ActionList component.
https://polaris.shopify.com/components/actions/action-list
I want to change the state value on the onAction event.
I did like this.
const [searchValue, setSearchValue] = useState('');
const handleAction = (value) => {
setSearchValue(value);
}
const a = ["A","B"];
const searchResultsMarkup = (
<ActionList
items={[
{
content: a[0],
onAction: handleAction(a[0]),
},
{
content: a[1],
onAction: handleAction(a[1]),
},
/>
);
I am a beginner in React.
So maybe a silly question.
but kindly teach me.
Thanks
You are passing it down as a prop, so you will have to change it within the Component.
import React, { useState } from 'react';
export const Parent = () => {
const [searchValue, setSearchValue] = useState('');
const handleAction = value => {
setSearchValue(value);
}
return <ActionList changeSearchVal={handleAction} />
}
export const ActionList = ({ changeSearchVal }) => {
return (
<form>
<input type="text" onChange={e => changeSearchVal(e.target.value)}/>
</form>
)
}
If you want to change the searchValue within ActionList.

How to pass data from child to parent component using react hooks

I have a Parent component and couple of child components. I need to disable or enable the button in the parent based on the ErrorComponent. If there is an error then I disable the button or else I enable it. I believe we can pass callbacks from the child to parent and let the parent know and update the button property. I need to know how to do the same using react hooks? I tried few examples but in vain. There is no event on error component. If there is an error (props.errorMessage) then I need to pass some data to parent so that I can disable the button. Any help is highly appreciated
export const Parent: React.FC<Props> = (props) => {
....
const createContent = (): JSX.Element => {
return (
{<ErrorPanel message={props.errorMessage}/>}
<AnotherComponent/>
);
}
return (
<Button onClick={onSubmit} disabled={}>My Button</Button>
{createContent()}
);
};
export const ErrorPanel: React.FC<Props> = (props) => {
if (props.message) {
return (
<div>{props.message}</div>
);
}
return null;
};
I'd use useEffect hook in this case, to set the disabled state depending on the message props. You can see the whole working app here: codesandbox
ErrorPanel component will look like this:
import React, { useEffect } from "react";
interface IPropTypes {
setDisabled(disabled:boolean): void;
message?: string;
}
const ErrorPanel = ({ setDisabled, message }: IPropTypes) => {
useEffect(() => {
if (message) {
setDisabled(true);
} else {
setDisabled(false);
}
}, [message, setDisabled]);
if (message) {
return <div>Error: {message}</div>;
}
return null;
};
export default ErrorPanel;
So depending on the message prop, whenever it 'exists', I set the disabled prop to true by manipulating the setDisabled function passed by the prop.
And to make this work, Parent component looks like this:
import React, { MouseEvent, useState } from "react";
import ErrorPanel from "./ErrorPanel";
interface IPropTypes {
errorMessage?: string;
}
const Parent = ({ errorMessage }: IPropTypes) => {
const [disabled, setDisabled] = useState(false);
const createContent = () => {
return <ErrorPanel setDisabled={setDisabled} message={errorMessage} />;
};
const handleSubmit = (e: MouseEvent) => {
e.preventDefault();
alert("Submit");
};
return (
<>
<button onClick={handleSubmit} disabled={disabled}>
My Button
</button>
<br />
<br />
{createContent()}
</>
);
};
export default Parent;

How to access react-list getVisibleRange() within a functional component

react-list has a method "getVisibleRange()". getVisibleRange() => [firstIndex, lastIndex]. The examples show accessing this like so:
onScrollHandler() {
console.log('onScrollHandler visible', this.getVisibleRange());
}
with the "this" keyword, within a class component. Is it possible to access the getVisibleRange() method within a functional component? For example:
const handleScroll = () => {
let [firstIndex, lastIndex] = getVisibleRange() <-- ??
}
<div id="list" onScroll={handleScroll}></div>
UPDATE: reproducable code
import React, {useState, useEffect} from 'react'
import ReactList from 'react-list'
var faker = require('faker')
const TalentSearch = () => {
let items = [...new Array(500)].map(() => faker.fake(faker.name.findName()))
const renderItem = (index, key) => {
return <div key={key}>{items[index]}</div>
}
const handleScroll = () => {
// access getVisibleRange() here?
}
return (
<div>
<h1>Search Results</h1>
<div id="list" style={{overflow: 'auto', maxHeight: 400}} onScroll={handleScroll}>
<ReactList
itemRenderer={renderItem}
length={items.length}
initialIndex={50}
type='uniform'
scrollTo={50}
/>
</div>
</div>
)
}
export default TalentSearch
You need to access it through a reference, with hooks you may use useRef:
const TalentSearch = () => {
const listRef = useRef();
return <ReactList ref={listRef} />;
};
Then you can access the methods like so:
listRef.current.getVisibleRange();

Can a React portal be used in a Stateless Functional Component (SFC)?

I have used ReactDOM.createPortal inside the render method of a stateful component like so:
class MyComponent extends Component {
...
render() {
return (
<Wrapper>
{ReactDOM.createPortal(<FOO />, 'dom-location')}
</Wrapper>
)
}
}
... but can it also be used by a stateless (functional) component?
Will chime in with an option where you dont want to manually update your index.html and add extra markup, this snippet will dynamically create a div for you, then insert the children.
export const Portal = ({ children, className = 'root-portal', el = 'div' }) => {
const [container] = React.useState(() => {
// This will be executed only on the initial render
// https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
return document.createElement(el);
});
React.useEffect(() => {
container.classList.add(className)
document.body.appendChild(container)
return () => {
document.body.removeChild(container)
}
}, [])
return ReactDOM.createPortal(children, container)
}
It can be done like this for a fixed component:
const MyComponent = () => ReactDOM.createPortal(<FOO/>, 'dom-location')
or, to make the function more flexible, by passing a component prop:
const MyComponent = ({ component }) => ReactDOM.createPortal(component, 'dom-location')
can it also be used by a stateless (functional) component
?
yes.
const Modal = (props) => {
const modalRoot = document.getElementById('myEle');
return ReactDOM.createPortal(props.children, modalRoot,);
}
Inside render :
render() {
const modal = this.state.showModal ? (
<Modal>
<Hello/>
</Modal>
) : null;
return (
<div>
<div id="myEle">
</div>
</div>
);
}
Working codesandbox#demo
TSX version based on #Samuel's answer (React 17, TS 4.1):
// portal.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
interface IProps {
className? : string
el? : string
children : React.ReactNode
}
/**
* React portal based on https://stackoverflow.com/a/59154364
* #param children Child elements
* #param className CSS classname
* #param el HTML element to create. default: div
*/
const Portal : React.FC<IProps> = ( { children, className, el = 'div' } : IProps ) => {
const [container] = React.useState(document.createElement(el))
if ( className )
container.classList.add(className)
React.useEffect(() => {
document.body.appendChild(container)
return () => {
document.body.removeChild(container)
}
}, [])
return ReactDOM.createPortal(children, container)
}
export default Portal
IMPORTANT useRef/useState to prevent bugs
It's important that you use useState or useRef to store the element you created via document.createElement because otherwise it gets recreated on every re-render
//This div with id of "overlay-portal" needs to be added to your index.html or for next.js _document.tsx
const modalRoot = document.getElementById("overlay-portal")!;
//we use useRef here to only initialize el once and not recreate it on every rerender, which would cause bugs
const el = useRef(document.createElement("div"));
useEffect(() => {
modalRoot.appendChild(el.current);
return () => {
modalRoot.removeChild(el.current);
};
}, []);
return ReactDOM.createPortal(
<div
onClick={onOutSideClick}
ref={overlayRef}
className={classes.overlay}
>
<div ref={imageRowRef} className={classes.fullScreenImageRow}>
{renderImages()}
</div>
<button onClick={onClose} className={classes.closeButton}>
<Image width={25} height={25} src="/app/close-white.svg" />
</button>
</div>,
el.current
);
Yes, according to docs the main requirements are:
The first argument (child) is any renderable React child, such as an element, string, or fragment. The second argument (container) is a DOM element.
In case of stateless component you can pass element via props and render it via portal.
Hope it will helps.
Portal with SSR (NextJS)
If you are trying to use any of the above with SSR (for example NextJS) you may run into difficulty.
The following should get you what you need. This methods allows for passing in an id/selector to use for the portal which can be helpful in some cases, otherwise it creates a default using __ROOT_PORTAL__.
If it can't find the selector then it will create and attach a div.
NOTE: you could also statically add a div and specify a known id in pages/_document.tsx (or .jsx) if again using NextJS. Pass in that id and it will attempt to find and use it.
import { PropsWithChildren, useEffect, useState, useRef } from 'react';
import { createPortal } from 'react-dom';
export interface IPortal {
selector?: string;
}
const Portal = (props: PropsWithChildren<IPortal>) => {
props = {
selector: '__ROOT_PORTAL__',
...props
};
const { selector, children } = props;
const ref = useRef<Element>()
const [mounted, setMounted] = useState(false);
const selectorPrefixed = '#' + selector.replace(/^#/, '');
useEffect(() => {
ref.current = document.querySelector(selectorPrefixed);
if (!ref.current) {
const div = document.createElement('div');
div.setAttribute('id', selector);
document.body.appendChild(div);
ref.current = div;
}
setMounted(true);
}, [selector]);
return mounted ? createPortal(children, ref.current) : null;
};
export default Portal;
Usage
The below is a quickie example of using the portal. It does NOT take into account position etc. Just something simple to show you usage. Sky is limit from there :)
import React, { useState, CSSProperties } from 'react';
import Portal from './path/to/portal'; // Path to above
const modalStyle: CSSProperties = {
padding: '3rem',
backgroundColor: '#eee',
margin: '0 auto',
width: 400
};
const Home = () => {
const [visible, setVisible] = useState(false);
return (
<>
<p>Hello World <a href="#" onClick={() => setVisible(true)}>Show Modal</a></p>
<Portal>
{visible ? <div style={modalStyle}>Hello Modal! <a href="#" onClick={() => setVisible(false)}>Close</a></div> : null}
</Portal>
</>
);
};
export default Home;
const X = ({ children }) => ReactDOM.createPortal(children, 'dom-location')
Sharing my solution:
// PortalWrapperModal.js
import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
const PortalWrapperModal = ({
children,
onHide,
backdrop = 'static',
focus = true,
keyboard = false,
}) => {
const portalRef = useRef(null);
const handleClose = (e) => {
if (e) e.preventDefault();
if (portalRef.current) $(portalRef.current).modal('hide');
};
useEffect(() => {
if (portalRef.current) {
$(portalRef.current).modal({ backdrop, focus, keyboard });
$(portalRef.current).modal('show');
$(portalRef.current).on('hidden.bs.modal', onHide);
}
}, [onHide, backdrop, focus, keyboard]);
return ReactDOM.createPortal(
<>{children(portalRef, handleClose)}</>,
document.getElementById('modal-root')
);
};
export { PortalWrapperModal };

Resources