dynamically load images with react - reactjs

I created a component that renders images
this is the component.
import React, { lazy, Suspense } from "react";
const Icon = (props) => {
const { src } = props;
return (
<img src={src} />
);
};
export default Icon;
then I use it like this
import ExampleIcon from "./../images/icons/example.png";
...
<Icon src={ExampleIcon} />
is there a more efficient way to load the icons?
and then just "load" example.png and use it as a source? tried to change it to:
const Icon = (props) => {
const src = lazy(() => import("./../images/icons/" + props.src + ".png"));
return (
<Suspense fallback={<p>loading...</p>}><img src={src} /></Suspense>
);
};
looks like it doesnยดt work that way. any other ideas? thanks!

No, you can't do this, since React.lazy() must be at the top level and only return React components. To lazily load images you can do inside an effect:
function Icon = props => {
const [image, setImage] = useState()
useEffect(() => {
import("./../images/icons/" + props.src + ".png").then(setImage)
}, [props.src])
return image ? <img src={image} /> : 'Loading...'
}
Edit: there's one little problem with this, namely, Webpack will not be able to figure out the file to code split since the string in the import function is not a literal. You could still access files in the public directory dynamically using fetch instead. And perhaps you don't need fetch at all, just provide an url to src and spare the whole hassle.

You could apply this approach:
Preloading images with JavaScript
const img=new Image();
img.src=url;
And how to do it with a hook with an online example:
https://www.selbekk.io/blog/2019/05/how-to-write-a-progressive-image-loading-hook/
Another approach just using hooks:
https://codesandbox.io/s/magical-pine-419kz?file=/src/App.tsx
import React, { useEffect } from "react";
import "./styles.css";
const loadImg = (src: string): Promise<string> =>
new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = () => resolve(src);
img.onerror = () => reject(new Error("could not load image"));
});
export default function App() {
const [src, setSrc] = React.useState("preloadimg");
useEffect(() => {
const load = async () => {
await loadImg(
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8c/Chess_Large.JPG/800px-Chess_Large.JPG"
).then((src) => {
setSrc(src);
});
}; // Execute the created function directly
load();
}, [src, setSrc]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<img src={src} alt="example" />
</div>
);
}

Related

useEffect fails on page refresh

I am an infant programmer and I am trying to fetch an api and style the results using React. My page works fine on the initial load and subsequent saves on VScode,but when I actually refresh the page from the browser I get the error thats posted on imageenter image description here:
Here is my code: App.js
```import React, { useEffect, useState } from 'react';
import './App.css';
import Students from './components/Students';
import styled from 'styled-components';
function App() {
const [studentInfo, setStudentInfo] = useState({});
const [searchResult, setSearchResult] = useState({});
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
getStudents();
}, []);
useEffect(() => {
getStudents();
console.log('useEffect');
}, [searchTerm]);
const getStudents = async () => {
const url = 'https://api.hatchways.io/assessment/students';
console.log(url);
fetch(url)
.then((res) => res.json())
.then((data) => {
console.log(data);
searchTerm != ''
? setStudentInfo(filterStudents(data.students))
: setStudentInfo(data.students);
});
};
const filterStudents = (studentsArray) => {
return studentsArray.filter((info) => {
return (
info.firstName.toLowerCase().includes(searchTerm) ||
info.lastName.toLowerCase().includes(searchTerm)
);
});
};
console.log(searchTerm);
return (
<div className="App">
<Students
studentInfo={studentInfo}
setSearchTerm={setSearchTerm}
searchTerm={searchTerm}
/>
</div>
);
}
export default App;```
here is my component Students.js:
```import React, { useState } from 'react';
import styled from 'styled-components';
import GradeDetails from './GradeDetails';
const Students = ({ studentInfo, searchTerm, setSearchTerm }) => {
console.log(typeof studentInfo);
console.log(studentInfo[0]);
const [isCollapsed, setIsCollapsed] = useState(false);
const handleDetails = () => {
setIsCollapsed(!isCollapsed);
};
const average = (arr) => {
let sum = 0;
arr.map((num) => {
sum = sum + parseInt(num);
});
return sum / arr.length.toFixed(3);
};
console.log(isCollapsed);
return (
<Container>
<Input
type="text"
value={searchTerm}
placeholder="Search by name"
onChange={(e) => setSearchTerm(e.target.value.toLowerCase())}
/>
{studentInfo?.map((student) => (
<Wrapper key={student.id}>
<ImageContainer>
<Image src={student.pic}></Image>
</ImageContainer>
<ContentContainer>
<Name>
{student.firstName} {student.lastName}{' '}
</Name>
<Email>Email: {student.email}</Email>
<Company>Company: {student.company}</Company>
<Skills>Skill: {student.skill}</Skills>
<Average>Average:{average(student.grades)}%</Average>
</ContentContainer>
<ButtonContainer>
<Button onClick={handleDetails}>+</Button>
</ButtonContainer>
{isCollapsed && <GradeDetails studentInfo={studentInfo} />}
</Wrapper>
))}
</Container>
);
};```
Every time I have the error, I comment out the codes in Students.js starting from studentInfo.map until the and save and then uncomment it and save and everything works fine again.
I am hoping someone can help me make this work every time so that I don't have to sit at the edge of my seat all the time. Thank you and I apologize for the long question.
You are using an empty object as the initial state for studentInfo (the value passed to useState hook will be used as the default value - docs):
const [studentInfo, setStudentInfo] = useState({});
.map is only supported on Arrays. So this is failing when the component is rendering before the useEffect has completed and updated the value of studentInfo from an object, to an array. Try swapping your initial state to be an array instead:
const [studentInfo, setStudentInfo] = useState([]);

