How can i give a url to a react components using props? - javascript

Component structure's
const IconSkill = (props) => {
return (
<img
onMouseOver={() => {
const skillIcon = document.getElementById("skills-icon");
skillIcon.classList.add("skills-icon-hover");
setTimeout(() => {
skillIcon.classList.remove("skills-icon-hover");
}, 1500);
}}
id="skills-icon"
className="skills-icon"
src={require(props.srcUrl).default}
alt={props.alt}
/>
)
}
Here you see that i am trying to give a relative URL to src img using props, and it isn't working:
<IconSkill srcUrl={"../../images/skills/figma.svg"} alt="HTML Icon" />
Here console log error:
react-refresh-runtime.development.js:315 Uncaught Error: Cannot find
module '../../images/skills/figma.svg'

try to import it by giving some random name if its under "src" folder:
import ImageName form "../../images/skills/figma.svg";
export default function(){
return <IconSkill srcUrl={ImageName} alt="HTML Icon" />
}
If images is under public folder -> images folder then your code should be:
<IconSkill srcUrl={"./images/skills/figma.svg"} alt="HTML Icon" />
and on IconSkill Component:
const IconSkill = (props) => {
return (
<img
onMouseOver={() => {
const skillIcon = document.getElementById("skills-icon");
skillIcon.classList.add("skills-icon-hover");
setTimeout(() => {
skillIcon.classList.remove("skills-icon-hover");
}, 1500);
}}
id="skills-icon"
className="skills-icon"
src={props.srcUrl}
alt={props.alt}
/>
)
}

Your code is generally not a React code. To achieve what you want your code must look smth like this.
import {useState} from "react";
const IconSkill = (props) => {
const {alt, srcUrl} = props;
//you need some state to control it's hover state
const [skillsIcon, setSkillsIcon] = useState(false);
const handleHover = () => {
setSkillsIcon(true);
setTimeout(() => {
setSkillsIcon(false);
}, 1500);
}
return (
<img
onMouseOver={handleHover}
src={srcUrl}
className={skillsIcon ? "skills-icon-hover" : null}
id="skills-icon"
alt={alt}
/>
)
}

Related

Is it possible to expose a function defined within a React function component to be called in other components?

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>
);
}

Component renders and gives an error before the data is completely loaded from the API

I am pulling data from a crypto coin API. It has 250 coins data in one request. But if I load all of them, the data is not loaded and component tries to render which gives an error. I am following the regular practice of await and useEffect but still the error is persistent.
const Home = () => {
const [search, setSearch] = useContext(SearchContext);
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const getCoinsData = async () => {
try {
const response = await Axios.get(
`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=100&page=1&sparkline=true&price_change_percentage=1h%2C24h%2C7d`
);
setData(response.data);
setLoading(false);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
getCoinsData();
}, []);
const negStyle = {
color: "#D9534F",
};
const positiveStyle = {
color: "#95CD41",
};
return (
<div className="home">
<div className="heading">
<h1>Discover</h1>
<hr className="line" />
</div>
{!loading || data ? (
<div style={{ width: "100%", overflow: "auto" }}>
<table *the entire table goes here* />
</div>
) : (
<img className="loading-gif" src={Loading} alt="Loading.." />
)}
</div>
);
};
export default Home;
This is the entire code. Still when I try to refresh, it gives errors based on how much data loads. Sometimes, .map function is not defined or toFixed is defined etc. It does not keep loading till the whole data is loaded.
Can you show the errors and how did you initialize your state loading and data so we can debug better ?
Otherwise, what I usually do in this case is:
if (!loading && data) return <Table />;
return <img className="loading-gif" ... />;
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
import axios from "axios";
import React from "react";
import { Image } from "#chakra-ui/image";
const Crypto = () => {
const { data, isLoading } = useQuery("crypto", () => {
const endpoint =
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=80&page=1&sparkline=true&price_change_percentage=1h%2C24h%2C7d";
return axios.get(endpoint).then(({ data }) => data);
});
return (
<>
{!isLoading && data ? (
data?.map((e, id) => <Image key={id} src={e.image} />)
) : (
<p>Loading</p>
)}
</>
);
};
export default function App() {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<Crypto />
</QueryClientProvider>
);
}
CodeSandBox Link, Preview
enter image description here

