react-player SeekTo is not a function - reactjs

I am using react-player with my next.js site and am having trouble using seekTo function. It results in error saying: "playerRef.seekTo is not a function"
I tried "playerRef.current.seekTo(parseFloat(e.target.value))" as well but same error occurs..
My code is based on react-player demo repo: https://github.com/cookpete/react-player/blob/master/src/demo/App.js
import { useRef } from "react";
const ReactPlayer = dynamic(() => import("react-player"), { ssr: false });
const Player = ({url}) => {
const playerRef = useRef();
const [player, setPlayer] = useState({
playing: true,
muted: true,
played: 0,
loaded: 0,
duration: 0,
seeking:false
});
const handlePlayPause = function () {
setPlayer({ ...player, playing: !player.playing });
};
const handleMuted = () => {
setPlayer({ ...player, muted: !player.muted });
};
const handleSeekMouseDown = (e) => {
setPlayer({ ...player, seeking: true });
};
const handleSeekChange = (e) => {
setPlayer({ ...player, played: parseFloat(e.target.value) });
};
const handleSeekMouseUp = (e) => {
setPlayer({ ...player, seeking: false });
playerRef.seekTo(parseFloat(e.target.value));
};
const handleProgress = (state) => {
if (!player.seeking) {
setPlayer(state);
}
};
return (
<div
className={`relative justify-center content-center w-full`}
>
<ReactPlayer
ref={playerRef}
className="absolute rounded"
width="100%"
height="100%"
url={url}
volume={1}
playIcon={<></>}
playing={player.playing}
loop={true}
muted={player.muted}
onProgress={handleProgress}
></ReactPlayer>
<div
className={"absolute w-full h-full"}
onClick={handlePlayPause}
></div>
<div>
<input
type="range"
min={0}
max={0.999999}
step="any"
value={player.played}
onMouseDown={handleSeekMouseDown}
onChange={handleSeekChange}
onMouseUp={handleSeekMouseUp}
/>
</div>
</div>
);
};
export default Player;
Thank you

create another component and pass the ref and other props to this component and import ReactPlayer manually
create VPlayer.jsx ==>
import React from "react";
import ReactPlayer from "react-player";
const VPlayer = ({
playerRef,
}) => {
return (
<ReactPlayer
ref={playerRef}
/>
);
};
export default VPlayer;
import that component dynamically and pass the props to it
and use it in another component or pages ==>
import React, {useRef} from "react";
import dynamic from "next/dynamic";
const VPlayer = dynamic(() => import("./VPlayer"), {
ssr: false,
});
const VideoPlayer = () => {
const videoRef = useRef();
return (
<VPlayer
playerRef={videoRef}/>
)
}
export default VideoPlayer;

playerRef is just a ref, to access it's actual value you need to use its current property, i.e
const handleSeekMouseUp = (e) => {
setPlayer({ ...player, seeking: false });
playerRef.current?.seekTo(parseFloat(e.target.value));
};

Related

Torch working on Android but not in iOS (ReactJS)