React Typescript Video Preview

I'm trying to create a video preview for a internal project, with "React & Typescript" using react hooks below is the component code,
import React, { useEffect, useRef, useState } from 'react';
import { INewVideo } from 'src/models';
import { useForm } from 'react-hook-form';
const NewVideo: React.FC = () => {
const { register, handleSubmit } = useForm<INewVideo>();
const [file, setFile] = useState<any>();
const videoChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.currentTarget.files![0]);
setFile(event.currentTarget.files![0])
};
useEffect(() => {
console.log("use effect", file)
}, [file])
return (<div>
<input
accept="video/mp4, video/mov"
onChange={videoChangeHandler}
type="file"
/>
{
file ? (
<div>
{file}
</div>
) : ("No Video")
}
</div>)
};
export default NewVideo;
But I'm not able to set the file, its throwing below error
I need to render upload video & give options for screen capture & trimming features. But these are later stages
You are getting this error because file is not a JSX.Element which you are trying to render in your DOM. Basically you got some Object in your file state. Either you can provide this as a source for HTML.Video Element or you can get file object data from it.
{
file ? <div> {file.name}</div> : "No Video";
}
This code should print the file name in your screen. This is the main place where you are getting some error.
Or if you want to show the preview of your recent upload video you can simply pass that file object as a HTML.Video src. Like it:
{
file ? <div> <video src={URL.createObjectURL(file)} autoPlay /></div> : "No Video";
}
This will show the preview of your video.
I've found below
import React, { useEffect, useState } from 'react';
import { INewVideo } from 'src/models';
import { useForm } from 'react-hook-form';
const NewVideo: React.FC = () => {
const { register } = useForm<INewVideo>();
const [file, setFile] = useState<any>();
const videoChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.currentTarget.files![0];
console.log("File", file);
const reader = new FileReader();
reader.addEventListener("load", () => {
setFile(reader.result);
});
reader.readAsDataURL(event.target.files![0]);
};
useEffect(() => {
console.log("use effect", file)
}, [file])
return (<div>
<input
{...register("Link")}
accept="video/mp4, video/mov"
onChange={videoChangeHandler}
type="file"
/>
<video controls src={file} />
</div>)
};
export default NewVideo;

Update only a specific item on button click

