how to set intersection observer correctly.
When I ask for img it only works on the last element, how to set for each element? So that they load one after another in turn
function useOnScreen(options:any){
const ref:any = React.useRef()
const[visible, setVisible] = React.useState(false)
useEffect(()=>{
const observer = new IntersectionObserver(([entry])=>{
setVisible(entry.isIntersecting)
}, options)
if(ref.current){
observer.observe(ref.current)
}
return ()=>{
if (ref.current){
observer.unobserve(ref.current)
}
}
}, [ref, options])
return [ref, visible]
}
const [ref, visible] = useOnScreen({threshold: '0.68'})
console.log(visible,ref)
const data:any = state.data.map((item:any) => {
return (<SectionHome key={item.id}>
<picture >
<img src={item.pictures} alt={item.show_name} key={item.pictures}/>
</picture>
<a href={item.show_name} key={item.show_name}><p key={item.id}>{item.show_name}</p></a>
</SectionHome>)
})
const data2:any = state.data.map((item:any) => {
return (
<div>
<a href={item.show_name} key={item.show_name}>
<picture ref={ref}>
{visible ? <img src={item.pictures} alt={item.show_name} key={item.pictures}/> : <section></section>}
</picture>
<p key={item.id}>{item.show_name}</p></a>
</div> )
})
You can create a new React Component to hold the intersection observer logic for each picture element.
// This function hook is the same.
function useOnScreen(options: any) {
const ref: any = React.useRef()
const [visible, setVisible] = React.useState(false)
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setVisible(entry.isIntersecting)
}, options)
if (ref.current) {
observer.observe(ref.current)
}
return () => {
if (ref.current) {
observer.unobserve(ref.current)
}
}
}, [ref, options])
return [ref, visible]
}
// This component holds the intersection observer logic and forwards all the props to the picture element.
// It accepts a function as children and the function is given whether it is visible and should return the content to render inside the picture element.
function SmartPicture(props: any) {
const { children, ...pictureProps } = props
const [ref, visible] = useOnScreen({ threshold: '0.68' })
return (
<picture {...pictureProps} ref={ref}>
{children(visible)}
</picture>
)
}
// In your render function
const data: any = state.data.map((item: any) => {
return (
<SectionHome key={item.id}>
<picture >
<img src={item.pictures} alt={item.show_name} key={item.pictures} />
</picture>
<a href={item.show_name} key={item.show_name}><p key={item.id}>{item.show_name}</p></a>
</SectionHome>
)
})
const data2: any = state.data.map((item: any) => {
return (
<div>
<a href={item.show_name} key={item.show_name}>
<SmartPicture>
{(visible) => visible ? <img src={item.pictures} alt={item.show_name} key={item.pictures} /> : <section></section>}
</SmartPicture>
<p key={item.id}>{item.show_name}</p></a>
</div>
)
})
Related
Hello I am using a switch statement to serve particular components to a page In my next js project. The switch statement receives a payload which it loops through in order to derive what component to serve. These components have been imported dynamically and I now wish to use this dynamic importing along with the Intersection Observer to load components when they come in the viewport to decrease the Initial page load time and split up the chunks. I have incorporated a hook that uses the intersection observer along with use ref to try to replicate my idea. Now this works when I give the reference to one div and it observes the component coming into the viewport as expected, however when I add multiple refs to my divs, I still only get the one div being observed with the ref.
What am I doing wrong? I thought you could reference the same ref multiple times and just use .current to identify the current element being observed?
Switch Statement:
import React from 'react';
import getTCTEnv from '../../../lib/helpers/get-tct-env';
import IconWishlistButton from '../../wishlist/add-to-wishlist-button/button-types/icon-wishlist-button';
import loadable from '#loadable/component';
import { useOnScreen } from '../../../hooks/on-screen';
const PriorityCollection = loadable(
() => import('#culture-trip/tile-ui-module/dist/collectionRail/PriorityCollections'),
{
resolveComponent: (components) => components.PriorityCollection
}
);
const TravelWithUs = loadable(
() => import('../../../components/trips/travel-with-us/travel-with-us'),
{
resolveComponent: (components) => components.TravelWithUs
}
);
const TrustMessaging = loadable(() => import('../../../components/trips/trust-messaging/index'), {
resolveComponent: (components) => components.TrustMessaging
});
const PressMessaging = loadable(() => import('../../../components/trips/press-messaging'), {
resolveComponent: (components) => components.PressMessaging
});
const TripsChatBanner = loadable(
() => import('../../../components/trips/chat-banner/chat-banner'),
{
resolveComponent: (components) => components.TripsChatBanner
}
);
const HpFeaturedArticles = loadable(
() => import('../home-page-featured-articles/home-page-featured-articles'),
{
resolveComponent: (components) => components.HpFeaturedArticles
}
);
const InstagramSection = loadable(() => import('../../../components/trips/instagram'), {
resolveComponent: (components) => components.InstagramSection
});
const EmailForm = loadable(() => import('../../../components/trips/email-form'));
const ReviewsSection = loadable(() => import('../../../components/trips/reviews'));
export const IncludeComponent = ({ collections, reviewData, type }) => {
const [containerRef, isVisible] = useOnScreen({
root: null,
rootMargin: '0px',
threshold: 0.1
});
const instagramCollection = collections.filter((collection) => collection.type === 'instagram');
const getComponents = () =>
collections.map((el, i) => {
switch (el.type) {
case 'trips':
case 'article':
return (
<PriorityCollection
key={i}
collections={[el]}
tctEnv={getTCTEnv()}
wishlistButton={<IconWishlistButton />}
/>
);
case 'reviews':
return (
<>
<div ref={containerRef} id={i}></div>
<ReviewsSection reviewData={reviewData} />
</>
);
case 'instagram':
return (
<>
<div ref={containerRef} id={i}></div>
<InstagramSection collection={instagramCollection} />
</>
);
case 'featured':
return <PressMessaging />;
case 'trust':
return <TrustMessaging type={type} />;
case 'featuredArticle':
return <HpFeaturedArticles />;
case 'email':
return <EmailForm />;
case 'chat':
return <TripsChatBanner />;
case 'travel':
return <TravelWithUs type={type} />;
default:
return;
}
});
return getComponents();
};
custom hook:
import { useEffect, useState, useRef } from 'react';
export const useOnScreen = (options): any => {
const containerRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState([]);
const callbackFunction = (entries) => {
const [entry] = entries;
if (entry.isIntersecting)
setIsVisible((oldArray) => [
...oldArray,
isVisible.indexOf(entry.target.id) === -1 && entry.target.id !== undefined
? entry.target.id
: console.log('nothing')
]);
};
useEffect(() => {
const observer = new IntersectionObserver(callbackFunction, options);
if (containerRef.current) observer.observe(containerRef.current);
return () => {
if (containerRef.current) observer.unobserve(containerRef.current);
};
}, [containerRef.current, options]);
return [containerRef, isVisible];
};
Currently only the instagram ref gets observed
If I understand your code correctly, more than one component is possibly rendered from getComponents.
For instance, the tree could contain:
<div ref={containerRef} id={i}></div>
<ReviewsSection reviewData={reviewData} />
<div ref={containerRef} id={i}></div>
<InstagramSection collection={instagramCollection} />
And you want both divs there to be observed.
It doesn't work because the ref doesn't trigger the effect by itself. The ref is simply an object like { current: null }.
When the tree is rendered, containerRef.current is set to the first div, then it is set to the second div, then the effect runs.
To do what you want you can:
Call the custom hook multiple times, and assign one containerRef to each div. The issue here is, of course, you will also have multiple IntersectionObservers instances.
Declare multiple refs and pass them to the custom hook via argument, instead of returning the ref from the custom hook.
Implement a callback ref that adds every div to a list, skipping duplicates. This one allows you to keep the same implementation in getComponents, but is also the trickiest for the hook.
Solved with this:
import React, { useEffect, useReducer } from 'react';
import getTCTEnv from '../../../lib/helpers/get-tct-env';
import IconWishlistButton from '../../wishlist/add-to-wishlist-button/button-types/icon-wishlist-button';
import loadable from '#loadable/component';
import { useOnScreen } from '../../../hooks/on-screen';
const PriorityCollection = loadable(
() => import('#culture-trip/tile-ui-module/dist/collectionRail/PriorityCollections'),
{
resolveComponent: (components) => components.PriorityCollection
}
);
const TravelWithUs = loadable(
() => import('../../../components/trips/travel-with-us/travel-with-us'),
{
resolveComponent: (components) => components.TravelWithUs
}
);
const TrustMessaging = loadable(() => import('../../../components/trips/trust-messaging/index'), {
resolveComponent: (components) => components.TrustMessaging
});
const PressMessaging = loadable(() => import('../../../components/trips/press-messaging'), {
resolveComponent: (components) => components.PressMessaging
});
const TripsChatBanner = loadable(
() => import('../../../components/trips/chat-banner/chat-banner'),
{
resolveComponent: (components) => components.TripsChatBanner
}
);
const HpFeaturedArticles = loadable(
() => import('../home-page-featured-articles/home-page-featured-articles'),
{
resolveComponent: (components) => components.HpFeaturedArticles
}
);
const InstagramSection = loadable(() => import('../../../components/trips/instagram'), {
resolveComponent: (components) => components.InstagramSection
});
const EmailForm = loadable(() => import('../../../components/trips/email-form'));
const ReviewsSection = loadable(() => import('../../../components/trips/reviews'));
export const IncludeComponent = ({ collections, reviewData, type }) => {
const [containerRef, isVisible] = useOnScreen({
root: null,
rootMargin: '0px',
threshold: 0.1
});
const instagramCollection = collections.filter((collection) => collection.type === 'instagram');
const getComponents = () =>
collections.map((el, i) => {
switch (el.type) {
case 'trips':
case 'article':
return (
<PriorityCollection
key={i}
collections={[el]}
tctEnv={getTCTEnv()}
wishlistButton={<IconWishlistButton />}
/>
);
case 'reviews':
return (
<>
<div
ref={(element) => {
containerRef.current[i] = element;
}}
id={i}
></div>
{isVisible.indexOf(i.toString()) !== -1 && <ReviewsSection reviewData={reviewData} />}
</>
);
case 'instagram':
return (
<>
<div
ref={(element) => {
containerRef.current[i] = element;
}}
id={i}
></div>
<InstagramSection collection={instagramCollection} />
</>
);
case 'featured':
return <PressMessaging />;
case 'trust':
return <TrustMessaging type={type} />;
case 'featuredArticle':
return <HpFeaturedArticles />;
case 'email':
return <EmailForm />;
case 'chat':
return <TripsChatBanner />;
case 'travel':
return <TravelWithUs type={type} />;
default:
return;
}
});
return getComponents();
};
hook:
import { useEffect, useState, useRef } from 'react';
export const useOnScreen = (options): any => {
const containerRef = useRef<HTMLDivElement[]>([]);
const [isVisible, setIsVisible] = useState([]);
const callbackFunction = (entries) => {
const [entry] = entries;
if (entry.isIntersecting) {
const checkIdInArray = isVisible.indexOf(entry.target.id) === -1;
if (checkIdInArray) setIsVisible((oldArray) => [...oldArray, entry.target.id]);
}
};
useEffect(() => {
const observer = new IntersectionObserver(callbackFunction, options);
if (containerRef.current)
containerRef.current.forEach((el) => {
observer.observe(el);
});
return () => {
if (containerRef.current)
containerRef.current.forEach((el) => {
observer.unobserve(el);
});
};
}, [containerRef, options]);
return [containerRef, isVisible];
};
Edit
I fixed it by adding permissions during token generation:
permissions: ['allow_join', 'allow_mod'],
Reproducible demo here
Whenever I try to toggle the mic, webcam or screenshare system, I keep receiving the error:
TypeError: this._mediasoupDevice is null
I've looked everywhere online, I wasn't able to get a single relevant result.
Looking into the source code, the program attempts to to call canProduce on _mediasoupDevice, which obviously fails. I'm not sure why this is happening. Is something wrong with my code or is it something else?
Here's my whole relevant file (backend routes excluded, but you can see them in the above source code):
import * as React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { MeetingProvider, MeetingConsumer, useMeeting, useParticipant } from '#videosdk.live/react-sdk';
import { startLiveStream } from 'src/features/streaming/redux/actions';
import { IState } from 'src/features/core/redux/reducers';
// Helper function for participant loop.
const chunk = (arr) => {
const newArr = [];
while (arr.length) newArr.push(arr.splice(0, 3));
return newArr;
};
const ParticipantView = ({ participantId }) => {
const webcamRef = React.useRef(null);
const micRef = React.useRef(null);
const screenShareRef = React.useRef(null);
const { displayName, webcamStream, micStream, screenShareStream, webcamOn, micOn, screenShareOn } =
useParticipant(participantId);
React.useEffect(() => {
if (webcamRef.current) {
if (webcamOn) {
const mediaStream = new MediaStream();
mediaStream.addTrack(webcamStream.track);
webcamRef.current.srcObject = mediaStream;
webcamRef.current.play().catch((error) => console.error('videoElem.current.play() failed', error));
} else {
webcamRef.current.srcObject = null;
}
}
}, [webcamStream, webcamOn]);
React.useEffect(() => {
if (micRef.current) {
if (micOn) {
const mediaStream = new MediaStream();
mediaStream.addTrack(micStream.track);
micRef.current.srcObject = mediaStream;
micRef.current.play().catch((error) => console.error('videoElem.current.play() failed', error));
} else {
micRef.current.srcObject = null;
}
}
}, [micStream, micOn]);
React.useEffect(() => {
if (screenShareRef.current) {
if (screenShareOn) {
const mediaStream = new MediaStream();
mediaStream.addTrack(screenShareStream.track);
screenShareRef.current.srcObject = mediaStream;
screenShareRef.current.play().catch((error) => console.error('videoElem.current.play() failed', error));
} else {
screenShareRef.current.srcObject = null;
}
}
}, [screenShareStream, screenShareOn]);
return (
<div key={participantId}>
<audio ref={micRef} autoPlay />
{webcamRef || micOn ? (
<div>
<h2>{displayName}</h2>
<video height={'100%'} width={'100%'} ref={webcamRef} autoPlay />
</div>
) : null}
{screenShareOn ? (
<div>
<h2>Screen Shared</h2>
<video height={'100%'} width={'100%'} ref={screenShareRef} autoPlay />
</div>
) : null}
<br />
<span>
Mic:{micOn ? 'Yes' : 'No'}, Camera: {webcamOn ? 'Yes' : 'No'}, Screen Share:{' '}
{screenShareOn ? 'Yes' : 'No'}
</span>
</div>
);
};
const MeetingView = React.memo(() => {
const [joined, setJoined] = React.useState(false);
const { join, leave, toggleMic, toggleWebcam, toggleScreenShare, participants } = useMeeting();
if (!joined) {
return (
<button
onClick={() => {
join();
setJoined(true);
}}
>
join meeting
</button>
);
}
return (
<div>
<div>
<button onClick={leave}>Leave</button>
<button onClick={toggleMic}>toggleMic</button>
<button onClick={toggleWebcam}>toggleWebcam</button>
<button onClick={toggleScreenShare}>toggleScreenShare</button>
</div>
{chunk([...participants.keys()]).map((k) => (
<div key={k} style={{ display: 'flex' }}>
{k.map((l) => (
<ParticipantView key={l} participantId={l} />
))}
</div>
))}
</div>
);
});
const Landing = React.memo(() => {
const [loading, setLoading] = React.useState(false);
const meetingId = useSelector((state: IState) => state.app.streaming.streamConfig.meetingId);
const videoToken = useSelector((state: IState) => state.app.streaming.streamConfig.videoToken);
const dispatch = useDispatch();
const handleClick = React.useCallback(() => {
dispatch(startLiveStream({ payload: { onStart: () => setLoading(true), onEnd: () => setLoading(false) } }));
}, []);
if (!meetingId || !videoToken)
return (
<div>
<button onClick={handleClick}>Go Live</button>
{loading && <div>LOADING</div>}
</div>
);
return (
<MeetingProvider
config={{
meetingId,
name: '<Name-of-participant>',
// participantId: 'Id-of-participant', // optional, auto-generated
micEnabled: true,
webcamEnabled: true,
// maxResolution: '<Maximum-resolution>',
}}
token={videoToken}
>
<MeetingConsumer>{() => <MeetingView />}</MeetingConsumer>
</MeetingProvider>
);
});
export default Landing;
P.S. I'm aware my public and secret keys are exposed here -- new keys will be generated whenever I deploy.
Also, if it matters, I'm using Chrome, but the same issue transpires in Firefox.
I'm refactoring some old code for an alert widget and am abstracting it into its own component that uses DOM portals and conditional rendering. I want to keep as much of the work inside of this component as I possibly can, so ideally I'd love to be able to expose the Alert component itself as well as a function defined inside of that component triggers the render state and style animations so that no outside state management is required. Something like this is what I'm looking to do:
import Alert, { renderAlert } from '../Alert'
const CopyButton = () => (
<>
<Alert text="Text copied!" />
<button onClick={() => renderAlert()}>Copy Your Text</button>
</>
)
Here's what I currently have for the Alert component - right now it takes in a state variable from outside that just flips when the button is clicked and triggers the useEffect inside of the Alert to trigger the renderAlert function. I'd love to just expose renderAlert directly from the component so I can call it without the additional state variable like above.
const Alert = ({ label, color, stateTrigger }) => {
const { Alert__Container, Alert, open } = styles;
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
const portalElement = document.getElementById('portal');
const renderAlert = (): void => {
setAlertRendered(false);
setAlertVisible(false);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
useEffect(() => {
renderAlert();
}, [stateTrigger])
const ele = (
<div className={Alert__Container}>
{ alertRendered && (
<div className={`${Alert} ${alertVisible ? open : ''}`}>
<DesignLibAlert label={label} color={color}/>
</div>
)}
</div>
);
return portalElement
? ReactDOM.createPortal(ele, portalElement) : null;
};
export default Alert;
Though it's not common to "reach" into other components and invoke functions, React does allow a "backdoor" to do so.
useImperativeHandle
React.forwardRef
The idea is to expose out the renderAlert function imperatively via the React ref system.
Example:
import { forwardRef, useImperativeHandle } from 'react';
const Alert = forwardRef(({ label, color, stateTrigger }, ref) => {
const { Alert__Container, Alert, open } = styles;
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
const portalElement = document.getElementById('portal');
const renderAlert = (): void => {
setAlertRendered(false);
setAlertVisible(false);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
useEffect(() => {
renderAlert();
}, [stateTrigger]);
useImperativeHandle(ref, () => ({
renderAlert,
}));
const ele = (
<div className={Alert__Container}>
{ alertRendered && (
<div className={`${Alert} ${alertVisible ? open : ''}`}>
<DesignLibAlert label={label} color={color}/>
</div>
)}
</div>
);
return portalElement
? ReactDOM.createPortal(ele, portalElement) : null;
});
export default Alert;
...
import { useRef } from 'react';
import Alert from '../Alert'
const CopyButton = () => {
const ref = useRef();
const clickHandler = () => {
ref.current?.renderAlert();
};
return (
<>
<Alert ref={ref} text="Text copied!" />
<button onClick={clickHandler}>Copy Your Text</button>
</>
)
};
A more React-way to accomplish this might be to abstract the Alert state into an AlertProvider that renders the portal and handles the rendering of the alert and provides the renderAlert function via the context.
Example:
import { createContext, useContext, useState } from "react";
interface I_Alert {
renderAlert: (text: string) => void;
}
const AlertContext = createContext<I_Alert>({
renderAlert: () => {}
});
const useAlert = () => useContext(AlertContext);
const AlertProvider = ({ children }: { children: React.ReactElement }) => {
const [text, setText] = useState<string>("");
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
...
const renderAlert = (text: string): void => {
setAlertRendered(false);
setAlertVisible(false);
setText(text);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
const ele = <div>{alertRendered && <div> ..... </div>}</div>;
return (
<AlertContext.Provider value={{ renderAlert }}>
{children}
// ... portal ...
</AlertContext.Provider>
);
};
...
const CopyButton = () => {
const { renderAlert } = useAlert();
const clickHandler = () => {
renderAlert("Text copied!");
};
return (
<>
<button onClick={clickHandler}>Copy Your Text</button>
</>
);
};
...
function App() {
return (
<AlertProvider>
...
<div className="App">
...
<CopyButton />
...
</div>
...
</AlertProvider>
);
}
I am seeing some strange behaviour when I am trying to pass down a variable to a child component in react. When I console.log just before the return statement (so console.log(1)) in the parent component the data is correct, however when I console.log in the child component (so console.log(2)) the data has changed??
I have a suspicion that it relates to the randomSelect() function but again when console logging out this looks to only be called twice (as expected).
I have pasted a 'playerOneId' in directly (avoiding using the randomSelect() function) and the data shows correctly in the child component when doing this, hence my suspicion around the randomSelect() function. It could be unrelated but not sure.
A gold medal to anyone can answer this one as it has had me for hours now and I've run out of ideas.
PARENT COMPONENT:
const Board = () => {
const [starships, setStarships] = useState([]);
const [playerSelected, setPlayerSelected] = useState(false);
const [result, setResult] = useState('');
const [playerScore, setPlayerScore] = useState(0);
const [computerScore, setComputerScore] = useState(0);
const STARSHIP_QUERY = `{
allStarships {
starships {
id
name
starshipClass
maxAtmospheringSpeed
costInCredits
passengers
filmConnection {
films {
title
}
}
}
}
}
`
useEffect(() => {
fetch('https://connectr-swapi.herokuapp.com/', {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({query: STARSHIP_QUERY})
})
.then(response => response.json())
.then(data => setStarships(data.data.allStarships.starships))
.catch(error => console.log({'Error': error}))
},[])
const randomSelect = () => {
const random = Math.floor(Math.random() * starShipIds.length);
const selectedId = starShipIds[random];
return selectedId;
}
const starShipIds = starships.map(ship => ship.id)
const valueOneID = randomSelect();
const valueTwoID = randomSelect();
const playerOneId = valueOneID;
const computerId = valueTwoID;
const playerOneShip = starships.filter(ship => ship.id === playerOneId) ;
const computerShip = starships.filter(ship => ship.id === computerId);
const catergorySelect = (key, value) => {
let computerValue = key === 'filmConnection' ? computerShip[0][key].films.length : computerShip[0][key];
if (value > computerValue) {
setResult('You Win!');
setPlayerScore(playerScore + 1)
}
if (value === computerValue) setResult('You Draw!');
if (value < computerValue) {
setResult('You Lose!');
setComputerScore(computerScore + 1)
}
setPlayerSelected(true);
}
console.log(1, playerOneShip[0]); // data is showing correctly
return (
<div className="background">
<div className="row">
<div className="col-12 col-sm-4">
{playerOneShip.length &&
<GameCard
ship={playerOneShip[0]} // data passed in
player='player-one'
select={catergorySelect}
/>
}
{playerSelected &&
<Score
score={playerScore}
colour="white"
/>
}
</div>
<div className="col-12 col-sm-4">
<div className="row">
<h1>{result}</h1>
</div>
<div className="row">
<DefaultBtn
text="START AGAIN"
colour="white"
/>
</div>
</div>
<div className="col-12 col-sm-4">
{playerSelected &&
<React.Fragment>
<div>
{computerShip.length &&
<GameCard
ship={computerShip[0]}
player='computer'
catergorySelect={catergorySelect}
/>
}
</div>
<div>
<Score
score={computerScore}
colour="white"
/>
</div>
</React.Fragment>
}
</div>
</div>
</div>
)
}
CHILD COMPONENT:
const GameCard = props => {
const [selected, setSelected] = useState(0);
const [disableCategory, setDisableCategory] = useState(false);
const {
ship,
player,
select,
} = props;
console.log(2, ship) // different data is showing
const categories = Object.entries(props.ship).map(([key, value], index) => {
const choosenCategory = selected === index ? 'selected' : '';
const disableButton = disableCategory ? 'disable' : '';
switch (key) {
case 'maxAtmospheringSpeed':
return <li className={`card ${player} ${choosenCategory} ${disableButton}`} onClick={(() => { select(key, value); setSelected(index); setDisableCategory(true)})} key={index}>{`Maximum Speed: ${value}`}</li>
case 'costInCredits':
return <li className={`card ${player} ${choosenCategory} ${disableButton}`} onClick={(() => { select(key, value); setSelected(index); setDisableCategory(true)})} key={index}>{`Cost In Credits: ${value}`}</li>
case 'passengers':
return <li className={`card ${player} ${choosenCategory} ${disableButton}`} onClick={(() => { select(key, value); setSelected(index); setDisableCategory(true)})} key={index}>{`Number Of Passengers: ${value}`}</li>
case 'filmConnection':
return <li className={`card ${player} ${choosenCategory} ${disableButton}`} onClick={(() => { select(key, value.films.length); setSelected(index); setDisableCategory(true)})} key={index}>{`Number Of films: ${value.films.length}`}</li>
default:
return null
}
});
return (
<div className="card">
<img className="card-image" src="assets/img/starships/2.jpg" />
<div className="card-body">
<p className="card-title">{`Ship Name: ${ship.name}`}</p>
<p className="card-sub-title">{`Class: ${ship.starshipClass}`}</p>
<ul>
{categories}
</ul>
</div>
</div>
)
}
It's probably a reference issue, the variable passed in props is updated by another render in the parent.
A way of fixing it could be to put all this section of code in a useEffect depending on the loading of the starships:
const starShipIds = starships.map(ship => ship.id)
const valueOneID = randomSelect();
const valueTwoID = randomSelect();
const playerOneId = valueOneID;
const computerId = valueTwoID;
const playerOneShip = starships.filter(ship => ship.id === playerOneId) ;
const computerShip = starships.filter(ship => ship.id === computerId);
It could look like this:
useEffect(() => {
const starShipIds = starships.map(ship => ship.id)
const valueOneID = randomSelect(starShipIds);
const valueTwoID = randomSelect(starShipIds);
const playerOneId = valueOneID;
const computerId = valueTwoID;
setPlayerOneShip(starships.filter(ship => ship.id === playerOneId));
setComputerShip(starships.filter(ship => ship.id === computerId));
},[starships])
For this you need to create a state for player ship and computer ship and replace previous usage of these, like in my example above.
Also, you should pass the starship ids to random select as a parameter and not use a const and assume it has the correct value because it is in the scope of the function.
I have 2 dynamic SSG pages under /blog/[slug], inside of these pages I am rendering a component with next/link, I can click those links to go to another slug, the problem is that I want to run some code that depends on document.title, I tried a combination of possible solutions:
const ref = createRef<HTMLDivElement>()
useEffect(() => {
while (ref.current?.firstChild) {
ref.current.firstChild.remove()
}
const timeout = setTimeout(() => {
if (typeof window === "object") {
const scriptElement = document.createElement("script")
scriptElement.src = "https://utteranc.es/client.js"
scriptElement.async = true
scriptElement.defer = true
scriptElement.setAttribute("crossorigin", "annonymous")
scriptElement.setAttribute("repo", "my/repo")
scriptElement.setAttribute("issue-term", "title")
scriptElement.setAttribute("theme", "photon-dark")
ref.current?.appendChild(scriptElement)
}
}, 0)
return () => {
clearTimeout(timeout)
}
}, [])
...
return <div ref={ref} />
The problem is that useEffect does not run when switching between pages, this code only works when I visit refresh my page, how can I work with this code when navigating between pages to make it work using a up to date document title?
Edit:
const BlogPost = ({
recordMap,
post,
pagination,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
if (!post) {
return null
}
const [script, setScript] = useState<HTMLScriptElement | null>(null)
const ref = createRef<HTMLDivElement>()
const router = useRouter()
useEffect(() => {
const handleRouteChange = () => {
const scriptElement = document.createElement("script")
scriptElement.src = "https://utteranc.es/client.js"
scriptElement.async = true
scriptElement.defer = true
scriptElement.setAttribute("crossorigin", "annonymous")
scriptElement.setAttribute("repo", "daniellwdb/website")
scriptElement.setAttribute("issue-term", "title")
scriptElement.setAttribute("theme", "photon-dark")
setScript(scriptElement)
}
router.events.on("routeChangeComplete", handleRouteChange)
return () => {
router.events.off("routeChangeComplete", handleRouteChange)
}
}, [])
useEffect(() => {
if (script) {
ref.current?.appendChild(script)
setScript(null)
} else {
ref.current?.firstChild?.remove()
}
}, [script])
return (
<>
<Box as="main">
<Container maxW="2xl" mb={16}>
<Button as={NextChakraLink} href="/" variant="link" my={8}>
🏠 Back to home page
</Button>
<NotionRenderer
className="notion-title-center"
recordMap={recordMap}
components={{
// Bit of a hack to add our own component where "NotionRenderer"
// would usually display a collection row.
// eslint-disable-next-line react/display-name
collectionRow: () => <BlogPostHero post={post} />,
code: Code,
equation: Equation,
}}
fullPage
darkMode
/>
<Pagination pagination={pagination ?? {}} />
<Box mt={4} ref={ref} />
<Footer />
</Container>
</Box>
</>
)
}
You can listen to the router.events:
useEffect(() => {
const handleRouteChange = (url, { shallow }) => {
//...
}
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [])