Create ref to external class component inside functional component in React - javascript

I would like to use react player library in my app
import React, { useEffect, useRef } from "react";
import ReactPlayer from "react-player";
import { useSelector } from "../../../redux/useSelector";
const VIDEO_URL =
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
export const Player: React.FC = React.memo(() => {
const player = React.useRef<any>(null);
const targetTimestamp = useSelector((state) => {
const {
timestamps: { selectedTimestamp },
} = state;
return (selectedTimestamp && selectedTimestamp.timestamp) || 0;
});
useEffect(() => {
console.log(player.current);
player.current && player.current.seekTo(targetTimestamp );
}, [targetTimestamp]);
return (
<ReactPlayer
ref={player}
url={VIDEO_URL}
controls
width={1280}
height={720}
/>
);
});
console.log(player.current); works, but on the next line I get the error
Uncaught TypeError: Cannot read property 'seekTo' of undefined
What's wrong? I can't use useRef here? Should I make Player class component? How to fix it and make it work?

////
let player;
const ref = (playerRef) => {
player = playerRef;
}
////
const seekHandler = (event) => {
....
player.seekTo(parseFloat(event.target.value), "fraction");
...
};
...
<ReactPlayer
ref={ref}
.....

Related

Check whether React.Suspense is ready

I'm using the Intersection Observer API in React to add some animations. I am adding as Intersection Entries some elements.
The problem is that I have the app in multiple languages, and due to the implementation that the tool I am using to translate has, I need to wrap all my components into React.Suspense to wait for languages to load.
When useEffect queries for the elements, they aren't still in the DOM, and therefore they are not assigned as entries.
This is my custom hook:
hooks/useObserver.js
import { useState } from "react";
import { useEffect, useRef } from "react";
export function useObserver(config = {}) {
const [elements, setElements] = useState([]);
const [entries, setEntries] = useState([]);
const observer = useRef(
new IntersectionObserver(observedEntries => {
setEntries(observedEntries);
}, config)
);
useEffect(() => {
const { current: currentObserver } = observer;
currentObserver.disconnect();
if (elements.length > 0) {
elements.forEach(el => currentObserver.observe(el));
}
return () => {
if (currentObserver) {
currentObserver.disconnect();
}
};
}, [elements]);
return { observer: observer.current, setElements, entries };
}
and this is my main component:
App.jsx
import Header from "./components/Header";
import Hero from "./components/Hero";
import Footer from "./components/Footer";
import { Loader } from "./components/shared/Loader";
import { useObserver } from "./hooks/useObserver";
import { useEffect, Suspense } from "react";
function App() {
const { entries, setElements } = useObserver({});
useEffect(() => {
const sections = document.querySelectorAll("section.animated-section");
setElements(sections);
};
}, [setElements]);
useEffect(() => {
entries.forEach(entry => {
entry.target.classList.toggle("section-visible", entry.isIntersecting);
});
}, [entries]);
return (
<Suspense fallback={<Loader />}>
<Header />
<Hero />
<Footer />
</Suspense>
);
}
export default App;
I tried to set a timeout to wait some seconds and then add the elements as entries, and it works correctly:
useEffect(() => {
const observeElements = () => {
const sections = document.querySelectorAll("section.animated-section");
setElements(sections);
};
const observeElementsTimeout = setTimeout(observeElements, 3000);
return () => clearTimeout(observeElementsTimeout)
}, [setElements]);
I want to know if:
There is a way to know when React.Suspense is ready
There is a better approach to solve my problem
Thanks in advance!!

Timer React + Redux. React don't dispatch action by timer (SetInterval) in ComponentDidMount

I'm trying to make a timer in my App with React + Redux.
So I have a component parent:
import React, { Component } from "react";
import { connect } from "react-redux";
import { compose } from "redux";
import QuestionCounter from "../question-counter";
import FinishButton from "../finish-button";
import TimeCounter from "../time-counter";
import PauseButton from "../pause-button";
import testFinished from "../../actions/test-finished";
import timerTick from "../../actions/timer-tick";
import setTimer from "../../actions/set-timer";
import totalWithEwStruct from "../hoc/total-with-ew-structure";
import withIndicators from "../hoc/with-indicators";
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
const mapStateToProps = ({ total, loading, error }) => {
return { total, loading, error };
};
const mapDispatchToProps = {
testFinished,
setTimer,
timerTick
}
export default compose(
totalWithEwStruct(),
connect(mapStateToProps, mapDispatchToProps),
withIndicators()
)(Total);
I try use timerTick by timer in componentDidMount
import React, { Component } from "react";
export default class TimeCounter extends Component {
componentDidMount() {
const { setTimer, timerTick } = this.props;
let timer = setInterval(() => {
timerTick();
console.log("tick");
}, 1000);
setTimer(timer);
}
componentDidUpdate() {
const { timeLeft, testFinished } = this.props;
if (timeLeft <= 0) {
testFinished();
}
}
render() {
const { timeLeft } = this.props;
return (
<div className="question-counter__timeleft">
Времени осталось
<span className="question-counter__timer">{timeLeft}</span>
</div>
);
}
}
So I see "tick" - "tick" - "tick" in console, but React doesn't dispatch my timerTick() function to reducer.
I have tried log to console action.type for debugging, and there is no action of timerTick.
const timerTick = () => {
return {
type: "TIMER_TICK"
};
};
export default timerTick;
Its code of action.
I don't understand why it doesn't work.
Your Total component needs to take timerTick function from props which is the one that is linked with redux store as you have added it to mapDispatchToProps.
If you do not destructure it from props, the ccomponent will use the imported function which isn't an action created unless its passed to dispatch function
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft, timerTick } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
You need to add dispatch of timer tick inside timer tick component. Because child component not aware about the actions.
Please refer below link for more details:
https://itnext.io/dispatching-actions-from-child-components-bd292a51f176
Response
if your component is not connected to redux you won’t be able to dispatch any action.
What do I mean?
Example
import React from “react”;
import { connect } from “react-redux”;
class MyCom extensa React.Component {
componentDidMount () {
const { action } = this.props;
action();
}
render () {
.....
}
}
const toState = state => ({....});
const toDispatch = {
action
};
export default connect(toState, toDispatch)(MyCom);
Explains
Basically connect from ”react-redux” is a HOC a high order component that on javascript world: is none but a high order function. a function that return another function.