I need to know what I must do to ensure that the texts of all the items(except for the one that needs to) don't have the line-through effect upon clicking the purchase button. Right now, every single item on the list tends to have this effect when I click either one of them. Also, another behavior of this component should be to change the name of the button to Purchased from Purchase and vice-versa on clicking upon it. This is working as it should except for the fact that all the buttons show this behavior rather than only the one that was clicked. I have used Context API to pass data around. The screenshot to show what I mean is attached as well.
The code is as follows:
context.js
import React, {useState, useEffect, createContext} from "react";
export const FoodContext = createContext();
export const FoodProvider = (props) => {
const[food, setFood] = useState([]);
const getData = () => {
const request = {
method : 'GET',
}
fetch("http://localhost:3008/api/get", request)
.then(res => res.json())
.then(data => {
setFood(data)
})
};
useEffect(() => {
getData()
}, []);
return(
<FoodContext.Provider value={[food, setFood]}>
{props.children}
</FoodContext.Provider>
);
}
content_screen.js - This is where the ListItems component is getting rendered.
import React, {Component} from 'react';
import Caption from '../components/caption/caption';
import InputBar from '../components/input_bar/input_bar';
import ListItems from '../components/list_items/list_items';
import "./content_screen.css";
//This is where the ListItems component is getting rendered
export default class ContentScreen extends React.Component {
render() {
return(
<div className="content_screen">
<div className="caption">
<Caption></Caption>
<div className="search_box">
<InputBar></InputBar>
</div>
<div className="box">
<ListItems></ListItems>
</div>
</div>
</div>
);
}
}
list_items.jsx - The component in question
import React,{useState, useContext} from "react";
import { FoodContext } from "../../context";
import "./list_items.css";
const ListItems = () => {
const [food, setFood] = useContext(FoodContext);
const [purchase, setPurchase] = useState(false);
const deleteItem = (id) => {
const request = {
method : 'DELETE'
};
fetch(`http://localhost:3008/api/delete/${id}`, request)
.then(res => res.json())
.then(data => console.log(data));
};
const clicked = () => {
setPurchase(!purchase);
}
return(
<div>
{!food.length ? <p>No Items Added</p> : food.map((key, value) => <div className="list_items" key={key._id}>
{purchase ? <span>{food[value].name}</span> : (food[value].name)}
<button className="x_button" onClick={() => deleteItem(food[value]._id)}>X</button>
<button className="purchase_button" onClick={clicked}>{purchase ? 'Purchased' : 'Purchase'}</button>
</div>)}
</div>
);
}
export default ListItems;
The problem is that you are using the same state for all of them.
You need to have each item that has its own state of being purchased or not.
Make a NEW component ListItem
import React, { useState } from 'react';
export const ListItem = ({ item, deleteItem }) => {
const [purchased, setPurchased] = useState(false);
const purchase = () => {
setPurchased(!purchased)
}
return (
<div className="list_items">
{purchased ? <span>{item.name}</span> : (item.name)}
<button className="x_button" onClick={() => deleteItem(item._id)}>X</button>
<button className="purchase_button" onClick={purchase}>{purchased ? 'Purchased' : 'Purchase'}</button>
</div>
)
}
and then in your ListItems component
import { ListItem } from "./ListItem";
const ListItems = () => {
const [food, setFood] = useContext(FoodContext);
const deleteItem = (id) => {
const request = {
method : 'DELETE'
};
fetch(`http://localhost:3008/api/delete/${id}`, request)
.then(res => res.json())
.then(data => console.log(data));
};
return (
<div>
{food.length
? food.map((item, key) => <ListItem item={item} key={key} deleteItem={deleteItem} />)
: <div> No food available </div>
}
</div>
)
}
for the second part: you have 1 single state (purchase). All your items are conditioned to that same state, meaning if it is true, the will show purchased and if it's false they will show purchase. You need to have different states for them or you can have one state containing all of them ( I'm talking about an array). This is how you should go about it:
const [purchsed, setPurchased] = useState([])
//let's say this is the list of your items ( items)
for ( let i in items.length()){
setPurchased((prevState)=>[...prevState,false]
}
// Up to here we have an array containing false's. The length of the array is
// the same as your items
// give your items a name ( or another attribute that could be read from event)
<button name="item.id" className="purchase_button" onClick={clicked}>{purchase ? 'Purchased' : 'Purchase'}</button>
// This was happened when mapping
//now you can target the name attribute when you call your function
const clicked = (e) => {
let copy = [...purchased]
//make a copy of your state
copy[e.target.name] = !purchased[e.target.name]
setPurchased([...copy])
}
and your first problem is caused by the same mistake. You can follow the same pattern to fix that too. Bear in mind that this was not a quality fix, since there are some mistakes in setting up the foundation of this project.

React lazy recursive import

I have requirement where I need to load component from dynamic folders. For example I have following folders inside components
components
-default
-component-one
-component-two
-component-three
-custom
-component-three
Suppose if componentFolder state set to custom folder then it should load from custom folder .if any component not found in custom folder then it should be load from default folder.
So my question is ,can we possible to import recursively ?
function App() {
const [componentFolder, setComponentFolder] = React.useState("default")
const Home = React.lazy(() => import("./components/" +componentFolder+ "/Home"));
return (
<div className="App">
<Suspense fallback="laoding">
<Home></Home>
</Suspense>
</div>
);
}
the below link has same requirement as i asked
How to check if a pariticular fileExists in reactjs
If you are using Webpack then you can use require.context to load modules dynamically:
import React, { Suspense } from "react";
const load = async (path, file) => {
const defaultPath = "default";
const files = require.context("./components", true, /\.js$/);
try {
return files(`./${path}/${file}.js`);
} catch (err) {
return files(`./${defaultPath}/${file}.js`);
}
};
export default function App() {
const [componentFolder, setComponentFolder] = React.useState("default");
const Home = React.lazy(() => load(componentFolder, "Home"));
return (
<div className="App">
<Suspense fallback="loading">
<Home />
</Suspense>
</div>
);
}
Since lazy returns a promise, you can use its catch block to return another lazy (promise) when the original module was not found.
An example:
import { lazy, Suspense, useState } from "react";
const rotate = {
custom: "default",
default: "custom",
};
function App() {
const [folder, setFolder] = useState("custom");
const [name, setName] = useState("component1");
// Here: catch and return another lazy (promise)
const Component = lazy(() =>
import("./components/" + folder + "/" + name).catch(
(err) => import("./components/" + rotate[folder] + "/" + name)
)
);
return (
<div>
<Suspense fallback="laoding">
<Component />
</Suspense>
<button onClick={() => setFolder(rotate[folder])}>toggle folder</button>
<br />
<button onClick={() => setName("component1")}>load component 1</button>
<button onClick={() => setName("component2")}>load component 2</button>
<button onClick={() => setName("component3")}>load component 3</button>
</div>
);
}
Here is a demo.
Note that Component, defined/created inside App component, will be recreated at every rerender of App. It will cause Component to reset its state when App rerenders.
Based in the others answers and comments here I came up with this:
https://codesandbox.io/s/so-react-lazy-recursive-import-2dqlp?file=/src/App.js
import React, { lazy, Suspense } from "react";
// test the code by removing the _ in front of file names
/*
components/
comp ๐Ÿšซ 3? a root file will not trigger -> go to default
/default
comp ๐Ÿ‘ˆ 3! nice ^^ (but if it not exists will throw an error)
/custom
comp ๐Ÿ‘ˆ 2?
/client
comp ๐Ÿ‘ˆ 1?
/omgStop
heIsAlreadyDead (but works)
/otherClient ...
*/
const recursiveImport = async (
componentName,
targetTree,
defaultTree = "./components/default"
) => {
console.count("paths tested");
if (!targetTree) {
return import(defaultTree + "/" + componentName);
}
return import("./components/" + targetTree + "/" + componentName).catch(
() => {
const newTreeArr = targetTree.split("/");
newTreeArr.pop();
const newTree = newTreeArr.join("/");
return recursiveImport(componentName, newTree, defaultTree);
}
);
};
export default function App() {
const targetTree = "custom/client1";
const Component = lazy(() => recursiveImport("Test", targetTree));
return (
<div>
<Suspense fallback="loading">{<Component />}</Suspense>
</div>
);
}
Folder structure:
This solves all of your requirements?
Simple and objective
const Recipe = React.lazy(() =>
import(`docs/app/Recipes/${props.componentName}`)
.catch(() => ({ default: () => <div>Not found</div> }))
);
I was trying something, endup with a simple solution that you should reach before:
https://codesandbox.io/s/awesome-violet-fr7np?file=/src/App.js

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

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

Resources