I'm building a QR scanner inside a ReactJS web app that is supposed to run on both Android and iOS. However, I cannot get the torch/flashlight to work on iOS.
I'm using the #blackbox-vision toolbox to handle both the torch and the QR scanner. As far as I understand you need to start the camera functionality and can use the video stream to manipulate the torch. Below code works fine on Android but not on iOS:
import { useState, useEffect, useRef } from "react";
import { QrReader } from "#blackbox-vision/react-qr-reader";
import { useTorchLight } from "#blackbox-vision/use-torch-light";
import styles from "./view.module.css";
import IconButton from "../../components/UI/iconbutton/view";
function SAQRView() {
const streamRef = useRef(null);
const [on, toggle] = useTorchLight(streamRef.current);
const [showTorchToggleButton, setShowTorchToggleButton] = useState(false);
const [msg, setMsg] = useState("");
const setRef = ({ stream }) => {
streamRef.current = stream;
setShowTorchToggleButton(true);
};
const previewStyle = {
width: "100%",
};
const onError = (error) => {
console.log(error);
};
const onTorchClick = (event) => {
toggle();
};
return (
<>
<div className={styles.container}>
<div className={styles.sub_container}>
<QrReader
delay={100}
showViewFinder={false}
style={previewStyle}
onLoad={setRef}
onError={onError}
onScan={setData}
constraints={{
facingMode: "environment",
video: true,
}}
/>
<div className={styles.footer}>
{showTorchToggleButton && (
<IconButton
icon="Flash_off"
toggleIcon="Flash_on"
isToggled={on}
onClick={onTorchClick}
/>
)}
</div>
{msg}
</div>
</div>
</>
);
}
export default SAQRView;
So then I tried manipulating the video stream manually:
import { useState, useEffect, useRef } from "react";
import { QrReader } from "#blackbox-vision/react-qr-reader";
import { useTorchLight } from "#blackbox-vision/use-torch-light";
import styles from "./view.module.css";
import IconButton from "../../components/UI/iconbutton/view";
function SAQRView() {
const streamRef = useRef(null);
const [on, toggle] = useTorchLight(streamRef.current);
const [showTorchToggleButton, setShowTorchToggleButton] = useState(false);
const [msg, setMsg] = useState("");
const setRef = ({ stream }) => {
streamRef.current = stream;
setShowTorchToggleButton(true);
};
const previewStyle = {
width: "100%",
};
const onError = (error) => {
console.log(error);
};
const onTorchClick = (event) => {
const tracks = streamRef.current.getVideoTracks();
const track = tracks[0];
setMsg(JSON.stringify(track.getCapabilities(), null, 2));
try {
if (!track.getCapabilities().torch) {
alert("No torch available.");
}
track.applyConstraints({
advanced: [
{
torch: true,
},
],
});
} catch (error) {
alert(error);
}
};
return (
<>
<div className={styles.container}>
<div className={styles.sub_container}>
<QrReader
delay={100}
showViewFinder={false}
style={previewStyle}
onLoad={setRef}
onError={onError}
onScan={setData}
constraints={{
facingMode: "environment",
video: true,
}}
/>
<div className={styles.footer}>
{showTorchToggleButton && (
<IconButton
icon="Flash_off"
toggleIcon="Flash_on"
isToggled={on}
onClick={onTorchClick}
/>
)}
</div>
{msg}
</div>
</div>
</>
);
}
export default SAQRView;
Again, this works on Android, but not iOS. Notice that I stringify the track capabilities and print them at the bottom of the screen. For Android this looks as follows:
And for iOS, it looks like this:
So it seems that iOS cannot access the torch capability. However, the torch will be greyed out when the QR scanner is active, so it does seem to grab hold of the torch.
Also we have tried installing the Chrome web browser but this gave exactly the same result.
Can I get around this and if so, how?

How can i test add filters button when clicked it opens search filters popup but before the button appears there is loading spinner?

