How do I use Async with array.map when rendering in ReactJS? - javascript

How would I go about running this async with ReactJS?
{array.map((content, index) => {
const var = await asyncFunction(param)
return(
<div key={index} className="someClass">
<h4 className="anotherClass">{var}</h4>
</div>
)
})}

You have to separate the async processing from the rendering (as of React 17, at least), and you have to use the promise directly, but can be done in a function, stored in a state variable, then rendered. That function can be called when the array changes using useEffect.
Example:
function MyComponent(array) {
const [vars, setVars] = useState([]);
function handleArrayChanged(newArray) {
// note: no error handling, do this yourself as appropriate.
Promise.all(array.map(var => asyncFunction(var)))
.then((newVars) => setVars(newVars))
.catch(error => { /* handle error */ });
}
useEffect(() => {
handleArrayChanged(array);
}, [array]);
return (
<>
{vars.map((var, index) => {
return(
<div key={index} className="someClass">
<h4 className="anotherClass">{var}</h4>
</div>
)
})}
</>
}
Depending on exactly what asyncFunction does, you may want to break that async check out into its own component, as well, but that's subjective based on what asyncFunction does.
function MyComponent({element}) {
const [var, setVar] = useState(undefined)
function handleElementChanged(newElement) {
asyncFunction(newElement)
.then(newVar => setVar(newVar))
.catch(error => { /* handle error */ });
}
useEffect(() => {
handleElementChanged(element);
}, [element]);
return (
<div className="someClass">
<h4 className="anotherClass">{var}</h4>
</div>
);
}
function MyList({array}) {
return (
<>
{array.map(element, index) => (
<MyComponent
key={index}
element={element}
/>
)}
</>
);
}

Related

React calling component function outside of the component

I have a functional component:
function Tile(props: any) {
const selectTile = (() => {
...
})
return (
<div>
...
</div>
)
}
I then create another functional component which will hold many tiles.
function Grid(props: any) {
const selectAllTiles = (() => {
})
return (
<div>
<Tile></Tile>
<Tile></Tile>
<Tile></Tile>
</div>
)
}
My question is, how can I call the selectTile() function for each Tile within the selectAllTiles() function? What approaches are there?
I would make the parent component keep track of selected tiles, and it will then inform each Tile (through props) if it is selected or not.
If a Tile should be able to "make itself selected", then a function can be passed from the parent into each Tile (also through props) that the Tile can call and which will update the selected tiles collection in the parent.
Updated code:
function Tile(props: any) {
const { isSelected, makeSelected } = props;
return (
<div style={{ ... something based on isSelected ... }}>
<button type="button" onClick={makeSelected}>Select me</button>
</div>
)
}
function Grid(props: any) {
const [isSelected, setIsSelected] = useState<bool[]>([false, false, false]);
const makeSelected = (index: int) => {
// ...
setIsSelected(...);
}
return (
<div>
<Tile isSelected={isSelected[0]} makeSelected={() => makeSelected(0)}></Tile>
<Tile isSelected={isSelected[1]} makeSelected={() => makeSelected(1)}></Tile>
<Tile isSelected={isSelected[2]} makeSelected={() => makeSelected(2)}></Tile>
</div>
)
}
This is just an (untested) example, many approaches are possible.
why don't use like this:
function Tile(props: any) {
return (
<div>
...
</div>
)
}
const selectTile = (() => {
...
})

Trigger useEffect with anotherComponents

I have 2 components, the Favorites component, makes a request to the api and maps the data to Card.
I also have a BtnFav button, which receives an individual item, and renders a full or empty heart according to a boolean.
Clicking on the BtnFav render removes a certain item from the favorites database.
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The data flow for now would be something like this:
Favorites component fetches all the complete data and passes it to the Card component, the Card component passes individual data to the BtnFavs component.
Favorites Component:
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>Mis favoritos</h1>
<Card listWines={vinosFavs} />
</div>
);
BtnFavs:
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The pattern you want is called a callback function, just like the onClick of a button. You pass a function to your components that get executed given a condition. If you want fetchWines to be called again, then just pass the function in as a prop.
Favorites Component:
<Card listWines={vinosFavs} refresh={fetchWines} />
Card Component
<BtnFavs onDelete={refresh} ... />
BtnFavs Component
onDelete();
You can name it whatever you want, but generally callbacks will be named like on<condition>.
If you really wanted useEffect to be triggered then you would pass a setState function that set one of the dependencies, but I don't see a point in this case.
I will share code, because this problem its normal for me, i really want to learn and improve that.
const Favorites = () => {
const { favoritesUser } = useFavoritesContext();
const user = useSelector((state) => state.user);
const id = user.id;
const [vinosFavs, setVinosFavs] = useState([]);
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>My favorits</h1>
<Grid listVinos={vinosFavs} />
</div>
);
};
export default Favorites
Grid
export default function Grid({ listVinos }) {
return (
<div>
<ul className={styles.layoutDeVinos}>
{listVinos?.map((element) => {
return <WineCard key={element.id} vino={element} />;
})}
</ul>
</div>
);
}
Card
export default function WineCard({ vino }) {
return (
<>
<div>
<Link to={`/products/${vino.id}`}>
<li>
<div className={styles.card}>
<div
className={styles.img1}
style={{
backgroundImage: `url(${vino.images})`,
}}
></div>
<div className={styles.text}>{vino.descripcion}</div>
<div className={styles.catagory}>
{vino.nombre}
<i className="fas fa-film"></i>
</div>
<div className={styles.views}>
{vino.bodega}
<i className="far fa-eye"></i>{" "}
</div>
</div>
</li>
</Link>
<div className="botonesUsuario">
<BtnFavs vino={vino} />
</div>
</div>
</>
);
}
BTN FAVS
export default function BtnFavs({ vino }) {
const { setFavoritesUser } = useFavoritesContext();
const [boton, setBoton] = useState(false);
const user = useSelector((state) => state.user);
const userId = user.id;
const productId = vino.id;
useEffect(() => {
axios
.post("/api/favoritos/verify", { userId, productId })
.then((bool) => setBoton(bool.data));
}, []);
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
return (
<>
{!user.id ? (
<div></div>
) : boton ? (
<span
class="favIcons material-symbols-rounded"
onClick={handleClickFav}
>
favorite
</span>
) : (
<span className="material-symbols-rounded" onClick={handleClickFav}>
favorite
</span>
)}
</>
);
}