How to avoid rerender of a component in React?

Creating a simple app using React and Redux.
The point is to get photos from the server, show them and if you click on the photo show modal window with bigger photo and comments.
The code for App component
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import './App.scss'
import List from './components/list/List'
import Header from './components/header/Header'
import Footer from './components/footer/Footer'
import ModalContainer from './containers/ModalContainer'
import { getPhotos, openModal } from './redux/actions/actions'
const App = () => {
const { isFetching, error } = useSelector(({ photos }) => photos)
const photos = useSelector(({ photos }) => photos.photos)
const { isOpen } = useSelector(({ modal }) => modal)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getPhotos())
}, [])
const getBigPhoto = (id) => {
dispatch(openModal(id))
}
return (
<div className="container">
<Header>Test App</Header>
<div className="list__content">
{isFetching
? <p>Loading...</p>
: error
? <p>{error}</p>
: photos.map(({ id, url }) => (
<List
key={id}
src={url}
onClick={() => getBigPhoto(id)}
/>
))
}
</div>
<Footer>© 2019-2020</Footer>
{isOpen && <ModalContainer />}
</div>
)
}
export default App
In this line I get photos only once to stop rerender if I refresh the page
useEffect(() => {
dispatch(getPhotos())
}, [])
When I click on the photo my modal opens and I want to stop rerendering all the components. For example for my header I use React.memo HOC like this
import React, { memo } from 'react'
import './Header.scss'
import PropTypes from 'prop-types'
const Header = memo(({ children }) => {
return <div className="header">{children}</div>
})
Header.propTypes = {
children: PropTypes.string,
}
Header.defaultProps = {
children: '',
}
export default Header
It works perfectly when I open and close my modal. Header and Footer are not rerendered. But List component is rerendered every time I open and close a modal window. It's happening because that prop onClick={() => getBigPhoto(id)} in List component creates a new anonymous function every time I click. As you know if your props changed, component is rerendered.
My question is how to avoid rerender of List component in my situation?
You can create a container for List that receives getBigPhoto and an id, create getBigPhoto with useCallback so the function doesn't change:
const ListContainer = React.memo(function ListContainer({
id,
src,
getBigPhoto,
}) {
return (
<List
key={id}
src={scr}
onClick={() => getBigPhoto(id)}
/>
);
});
const App = () => {
const { isFetching, error, photos } = useSelector(
({ photos }) => photos
);
const { isOpen } = useSelector(({ modal }) => modal);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPhotos());
}, []);
//use callback so getBigPhoto doesn't change
const getBigPhoto = React.useCallback((id) => {
dispatch(openModal(id));
}, []);
return (
<div className="container">
<Header>Test App</Header>
<div className="list__content">
{isFetching ? (
<p>Loading...</p>
) : error ? (
<p>{error}</p>
) : (
photos.map(({ id, url }) => (
// render pure component ListContainer
<ListContainer
key={id}
src={url}
id={id}
getBigPhoto={getBigPhoto}
/>
))
)}
</div>
<Footer>© 2019-2020</Footer>
{isOpen && <ModalContainer />}
</div>
);
};

My .filter in react lost when refresh page