add filter button is appeared when loading spinner is completed(means there is dispatch trigger action called loadListings which set loading to true).
I'm using react testing library, how can i achieve this by using mockedAxios? or any idea.
useEffect(() => {
dispatch(loadListings());
}, [dispatch]);
Search.js
import React, {useEffect} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {useNavigate} from '#reach/router';
import Picture from '#/components/picture';
import Button from '#/components/button';
import Pagination from '#/components/pagination';
import Tag from '#/components/tag';
import Spinner from '#/components/basic-spinner';
import Footer from '#/components/footer/footer';
import {
getAppliedSelectFilters,
getListings,
getListingsTotal,
listingsLoading,
} from '#/selectors/search';
import {showPopup} from '#/redux/modules/app/actions';
import {
saveDraftFilters,
clearDraftFilters,
draftSelectFilter,
clearAppliedFilter,
clearAllAppliedFilters,
loadListings,
} from '#/redux/modules/search/actions';
import {getSelectFilters, getSelectFiltersGroups} from '#/selectors/search';
import SearchFilterPopup from '#/components/search-filter-popup';
const Search = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const appliedFilters = useSelector(getAppliedSelectFilters);
const hits = useSelector(getListings);
const hitsCount = useSelector(getListingsTotal);
const loading = useSelector(listingsLoading);
const hitsPerPage = 10;
useEffect(() => {
dispatch(loadListings());
}, [dispatch]);
const showFilters = () => {
dispatch(
showPopup({
content: (
<SearchFilterPopup
saveDraftFilters={saveDraftFilters}
clearDraftFilters={clearDraftFilters}
draftSelectFilter={draftSelectFilter}
loadListings={loadListings}
selectFiltersSelector={getSelectFilters}
selectFiltersGroupsSelector={getSelectFiltersGroups}
/>
),
showClose: false,
}),
);
};
const handleTagClicked = item => {
dispatch(clearAppliedFilter(item));
dispatch(loadListings());
};
// eslint-disable-next-line #typescript-eslint/no-unused-vars
const handleClearAllFilters = () => {
dispatch(clearAllAppliedFilters());
dispatch(loadListings());
};
const handleBusinessSelected = ({identity, urlName}) => () => {
navigate(`/listings/${urlName}/${identity}`);
};
const handlePaginationPageClick = data => {
const indexOfLastHit = (data.selected + 1) * hitsPerPage;
const pages = indexOfLastHit - hitsPerPage;
dispatch(loadListings(pages));
};
return (
<section className='np-flex np-w-full np-filter-container np-overflow-x-hidden np-max-w-1300px np-flex-col np-justify-between np-mx-auto np-min-h-screen'>
<div className='np-flex np-pd np-flex-col np-justify-center'>
{!loading ? (
<div className='np-w-full'>
<div className='np-w-full'>
<h3 className='np-font-primary np-result-places np-mb-3 np-pt-8 np-truncate'>
<span>{hitsCount === 0 ? hitsCount : `${hitsCount}+`}</span>{' '}
{`Place${hitsCount !== 1 ? 's' : ''}`} in Dar es Salaam
</h3>
<div className='np-mb-3 np-flex'>
<Button
variant='link'
className='np-font-semibold np-uppercase'
onClick={showFilters}
>
<span>Add Filters</span>
</Button>
</div>
<div className='np-overflow-x-auto np-flex np-w-full'>
{appliedFilters.map(item => (
<Tag
key={item.name}
item={item}
labelKey='label'
toBe='removed'
onClick={handleTagClicked}
className='np-Tag-width'
/>
))}
{appliedFilters.length ? (
<Button variant='link' onClick={handleClearAllFilters}>
Clear all filters
</Button>
) : null}
</div>
</div>
</div>
) : null}
<div className='np-flex np-flex-wrap np-justify-center'>
{loading ? (
<div className='np-pt-32 np-h-80vh np-w-full np-flex np-justify-center'>
<Spinner size='large' label='loading' color='primary' />
</div>
) : hitsCount > 0 ? (
hits.map(hit => (
<div
key={hit.id}
className='np-search-card'
onClick={handleBusinessSelected(hit)}
>
<Picture
height='2/3'
src={`${
process.env.IMAGE_SERVICE_URL
}/cover:entropy/340x226/${hit.photos.length > 0 &&
hit.photos[hit.photos.length - 1].name}`}
className='np-overflow-hidden np-rounded-sm np-cursor-pointer'
/>
<section className='np-py-2 np-leading-normal'>
<h4 className='np-truncate'>{hit.category}</h4>
<h3 className='np-font-primary np-font-medium np-cursor-pointer np-truncate np-capitalize'>
{hit.name}{' '}
</h3>
<h4 className='np-text np-text-gray np-truncate'>
<span className='icon location'></span> {hit.location}
</h4>
</section>
</div>
))
) : null}
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
actions.js
export const clearDraftFilters = () => ({
type: types.CLEAR_DRAFT_FILTERS,
});
export const loadListings = payload => ({
type: types.LOAD_LISTINGS,
payload,
});
export const loadListingStart = () => ({
type: types.LOAD_LISTINGS_START,
});
export const loadListingSuccess = payload => ({
type: types.LOAD_LISTINGS_SUCCESS,
payload,
});
export const loadListingError = error => ({
type: types.LOAD_LISTINGS_ERROR,
payload: error,
error: true,
});
epics.js
export const loadListingsEpic = (action$, state$, {API}) =>
action$.pipe(
filter(action => action.type === types.LOAD_LISTINGS),
switchMap(action => {
const features = getAppliedPlaceFeatureFilters(state$.value).join(',');
const category = getAppliedPlaceCategoryFilters(state$.value).join(',');
const q = get(state$.value, 'search.q', '');
const page = action.payload ? action.payload : null;
const query = omitEmpty({q, category, features, page, limit: 12});
return defer(() => API.getListings(query)).pipe(
switchMap(response => {
const [error, result] = response;
if (error) {
return of(actions.loadListingError(error));
}
return of(actions.loadListingSuccess(result));
}),
startWith(actions.loadListingStart()),
);
}),
);
reducers.js
const initialState = {
selectFilters: [],
suggestions: {docs: [], total: 0, loading: false},
};
export default (state = initialState, action) => {
switch (action.type) {
case types.LOAD_LISTINGS_START:
return {
...state,
listings: {loading: true},
};
case types.LOAD_LISTINGS_SUCCESS:
return {
...state,
listings: {loading: false, ...action.payload},
};
case types.LOAD_LISTINGS_ERROR:
return {
...state,
listings: {loading: false, error: action.payload},
};
default:
return state;
}
};
Search.test.js
import React from 'react';
import {render, screen, fireEvent} from '#/utils/testUtils';
import {waitFor} from '#testing-library/react';
import mockedAxios from 'axios';
import Search from './Search';
import { async } from 'rxjs/internal/scheduler/async';
jest.mock('axios');
describe('Search', () => {
afterEach(() => {
jest.resetAllMocks();
});
test('should render search result filters popup when add filters button is clicked', async () =>{
render(<Search />);
await waitFor(() => fireEvent.click(screen.getByRole('button', {name: 'Add Filters'}))
)
})
});

Can't call setState on a component that is not yet mounted. react-draft-wysiwyg

Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the o component.
import React, { useState } from "react";
import { EditorState } from "draft-js";
import { convertToHTML } from "draft-convert";
import dynamic from "next/dynamic";
import DOMPurify from "dompurify";
const Editor = dynamic(
() => import("react-draft-wysiwyg").then((mod) => mod.Editor),
{ ssr: false },
);
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import styles from "./Editor.module.scss";
const TextEditor = () => {
const [data, setData] = useState("");
const [wordCount, setWordCount] = useState("0");
const [clicked, setClicked] = useState(false);
const [editorState, setEditorState] = useState(() =>
EditorState.createEmpty(),
);
const [convertedContent, setConvertedContent] = useState(null);
const handleClick = () => {
setClicked(!clicked);
};
const handleEditorChange = (state) => {
setEditorState(state);
setData(state.getCurrentContent().getPlainText());
convertContentToHTML();
setWordCount(data.length);
// console.log(data);
console.log(state);
};
const convertContentToHTML = () => {
let currentContentAsHTML = convertToHTML(editorState.getCurrentContent());
setConvertedContent(currentContentAsHTML);
};
// const createMarkup = (html) => {
// return {
// __html: DOMPurify.sanitize(html),
// };
// };
return (
<div className={styles.editor__wrapper}>
<Editor
editorState={editorState}
onEditorStateChange={handleEditorChange}
wrapperClassName={styles.wrapper_class}
editorClassName={`${styles.editor_class} ${
clicked && styles.expand__editor_size
}`}
toolbarClassName={styles.toolbar_class}
localization={{
locale: "ko",
}}
// toolbarOnFocus
toolbar={{
options: [
"fontFamily",
"blockType",
"fontSize",
"inline",
"list",
"textAlign",
"colorPicker",
"link",
"embedded",
"emoji",
"image",
"remove",
"history",
],
}}
onFocus={(event) => {}}
/>
<div className={styles.btn__wrapper}>
<button className={styles.fullscreen__btn} onClick={handleClick}>
<i className={styles.icon} /> 전체화면
</button>
<span className={styles.word__count}>: {wordCount}</span>
</div>
<div
className="preview"
// dangerouslySetInnerHTML={createMarkup(convertedContent)}
></div>
</div>
);
};
export default TextEditor;
I am getting this warning when I use react-draft-wysiwyg editor with react funtional component. How can I fix it Please help

How to create infinite scroll in React and Redux?

import React, {useState, useEffect} from 'react';
import {connect} from 'react-redux';
import {
fetchRecipes
} from '../../store/actions';
import './BeerRecipes.css';
const BeerRecipes = ({recipesData, fetchRecipes}) => {
const [page, setPage] = useState(1);
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchRecipes();
}, [])
return (
<div className='beer_recipes_block'>
<div className='title_wrapper'>
<h2 className='title'>Beer recipes</h2>
</div>
<div className='beer_recipes'>
<ul className='beer_recipes_items'>
{
recipesData && recipesData.recipes && recipesData.recipes.map(recipe =>
<li className='beer_recipes_item' id={recipe.id}>{recipe.name}</li>
)
}
</ul>
</div>
</div>
);
};
const mapStateToProps = state => {
return {
recipesData: state.recipes
}
}
const mapDispatchToProps = dispatch => {
return {
fetchRecipes: () => dispatch(fetchRecipes())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BeerRecipes);
this is my component where I would like to create infinite scroll and below is my redux-action with axios:
import axios from "axios";
import * as actionTypes from "./actionTypes";
export const fetchRecipesRequest = () => {
return {
type: actionTypes.FETCH_RECIPES_REQUEST
}
}
export const fetchRecipesSuccess = recipes => {
return {
type: actionTypes.FETCH_RECIPES_SUCCESS,
payload: recipes
}
}
export const fetchRecipesFailure = error => {
return {
type: actionTypes.FETCH_RECIPES_FAILURE,
payload: error
}
}
export const fetchRecipes = (page) => {
return (dispatch) => {
dispatch(fetchRecipesRequest)
axios
.get('https://api.punkapi.com/v2/beers?page=1')
.then(response => {
const recipes = response.data;
dispatch(fetchRecipesSuccess(recipes));
})
.catch(error => {
const errorMsg = error.message;
dispatch(fetchRecipesFailure(errorMsg));
})
}
}
I want to create a scroll. I need, firstly, to display first 10 elements and then to add 5 elements with every loading. I have 25 elements altogether and when the list is done it should start from the first five again.
Assuming you already have everything ready to load your next page. You can probably simplify the entire process by using a package like react-in-viewport so you don't have to deal with all the scroll listeners.
then you use it like this way.
import handleViewport from 'react-in-viewport';
const Block = (props: { inViewport: boolean }) => {
const { inViewport, forwardedRef } = props;
const color = inViewport ? '#217ac0' : '#ff9800';
const text = inViewport ? 'In viewport' : 'Not in viewport';
return (
<div className="viewport-block" ref={forwardedRef}>
<h3>{ text }</h3>
<div style={{ width: '400px', height: '300px', background: color }} />
</div>
);
};
const ViewportBlock = handleViewport(Block, /** options: {}, config: {} **/);
const Component = (props) => (
<div>
<div style={{ height: '100vh' }}>
<h2>Scroll down to make component in viewport</h2>
</div>
<ViewportBlock
onEnterViewport={() => console.log('This is the bottom of the content, lets dispatch to load more post ')}
onLeaveViewport={() => console.log('We can choose not to use this.')} />
</div>
))
What happen here is, it creates a 'div' which is outside the viewport, once it comes into the view port ( it means user already scrolled to the bottom ), you can call a function to load more post.
To Note: Remember to add some kind of throttle to your fetch function.