How to pass HTML attributes to child component in React?

I have a parent and a child component, child component has a button, which I'd like to disable it after the first click. This answer works for me in child component. However the function executed on click now exists in parent component, how could I pass the attribute down to the child component? I tried the following and it didn't work.
Parent:
const Home = () => {
let btnRef = useRef();
const handleBtnClick = () => {
if (btnRef.current) {
btnRef.current.setAttribute("disabled", "disabled");
}
}
return (
<>
<Card btnRef={btnRef} handleBtnClick={handleBtnClick} />
</>
)
}
Child:
const Card = ({btnRef, handleBtnClick}) => {
return (
<div>
<button ref={btnRef} onClick={handleBtnClick}>Click me</button>
</div>
)
}
In general, refs should be used only as a last resort in React. React is declarative by nature, so instead of the parent "making" the child disabled (which is what you are doing with the ref) it should just "say" that the child should be disabled (example below):
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({isDisabled, onButtonClick}) => {
return (
<div>
<button disabled={isDisabled} onClick={onButtonClick}>Click me</button>
</div>
)
}
Actually it works if you fix the typo in prop of Card component. Just rename hadnlBtnClick to handleBtnClick
You don't need to mention each prop/attribute by name as you can use javascript Object Destructuring here.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = (props) => {
return (
<div>
<button {...props}>Click me</button>
</div>
)
}
You can also select a few props and use them differently in the child components. for example, see the text prop below.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card text="I'm a Card" isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({text, ...restProps}) => {
return (
<div>
<button {...restProps}>{text}</button>
</div>
)
}

React - combine functions