I'm trying create a search bar, when user want to search a product.
Here is my Search Input:
const [searchTerm, setSearchTerm] = useState("");
const onSubmit = (e) => {
e.preventDefault();
navigate(`/search/${searchTerm}`);
setIsShowing(false);
setOpacity(1);
};
<FormSearch onSubmit={onSubmit}>
<SearchInput type="text"
placeholder="Type something to search"
onChange={(e)=> setSearchTerm(e.target.value)}
defaultValue={searchTerm} />
<SearchButton type="submit" value="Search" />
</FormSearch>
and here is the router when click search and take user to another page:
<Router>
<SearchInfo
path="/search/:title "
searchTerm={searchTerm}
/>
</Router>
and here is my react function for the page after search:
import React, { useEffect, useState } from "react";
import styled from "styled-components";
const SearchInfo = (props) => {
const [products, setProducts] = useState([]);
const getProductsAPI = () => {
axios
.get("http://localhost:8000/api/products")
.then((res) => {
setProducts(res.data);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
getProductsAPI();
}, [props]);
const InfoWrapper = styled.div`
text-align: center;
`;
return (
<div>
<InfoWrapper>
{products
.filter((product) =>
product.title.includes(props.searchTerm.toUpperCase())
)
.map((filteredItem, i) => (
<Item key={i}>
<ItemTitle> {filteredItem.title} </ItemTitle>
</Item>
))}
</InfoWrapper>
</div>
);
};
export default SearchInfo;
if I refresh the page it will show all my products instead of just props.searchTerm.
How can I fix this? Seems like the props I passed from route didn't session
The searchTerm comes from the state and props you pass, not from the url. Youll need to get the param from the Router and use that instead, see https://reactrouter.com/web/api/Hooks/useparams
Something like:
<Router>
<SearchInfo path="/search/:searchterm"/>
</Router>
import { useParams } from "react-router-dom";
const SearchInfo = (props) => {
let { searchterm } = useParams();
// ...
return (
<div>
<InfoWrapper>
{products.filter((product) => product.title.includes(searchterm))
.map((filteredItem, i) => (
<Item key={i}>
<ItemTitle> {filteredItem.title} </ItemTitle>
</Item>
))}
</InfoWrapper>
</div>
);
};
I don't know why your SearchInfo have path as prop but I think path is supposed to be managed by router, so the ideal structure would be:
<Router path="/search/:searchterm" component={SearchInfo} />
Then you can easily access to location info:
const SearchInfo = (props) => {
// Here is what you need
const {
match: { params },
} = props;
}

How to move state outside of component using context provider

I currently have a preview component which has a reloading functionality attached into it using the useState hook. I now want the ability to refresh this component with the same functionality but with an external component. I know that this can be achieved by the useContext API, however i'm struggling to plug it all together.
Context:
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
const PreviewProvider = PreviewContext.Provider;
PreviewFrame:
const PreviewFrame = forwardRef((props, ref) => {
const { height, width } = props;
const classes = useStyles({ height, width });
return (
<Card className={classes.root} ref={ref}>
<div className={classes.previewWrapper} > {props.children} </div>
<div className={classes.buttonContainer}>
<IconButton label={'Refresh'} onClick={props.toggleReload} />
</div>
</Card>
);
});
PreviewFrameWrapped:
<PreviewFrame
toggleReload={props.toggleReload}
height={props.height}
width={props.width}
ref={frameRef}
>
<PreviewDiv isReloading={props.isReloading} containerRef={containerRef} height={height} width={width} />
</PreviewFrame>
const PreviewDiv = ({ isReloading, containerRef, height, width }) => {
const style = { height: `${height}px`, width: `${width}px`};
return !isReloading ?
<div className='div-which-holds-preview-content' ref={containerRef} style={style} />
: null;
};
Preview:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
So now i want to just be able to import the preview component and be able to refresh it using an external button, so not using the one that's already on the <PreviewFrame>.
I ideally want to consume it like this:
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>
function PreviewControls () {
let { handleRefresh } = React.useContext(PreviewContext);
return <div><button onClick={handleRefresh}>↺ Replay</button></div>
}
Preview With My Attempt at Wrapping with Provider:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return (<PreviewProvider value={{ reloading: reloading, setReloading: setReloading, handleRefresh: toggleReload }} >
<PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
{/* it works if i put the external button called <PreviewControls> here*/}
</PreviewProvider>
);
}
So yeah as i said in the commented out block, it will work if put an external button there, however then that makes it attached/tied to the Preview component itself, I'm really not sure how to transfer the reloading state outside of the Preview into the Provider. Can someone please point out what i'm missing and what i need to do make it work in the way i want to.
All you need to do is to write a custom component PreviewProvider and store in the state of reloading and toggleReload function there. The preview and previewControls can consume it using context
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
export default function PreviewProvider({children}) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewContext.Provider value={{reloading, toggleReload}}>{children}</PreviewContext.Provider>
}
export default function Preview(props) {
const {reloading, toggleReload} = useContext(PreviewContext);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
function PreviewControls () {
let { toggleReload } = React.useContext(PreviewContext);
return <div><button onClick={toggleReload}>↺ Replay</button></div>
}
Finally using it like
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>

Categories