Having React Context in Separate File, Can't Get Component to Not re-render

I've got a simple example of React Context that uses useMemo to memoize a function and all child components re-render when any are clicked. I've tried several alternatives (commented out) and none work. Please see code at stackblitz and below.
https://stackblitz.com/edit/react-yo4eth
Index.js
import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import { GlobalProvider } from "./GlobalState";
function App() {
return (
<GlobalProvider>
<Hello />
</GlobalProvider>
);
}
render(<App />, document.getElementById("root"));
GlobalState.js
import React, {
createContext,useState,useCallback,useMemo
} from "react";
export const GlobalContext = createContext({});
export const GlobalProvider = ({ children }) => {
const [speakerList, setSpeakerList] = useState([
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true },
]);
const clickFunction = useCallback((speakerIdClicked) => {
setSpeakerList((currentState) => {
return currentState.map((rec) => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
});
});
},[]);
// const provider = useMemo(() => {
// return { clickFunction: clickFunction, speakerList: speakerList };
// }, []);
//const provider = { clickFunction: clickFunction, speakerList: speakerList };
const provider = {
clickFunction: useMemo(() => clickFunction,[]),
speakerList: speakerList,
};
return (
<GlobalContext.Provider value={provider}>{children}</GlobalContext.Provider>
);
};
Hello.js
import React, {useContext} from "react";
import Speaker from "./Speaker";
import { GlobalContext } from './GlobalState';
export default () => {
const { speakerList } = useContext(GlobalContext);
return (
<div>
{speakerList.map((rec) => {
return <Speaker speaker={rec} key={rec.id}></Speaker>;
})}
</div>
);
};
Speaker.js
import React, { useContext } from "react";
import { GlobalContext } from "./GlobalState";
export default React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const { clickFunction } = useContext(GlobalContext);
return (
<>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</>
);
});
Couple of problems in your code:
You already have memoized the clickFunction with useCallback, no need to use useMemo hook.
You are consuming the Context in Speaker component. That is what's causing the re-render of all the instances of Speaker component.
Solution:
Since you don't want to pass clickFunction as a prop from Hello component to Speaker component and want to access clickFunction directly in Speaker component, you can create a separate Context for clickFunction.
This will work because extracting clickFunction in a separate Context will allow Speaker component to not consume GlobalContext. When any button is clicked, GlobalContext will be updated, leading to the re-render of all the components consuming the GlobalContext. Since, Speaker component is consuming a separate context that is not updated, it will prevent all instances of Speaker component from re-rendering when any button is clicked.
Demo
const GlobalContext = React.createContext({});
const GlobalProvider = ({ children }) => {
const [speakerList, setSpeakerList] = React.useState([
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true }
]);
return (
<GlobalContext.Provider value={{ speakerList, setSpeakerList }}>
{children}
</GlobalContext.Provider>
);
};
const ClickFuncContext = React.createContext();
const ClickFuncProvider = ({ children }) => {
const { speakerList, setSpeakerList } = React.useContext(GlobalContext);
const clickFunction = React.useCallback(speakerIdClicked => {
setSpeakerList(currentState => {
return currentState.map(rec => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
});
});
}, []);
return (
<ClickFuncContext.Provider value={clickFunction}>
{children}
</ClickFuncContext.Provider>
);
};
const Speaker = React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const clickFunction = React.useContext(ClickFuncContext)
return (
<div>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</div>
);
});
function SpeakerList() {
const { speakerList } = React.useContext(GlobalContext);
return (
<div>
{speakerList.map(rec => {
return (
<Speaker speaker={rec} key={rec.id} />
);
})}
</div>
);
};
function App() {
return (
<GlobalProvider>
<ClickFuncProvider>
<SpeakerList />
</ClickFuncProvider>
</GlobalProvider>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can also see this demo on StackBlitz
this will not work if you access clickFuntion in children from provider because every time you updating state, provider Object will be recreated and if you wrap this object in useMemolike this:
const provider = useMemo(()=>({
clickFunction,
speakerList,
}),[speakerList])
it will be recreated each time clickFunction is fired.
instead you need to pass it as prop to the children like this:
import React, {useContext} from "react";
import Speaker from "./Speaker";
import { GlobalContext } from './GlobalState';
export default () => {
const { speakerList,clickFunction } = useContext(GlobalContext);
return (
<div>
{speakerList.map((rec) => {
return <Speaker speaker={rec} key={rec.id} clickFunction={clickFunction }></Speaker>;
})}
</div>
);
};
and for provider object no need to add useMemo to the function clickFunction it's already wrapped in useCallback equivalent to useMemo(()=>fn,[]):
const provider = {
clickFunction,
speakerList,
}
and for speaker component you don't need global context :
import React from "react";
export default React.memo(({ speaker,clickFunction }) => {
console.log("render")
return (
<>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</>
);
});

Resources