How could I combine those two functions (handleSelectAll and handleSelectNone)? Toggle (on/off) wouldn't work here as in most cases some options would be 'checked' so you won't know whether to toggle it ALL or NONE so there need to be 2 separate buttons (at least that's what I think). What I was thinking was that the function can be shared
const handleSelectAll = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: true
}
}))
}
const handleSelectNone = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: false
}
}))
}
and then the buttons in a component:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={props.handleSelectAll}>
Select All
</button>
<button onClick={props.handleSelectNone}>
Select None
</button>
</div>
);
};
Would there be a way of just defining one function for both buttons?
I usually do like this, keep things simple and legible, nothing too fancy:
const handleSelect = (selected = false) => {
setCategories((oldCats) =>
oldCats.map((category) => {
return {
...category,
selected,
}
})
)
}
const handleSelectAll = () => {
return handleSelect(true)
}
const handleSelectNone = () => {
return handleSelect()
}
(the render part continues as is)
Doing like so avoids you creating that extra template function passing an argument and creating a new function on every render
Yes. You can wrap the call for props.handleSelectAll and props.handleSelectNone with a function and pass the new value as argument:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={()=>props.handleSelect(true)}>
Select All
</button>
<button onClick={()=>props.handleSelect(false)}>
Select None
</button>
</div>
);
};
[ ()=>props.handleSelect(true) is an arrow function that calls handleSelect on click ]
And the new function will be:
const handleSelect= (newValue) => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: newValue
}
}))
}
You can make it one function using a parameter, such as
const handleChangeAll = (selected) => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: selected
}
}))
}
Then you can call this function with a parameter in each button like this:
const Categories = (props) => {
return(
<div className="categories">
<h2>Categories</h2>
<form className="check-form">
{props.categories.map(category => (
<Category key={category.id} category={category} {...props} />
))}
</form>
<button onClick={() => props.handleChangeAll(true)}>
Select All
</button>
<button onClick={() => props.handleChangeAll(false)}>
Select None
</button>
</div>
);
};
selected: !category.selected
const handleSelectNone = () => {
setCategories(oldCats => oldCats.map(category => {
return {
...category,
selected: !category.selected
}
}))
}

Using setState with onChange does not update

What am I looking for?
A way to use setState() on an input element that updates my state immediately with onChange.
What have I done so far?
I have two components, BooksApp and SearchBook. I am passing result to SearchBook as a prop to be rendered. Then I am capturing the changes on the input element using onChange. The onChange is calling the method I passed as a prop as well.
React documentation says the following, but if I use a callback and then in the callback I invoke setState() it sounds to me that I am back at the same situation, because setState() will again be async:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
What is the issue?
If the user erases the content of the input too quickly, although there is still a console.log('empty') in the console, the setState() does not run and thus result stays the same and does not become an empty array.
This is the content of App.js:
class BooksApp extends Component {
state = {
books: [],
result: [],
query: ''
}
updateQuery = (query) => {
this.setState({ query: query.trim() })
}
objDigger = (arr, diggers) => {
const digged = [];
diggers.forEach((d) => {
let o = {};
o = arr.filter((a) => a.id === d);
o.forEach((i) => {
digged.push(i);
})
});
return digged;
}
filterHelper = (result) => {
const resultKeys = result.map((r) => r.id)
const stateKeys = this.state.books.map((s) => s.id)
// this.objDigger(this.state.books, stateKeys)
// new books
const newKeys = _array.difference(resultKeys, stateKeys);
const newObjs = this.objDigger(result, newKeys)
// existing books
const existingKeys = _array.difference(resultKeys, newKeys)
const existingObjs = this.objDigger(this.state.books, existingKeys);
// search books
const searchObjs = newObjs.concat(existingObjs);
return searchObjs;
}
searchBook = (query) => {
this.updateQuery(query);
if (query) {
BooksAPI.search(query).then((result) => {
result = this.filterHelper(result)
if (!result.error) {
this.setState({
result,
})
}
})
} else {
console.log('empty');
this.setState(state => ({
result: []
}));
}
}
appHandleChange = (query) => {
console.log('searching');
this.searchBook(query);
}
render() {
return (
<div className="app">
<Route path="/search" render={() => (
<SearchBook
result={ this.state.result }
onChange={ this.appHandleChange }
onChangeShelf={ this.changeShelf }/>
)}>
</Route>
</div>
)
}
}
This is the content of SearchBook:
class SearchBook extends Component {
handleChange = (book, newShelf) => {
this.props.onChangeShelf(book, newShelf);
}
handleInputChange = (event) => {
this.props.onChange(event.target.value);
}
render() {
const { result } = this.props;
return (
<div className="search-books">
<div className="search-books-bar">
<Link
to="/"
className="close-search"
>
Close
</Link>
<div className="search-books-input-wrapper">
<input
type="text"
placeholder="Search by title or author"
onChange={ (event) => this.handleInputChange(event) }
/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid">
{result.length>0 && result.map((book) => (
<li key={book.id}>
<div className="book">
<div className="book-top">
<div className="book-cover" style={{ width: 128, height: 193, backgroundImage: `url(${book.imageLinks && book.imageLinks.smallThumbnail})` }}></div>
<BookShelfChanger
shelf={book.shelf ? book.shelf : 'none'}
onChangeShelf={(newShelf) => {
this.handleChange(book, newShelf)
}}
></BookShelfChanger>
</div>
<div className="book-title">{book.title}</div>
<div className="book-authors">{book.authors}</div>
</div>
</li>
))}
</ol>
</div>
</div>
)
}
}

Categories

Resources