Passing array to other components after calling Api in react using context

I'm trying to pass an array of data as props (using context) to another component Carousello.js
But I’m unable to retrieve data on my Home.js component.
This is My HomeContext.js Component:
I use context to calling the API and then passing data to Home.js
import React, { useState, createContext } from 'react';
import axios from 'axios'
export const HomeContext = createContext();
export const HomeProvider = (props) => {
let array = []
axios.get(`/wordpress/wp-json/wp/v2/pages/15`)
.then(function (res) {
let slider = res.data.acf.slider
array.push(slider)
})
console.log(typeof (array))
let ar = array.flat()
console.log(ar)
return (
<HomeContext.Provider value={[array]}>
{props.children}
</HomeContext.Provider>
)
}
This is my Carosello.js component where i try to retrive data and render it with jsx:
import React, { Component, useContext } from 'react'
import sfondo from './sfondo-gray.jpg'
import { HomeProvider, HomeContext } from './HomeContext';
const Carosello = () => {
const [acf] = useContext(HomeContext)
console.log(acf)
return (
<div id="myCarousel" className="carousel slide" data-ride="carousel" >
<h1> {acf.title} </h1>
</div >
)
}
export default Carosello
You probably also need to wrap your axios call in a function to use inside useEffect. For more detailed info on that, check these: A complete guide to useEffect and fetchind data with useEffect. Basically, using the [] (empty list of dependencies) to trigger one-time action doesn't always work as you would expect from class components' componentDidMount.
export const HomeProvider = (props) => {
const [array, setArray] = useState([]);
useEffect(()=> {
function fetchData() {
axios.get(`/wordpress/wp-json/wp/v2/pages/15`)
.then(function (res) {
let slider = res.data.acf.slider
setArray([...slider.flat()])
})
}
fetchData();
}, [])
return (
<HomeContext.Provider value={[array]}>
<>{props.children}</>
</HomeContext.Provider>
)
}
import React, { useState, createContext } from 'react';
import axios from 'axios'
export const HomeContext = createContext();
export const HomeProvider = ({children}) => {
// array type
const [items, setItems] = useState([])
axios.get(`/wordpress/wp-json/wp/v2/pages/15`)
.then(function (res) {
let slider = res.data.acf.slider
setItems([...slider.flat()])
})
return (
<HomeContext.Provider value={{
array:items
}}>
{children}
</HomeContext.Provider>
)
}
const Carosello = () => {
// object destructuring
const {array} = useContext(HomeContext)
return (
<div id="myCarousel" className="carousel slide" data-ride="carousel" >
{/* if is array, try to map or [key] */}
<h1> {array[0].title} </h1>
</div >
)
}
export default Carosello
`
function HomeProvider(){
const [array, setArray] = React.useState([]);
React.useEffect(()=> {
axios.get(`/wordpress/wp-json/wp/v2/pages/15`)
.then(function (res) {
let slider = res.data.acf.slider
setArray((arr)=> [...arr, slider])
})
}, [])
return (
<HomeContext.Provider value={[array]}>
{props.children}
</HomeContext.Provider>
)
}

Custom hook error: Hooks can only be called inside of the body of a function component

I am trying to develop a custom hook which seems to be pretty easy but I am getting an error
Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
This is my hook:
import React, { useState, useEffect } from 'react';
const useInfiniteScroll = (isLastPage: boolean, fetchFn: any) => {
const [pageCount, setPageCount] = useState(0);
const triggerFetchEvents = (): void => {
let response;
setPageCount(() => {
if (!isLastPage) {
response = fetchFn(pageCount + 1, 5, 'latest');
}
return pageCount + 1;
});
return response;
};
useEffect(() => {
triggerFetchEvents();
}, []);
return pageCount;
};
export default useInfiniteScroll;
And the component here I am calling it:
import React, { FC } from 'react';
import { connect } from 'react-redux';
import { fetchEvents } from '../../shared/actions/eventActions';
import { AppState } from '../../shared/types/genericTypes';
import EventModel from '../../shared/models/Event.model';
import EventListPage from '../../components/events/EventListPage';
import useInfiniteScroll from '../../shared/services/triggerInfiniteScroll';
type Props = {
fetchEvents?: any;
isLastPage: boolean;
eventsList?: EventModel[];
};
const mapState: any = (state: AppState, props: Props): Props => ({
eventsList: state.eventReducers.eventsList,
isLastPage: state.eventReducers.isLastPage,
...props
});
const actionCreators = {
fetchEvents
};
export const EventsScene: FC<Props> = props => {
const { eventsList, fetchEvents, isLastPage } = props;
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// const [pageCount, setPageCount] = useState(0);
// const triggerFetchEvents = (): void => {
// let response;
// setPageCount(() => {
// if (!isLastPage) {
// response = fetchEvents(pageCount + 1, 1, 'latest');
// }
// return pageCount + 1;
// });
// return response;
// };
// useEffect(() => {
// triggerFetchEvents();
// }, []);
if (!eventsList || !eventsList.length) return null;
return (
<EventListPage
eventsList={eventsList}
isLastPage={isLastPage}
triggerFetchEvents={useIn}
/>
);
};
export default connect(
mapState,
actionCreators
)(EventsScene);
I left the commented code there to show you that if I uncomment the code and remove useInfiniteScroll then it works properly.
What could I be missing?
UPDATE:
This is EventListPage component
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import EventModel from '../../shared/models/Event.model';
import { formatDate } from '../../shared/services/date';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Card from 'react-bootstrap/Card';
type Props = {
eventsList?: EventModel[];
isLastPage: boolean;
triggerFetchEvents: any;
};
export const EventListPage: React.FC<Props> = props => {
const { eventsList, triggerFetchEvents, isLastPage } = props;
const [isFetching, setIsFetching] = useState(false);
const fetchMoreEvents = (): Promise<void> =>
triggerFetchEvents().then(() => {
setIsFetching(false);
});
const handleScroll = (): void => {
if (
document.documentElement.offsetHeight -
(window.innerHeight + document.documentElement.scrollTop) >
1 ||
isFetching
) {
return;
}
return setIsFetching(true);
};
useEffect(() => {
if (isFetching) return;
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
if (!isFetching) return;
if (!isLastPage) fetchMoreEvents();
}, [isFetching]);
if (!eventsList) return null;
return (
<Container className='article-list mt-5'>
///...
</Container>
);
};
export default EventListPage;
In EventsScene, change useInfiniteScroll to be invoked directly at the function body top-level (not sure why you are creating this indirection in the first place):
// before
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// after
useInfiniteScroll(isLastPage, fetchEvents)
React expects Hook calls to only happen at the top-level as it relies on the order of Hooks to be always the same. If you wrap the Hook in a function, you can potentially invoke this function in many code locations disturbing the Hooks' order.
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state. Link

'x' is not defined - React map dispatch to Props

Currently have an issue where by I want to update props based on 'componentdidupdate'. However everytime i call this function (onUpdateSelectedDate), it keeps saying
onUpdateSelectedDate is not defined
I have tried the following:
onUpdateSelectedDate(toggledDate)
this.onUpdateSelectedDate(toggledDate)
this.props.onUpdateSelectedDate(toggledDate)
and still unsure why i am getting this error.
Code below
import DayPicker from "react-day-picker"
import React, {Component} from 'react'
import './calendarDatePicker.scss'
import propTypes from 'prop-types'
import { connect } from 'react-redux'
class CalendarDatePicker extends Component {
state = {
toggledDate: null,
}
componentDidUpdate = () => {
const toggledDate = this.state.toggledDate
onUpdateSelectedDate(toggledDate)
}
render() {
const selectedDate = this.props.selectedDays
const onDayClick = this.props.onDayClick
const toggledDate = this.state.toggledDate
const modifiers = {
}
return (
<DayPicker
selectedDays={toggledDate===null ? selectedDate : toggledDate}
onDayClick={onDayClick}
todayButton="Go to Today"
firstDayOfWeek={1}
modifiers = {modifiers}
onMonthChange={(d) => this.setState({toggledDate: d})}
/>
)
}
}
CalendarDatePicker.propTypes = {
selectedDays: propTypes.instanceOf(Date),
onDayClick: propTypes.func,
onUpdateSelectedDate: propTypes.func,
}
const mapStateToProps = (state) => {
return {
//toggledDate: state.diaryContext.activities.selectedDates,
}
}
const mapDispatchToProps = (dispatch) => {
return {
onUpdateSelectedDate: (toggledDate) => { dispatch(diaryActions.updateSelectedDate(toggledDate)) },
}
}
export default connect(null, mapDispatchToProps)(CalendarDatePicker)
You use a wrong signature for the componentDidUpdate method it should be componentDidUpdate(prevProps, prevState) and then you can access your function from mapStateToProps like that:
componentDidUpdate (prevProps, prevState) {
const toggledDate = prevState.toggledDate
prevProps.onUpdateSelectedDate(toggledDate)
}

Categories