React not updates component on store changes - javascript

I have component with cards. Needs to update it on page changing with Redux store. I understand why code is not working but have no idea how to fix it.
function Cards(props) {
const { beers = [] } = props
const changePage = (newPage) => {
page = newPage
}
let page = store.getState().pages
store.subscribe(() => changePage(store.getState().pages))
return (
<div className='cards'>
<div className='cards__content'>
{beers.slice(page*9, page*9+9).map(beer => <MenuCard name={beer.name} description={beer.brewers_tips} id={beer.id} key={beer.id} />)}
</div>
<Pages amount={Math.floor(beers.length / 9)} />
</div>
)
}

Related

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 make a react js element by using props?

I have a functional element in react js like this,
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{["Category", "Design", "Size", "Style"].map((ourOption) => (
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="filter__options__container"
>
<div className="filter__options__button">
{ourOption}
</div>
{isShown && <div className="filter__options__content"> Here I want to return the element using props </div>}
</div>
))}
</div>
);
}
I have created a files called, Category.js, Design.js, Size.js, Style.js.
Now I want to use the props so that I can concatenate like this <{ourOption}> <{ourOption}/> so that this will return element.
Any idea how to do this guys?
Choosing the Type at Runtime
First: Import the components used and create a lookup object
import Category from 'Category';
import Design from 'Design';
import Size from 'Size';
import Style from 'Style';
// ... other imports
const components = {
Category,
Design,
Size,
Style,
// ... other mappings
};
Second: Lookup the component to be rendered
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{["Category", "Design", "Size", "Style"].map((ourOption) => {
const Component = components[ourOption];
return (
...
<div className="filter__options__button">
<Component />
</div>
...
))}}
</div>
);
}
Alternatively you can just import and specify them directly in the array to be mapped.
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{[Category, Design, Size, Style].map((Component) => (
...
<div className="filter__options__button">
<Component />
</div>
...
))}
</div>
);
}
Instead of strings you could iterate over Array of Components
{[Category, Design, Size, Style].map((Component) => (
<Component/>
);
Ill do this as react document
//create components array
const components = {
photo: Category,
video: Design
.....
};
{
Object.keys(components).map((compName) => {
const SpecificSection = components[compName];
return <SpecificSection />;
})
}
Here is a small sample code that you can work with. Use direct component instead of trying to determine by strings.
const Comp1 = () => {
return <p>Comp1 Here</p>
}
const Comp2 = () => {
return <p>Comp 2 Here</p>
}
export default function App() {
return (
<div className="App">
{[Comp1, Comp2].map(Komponent => {
// use Komponent to prevent overriding Component
return <Komponent></Komponent>
})}
</div>
);
}

Too many React components re-rendering, how to limit it?

I am generating a list of components on the screen like so:
const MessagesContainer = ({ messages, categories, addHandler }) => {
const options = categories.map(category => (
{ value: category.name, label: category.name }
));
return (
<div className="d-flex flex-wrap justify-content-center">
{messages.map(message =>
<div key={message.id}>
<MessageEditor
message={message}
options={options}
addHandler={addHandler}
/>
</div>
)}
</div>
);
};
const MessageEditor = ({ message, options, addHandler }) => {
const [modifedMessage, setModifiedMessage] = useState(message);
const [isAdded, setIsAdded] = useState(false);
const textClass = (charLimit - modifedMessage.text.length) > 0 ?
'text-success' : 'text-danger';
const buttonClass = isAdded ? 'danger' : 'primary';
const ref = useRef(null);
const textAreaHandler = textArea => {
const copiedMessage = { ...modifedMessage };
copiedMessage.text = textArea.target.value;
setModifiedMessage(copiedMessage);
};
const addButtonHandler = () => {
const add = !isAdded;
setIsAdded(add);
let selectedCategoires = ref.current.state.value;
// Firing this handler results in ALL the MessageEditor
// componets on the screen being re-rendered
addHandler(modifedMessage, add, selectedCategoires);
}
return (
<div className="d-flex flex-column message-view-container ml-5 mr-5 mb-5">
<div className={`message-count-container ${textClass}`}>
{charLimit - modifedMessage.text.length}
</div>
<Select
ref={ref}
placeholder="Tags"
isMulti
name="tags"
options={options}
defaultValue={[options[0]]}
className="basic-multi-select select-container"
classNamePrefix="select"
isDisabled={isAdded}
/>
<Form.Control
style={{
width:350,
height:220,
resize:'none'
}}
className="mb-1"
as="textarea"
defaultValue={message.text}
onChange={textAreaHandler}
disabled={isAdded}
/>
<Button variant={buttonClass} onClick={addButtonHandler}>
{isAdded ? 'Remove' : 'Add'}
</Button>
</div>
);
};
And the parent component that holds the addHandler:
const { useState } = require("react");
const Messages = () => {
const [messages, setMessages] = useState([]);
const [saveMessages, setSaveMessages] = useState({});
const addHandler = (modifiedMessage, add, selectedCategoires) => {
const copiedSaveMessages = { ...saveMessages };
if (add) {
if (selectedCategoires) {
selectedCategoires = selectedCategoires.map(item => item.value);
}
copiedSaveMessages[modifiedMessage.id] = {
text: modifiedMessage.text,
tags: selectedCategoires ? selectedCategoires : []
}
} else {
delete copiedSaveMessages[modifiedMessage.id];
}
// This results in every single MessageEditor component being
// re-rendered
setSaveMessages(copiedSaveMessages);
};
return (
<div>
{categories &&
<div>
<div className="ml-5 mr-5 mt-5">
<MessagesContainer
messages={messages}
categories={categories}
addHandler={addHandler}
/>
</div>
</div>
}
{Object.keys(saveMessages).length > 0 &&
<div>
<Image
className="upload-icon"
src={uploadIcon}
/>
<div className="text-primary count-container">
<h2>{Object.keys(saveMessages).length}</h2>
</div>
</div>
}
</div>
);
};
The issue is that if I hit the add button an trigger addHandler it causes all the MessageEditor components to re-render. And the performance is very slow if I have a few hundred components on the screen.
I guess this is because the saveMessages state variable belongs to the Messages component and MessageEditor is a child of Messages so it also re-renders.
Is there an approach I can take to update this state without causing all the other components to re-render?
In Messages you should wrap your addHandler in a useCallback hook (React useCallback hook) so that it is not re-created at each render.
const addHandler = useCallback((modifiedMessage, add, selectedCategoires) => {
// function body...
}, []);
Additionally, you can also memoize MessageEditor using React.memo() (React memo).
const MessageEditor = React.memo(({ message, options, addHandler }) => {
// component body...
});

About route(link) and asynchronous fetch data in react

First of all, good evening. I'm trying to improve myself at React. So I'm working on a Starwars project ๐Ÿ‘จโ€๐Ÿ’ป.
I have two problems.
First of all, I listed different characters at the bottom of my character detail page. Again, I want it to be directed to different characters through the same component. But even if the link changes, my component is not refreshed. But the picture is changing ๐Ÿค”.
Note:
sandbox link : https://codesandbox.io/s/github/kasim444/Javascript-Camp-2019/tree/master/challenges/star-wars-app/
my project github link : https://github.com/kasim444/Javascript-Camp-2019/tree/master/challenges/star-wars-app/
// component that I redirect in.
class CharacterDetail extends Component {
render () {
const characterId = this.props.match.params.id;
const {
name,
height,
mass,
hair_color,
skin_color,
eye_color,
birthday_year,
gender,
homeworld,
loading,
} = this.state;
return loading
? <Loading />
: (
<div>
<main className="characterBg">
<DetailHeader imgLink={characterAvatarLink[characterId - 1]} />
<CharacterContent
imgLink={characterAvatarLink[characterId- 1]}
characterInfo={this.state}
/>
</main>
<FeaturedCharacters />
</div>
);
}
}
// feautered character component
function FeaturedCharacters () {
const [characters, setCharacters] = useState ([]);
const [loading, setLoading] = useState (true);
const fetchCharacters = async () => {
const data = await fetch ('https://swapi.co/api/people/');
const fetchPeople = await data.json ();
const feauteredCharacter = fetchPeople.results.filter (
(character, index) => index < 4
);
setCharacters (feauteredCharacter);
setLoading (false);
};
useEffect (() => {
fetchCharacters ();
}, []);
return (
<div>
<h2>Popular Characters</h2>
<div className="d-flex-row container">
{loading
? <PlaceholderDiv />
: characters.map ((character, index) => (
<CharacterCard
key={character.name}
chaId={index + 1}
chaDet={character.name}
imgLink={characterAvatarLink[index]}
/>
))}
</div>
</div>
);
}
// character card link component
const CharacterCard = props => {
const name = props.chaDet;
return (
<Link className="profile_card" to={`/character/${props.chaId}`}>
<div className="profile_image">
<img src={props.imgLink} alt={name} />
</div>
<div className="profile_content">
<h3>{name}</h3>
<div className="read_more d-flex-row">
<img src={showIcon} alt="Show Icon" />
Show More
</div>
</div>
</Link>
);
};
// main component
const App = () => {
return (
<Router>
<div className="st-container d-flex-column">
<Header />
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/movie/:title" component={MovieDetails} />
<Route path="/character/:id" component={CharacterDetail} />
<Route
path="/githubProfile"
component={() => {
window.location.href = 'https://github.com/kasim444/Javascript-Camp-2019/tree/master/challenges/star-wars-app';
return null;
}}
/>
</Switch>
<Footer />
</div>
</Router>
);
};
My second problem is that I draw a data back from the data from Api. I can reach outlines of the character. It's working now. But I feel that there are some things that don't feel right.๐Ÿคจ How can I improve Fetch operations in Axios?
async componentDidMount () {
const characterId = this.props.match.params.id;
const filmSeries = [];
const characterDetail = await axios.get (
`https://swapi.co/api/people/${characterId}/`
);
const filmsFetchLinks = characterDetail.data.films;
const promisesData = await filmsFetchLinks.map(link => axios.get(link));
axios.all (promisesData).then(value => {
value.map (val => filmSeries.push (val.data.title));
let {
name,
height,
mass,
hair_color,
skin_color,
eye_color,
birthday_year,
gender,
homeworld,
films,
} = characterDetail.data;
fetch(homeworld).then(home => home.json()).then(val => this.setState({homeworld: val.name}));
this.setState ({
name,
height,
mass,
hair_color,
skin_color,
eye_color,
birthday_year,
gender,
films: filmSeries,
loading: false,
});
});
}
I'm sorry if I bored you. It seems a little long because the components are interconnected. Thank you in advance for your interest. ๐Ÿ–– ๐Ÿ™
You can use componentDidUpdate and compare the the current parameter id to the previous one (you would have to save it to state) and you fetch data again IFF the two are different. componentDidUpdate will go every time the route changes.
A better approach would be to use useEffect and depend on the parameter id. In the use effect, you do all your data fetching. Something like this:
const { id: characterId } = props.match.params;
React.useEffect(() => {
async function getData() {
// fetch all you data here
}
getData();
}, [characterId]);
You can see a crude version of this here:
https://codesandbox.io/s/star-wars-app-sx8hk

Load more data on scroll

I have a page with a search input, once the user clicks on submit the results come up.
There can be a lot of results (usually not but there can be thousands of results) and I don't want to load them all at once, how can I get a few dozens and fetch more results from he API as the user scrolls down, what's the correct way to do that? I was thinking that Lodash throttle can fit but I couldn't find a good example for it.
This is my react component:
const getContacts = async (searchString) => {
const { data: contactsInfo} = await axios.get(`api/Contats/Search?contactNum=${searchString}`);
return contactsInfo;
};
export default class Home extends React.Component {
state = {
contactsInfo: [],
searchString: '',
};
handleSubmit = async () => {
const { searchString } = this.state;
const contactsInfo = await getContacts(searchString);
this.setState({ contactsInfo });
};
onInputChange = e => {
this.setState({
searchString: e.target.value,
});
};
onMouseMove = e => {
};
render() {
const { contactsInfo, searchString, } = this.state;
return (
<div css={bodyWrap} onMouseMove={e => this.onMouseMove(e)}>
<Header appName="VERIFY" user={user} />
{user.viewApp && (
<div css={innerWrap}>
<SearchInput
searchIcon
value={searchString || ''}
onChange={e => this.onInputChange(e)}
handleSubmit={this.handleSubmit}
/>
{contactsInfo.map(info => (
<SearchResultPanel
info={info}
isAdmin={user.isAdmin}
key={info.id}
/>
))}
</div>
)}
<Footer />
</div>
);
}
}
If the API supports pagination then you can use React-Infinite-Scroll. It looks like this
<div style="height:700px;overflow:auto;">
<InfiniteScroll
pageStart={0}
loadMore={loadFunc}
hasMore={true || false}
loader={<div className="loader">Loading ...</div>}
useWindow={false}>
{items}
</InfiniteScroll>
</div>
However if the REST API does not support it, you can still load the data and show them in chunks to the user with the same library but you would need to handle the current load state by yourself.

Categories

Resources