How can animate between array filter methods with React? - reactjs

Im currently building my personal Portfolio and I have an array of objects containing multiple projects, each one of the project object contain a property called category, which can have a value of 'Visual' or 'Complexity'.
My plan is to add two buttons, one for each category, but I would like to animate between the category changes, a simple fade out and fade in would be nice, any idea how can I do this ?
I tried using React Transition Group for this, but didn't manage to figure it out.
The first Component is the ProjectUl, this component will receive a filter prop, which is basically a string indicating which category should the filter method use.
const projects = [
{
name: "Limitless",
category: "visual",
dash: "- Landing Page",
img: imgList.limitless,
gif: imgList.limitlessGif,
},
{
name: "Spacejet",
category: "complexity visual",
dash: "- Landing Page / API Project; ",
img: imgList.spacejet,
},
];
export const ProjectsUl: FC<IProps> = ({ filter }) => {
const [filtered, setFiltered] = useState<IProject[]>([])
useEffect(() => {
const filteredProjects = projects.filter((i) => i.category.includes(filter));
setFiltered(filteredProjects);
}, [filter]);
return (
<ProjectsUlStyle>
{filtered.map((i) => (
<ProjectsLi filtered={i} />
))}
</ProjectsUlStyle>
);
};
Then, inside the ProjectsUl there will be the ProjectsLi, which are the list of projects, here is the code for the ProjectsLi
export const ProjectsLi: FC<any> = ({ filtered }) => {
return (
<LiStyle>
<img src={filtered.img} />
<div>
<span className="dash">{filtered.dash}</span>
<header>
<h1>{filtered.name}</h1>
<FAicons.FaGithub />
<FAicons.FaCode />
</header>
</div>
</LiStyle>
);
};

Related

react-data-table-component ouput data in ExpandComponent

Hello good people on the Internet. I am using react-data-table-component library and I want to output something when a row is expanded, when the first row is expanded it should should show this is row one and the second this is row two like that but i cant figure out how to do it. Any help is appreciated?
this is how the library works :
<DataTable
title="Movie List"
columns={columns}
data={completedProducts}
expandableRows
expandableRowsComponent={ExpandedComponent}
pagination
/>
the expandableRowsComponent props renders out what is shown on expanding in this case ExpandedComponent,which takes the data prop as a parameter
const ExpandedComponent =({ data }) => {
// i would like to show the index of the current expanded row here
});
};
how to i do it?
the row and columns work perfectlty and data is rendered out as expected
updated image with array data
I have extracted this data from a bigger api data as follows
specifically the bp_product_information array
Here is a sample for pulling an item off data to display in the row where the data has child records
import "./styles.css";
import DataTable from "react-data-table-component";
const columns = [
{
name: "Name",
selector: (row) => row.name
},
{
name: "Species",
selector: (row) => row.species
}
];
const data = [
{
id: 1,
name: "Fluffy",
species: "cat",
hobbies: ["cleaning", "meowing", "chasing mice"]
},
{
id: 2,
name: "Boomer",
species: "dog",
hobbies: ["barking", "chewing", "eating"]
}
];
export default function App() {
const ExpandedComponent = ({ data }) => {
return data && data.hobbies ? (
data.hobbies.map((item) => <div>{item}</div>)
) : (
<div>no data</div>
);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<DataTable
title="Pet List"
columns={columns}
data={data}
expandableRows
expandableRowsComponent={ExpandedComponent}
pagination
/>
</div>
);
}
Here is a link to their Storybook with more examples.
https://jbetancur.github.io/react-data-table-component/?path=/story/expandable-basic--basic

How can I rerender only one item in a flatlist?

I have products with a star icon to add this product in wishlist. I map 10 list of products and each map has 3 products like:
(I Map it in Pagerview to swipe to the next products)
Products Component
const ListProducts = [
{
id: 1,
products: [{
product_id: 1,
photos: [...]
}]
},
{
id: 2,
products: [{
product_id: 1,
photos: [...]
}]
}
{
id: 3,
products: [{
product_id: 1,
photos: [...]
}]
},
{
id: 4,
products: [{
product_id: 1,
photos: [...]
}]
}
...
];
function isEq(prev, next) {
if(prev.is_wishlist === next.is_wishlist) {
return true;
}
}
const Item = memo(({ id, photos, is_wishlist, onPress, onPressWishlist }) => {
const findProductIdInWishList = is_wishlist.find((el => el.product_id === id));
return (
<Button style={s.imgBtn} onPress={() => onPress(id)}>
<Button onPress={() => onPressWishlist(id)} style={s.starBtn}>
<AntDesign name={findProductIdInWishList ? 'star' : 'staro'} size={20} color={globalStyles.globalColor} />
</Button>
<ImageSlider photos={photos} />
</Button>
)
// #ts-ignore
}, isEq);
const wishlist = useSelector((state) => state.WishList.wishlist);
const dispatch = useDispatch();
const renderItem: ListRenderItem<IProduct> = ({ item }) => (
<Item
id={item.id}
photos={item.photos}
is_wishlist={wishlist}
onPressWishlist={handlePressWishList}
/>
)
const handlePressWishList = (product_id: string) => {
dispatch(addAndRemoveProductToWishList({product_id}));
};
List of Products component
Products Map:
<PagerView onPageSelected={(e) => handleSetAllIndexes(e.nativeEvent.position)} style={s.container} initialPage={index}>
{
allProducts.map((el, i) => (
<View style={s.viewsContainer} key={i}>
{ allIndex.includes(i) ? (
<View style={s.viewsInnerContainer}>
{ /* products */ }
<Products products={el.products as IProduct[]} total_price={el.total_price} product_name={el.packet_name} />
</View>
) : (
<View style={s.loadingContainer}>
<Loader size={'large'} color='#fff' />
</View>
)
}
</View>)
)
}
</PagerView>
if I click on star icon its dispatch and it goes fast but if I swipe to other products maybe to the last, then I press the star icon to dispatch then its a delay/lag you can see it
I dont add the full code because there are some snippets that has nothing to do with problem.
PS:
Video
I think there are a few issues in your code:
1. Wrong dependency list for useMemo.
In your Item component, you should pass the list of dependency, rather than a compare function:
const Item = memo(({ id, photos, is_wishlist, onPress, onPressWishlist }) => {
...
// #ts-ignore
}, isEq); // <- this is wrong
// Instead, you should do this:
}, [is_wishlist]); // <- this is correct, if you only want to update Item component when `is_wishlist` is changed
2. Never use index as key if item can be reordered
In your products maps component, you are doing:
allProducts.map((el, i) => (
<View style={s.viewsContainer} key={i}>
You should pass id instead, so React will not re-render all items when you insert a new item at the beginning or in the middle:
<View style={s.viewsContainer} key={el.id}>
3. Passing wishlist to all Items, however, wishlist will be updated whenever user click star button on any item.
This causes all Item to re-generate memoized component, because wishlist is changed.
What you want to do here is only passing essential data to Item, which is inWishList (or findProductIdInWishList in your code), which only get changed for affected item:
const renderItem: ListRenderItem<IProduct> = ({ item }) => {
const inWishList= wishlist.find((el => el.product_id === id));
return (
<Item
id={item.id}
photos={item.photos}
inWishList={inWishList}
onPressWishlist={handlePressWishList}
/>
)
}
I am going to edit my answer instead of comment. Before my code, let me explain first. In your current code, whenever 'allProducts' changes, everything will re-render. Whenever 'allIndex' changes, everything will re-render too. The longer the list, the more lag it will be.
Can you try 'useCallback' in this case?
const renderItem = React.useCallback((el, i) => (
<View style={s.viewsContainer} key={i}>
{allIndex.includes(i) ? (
<View style={s.viewsInnerContainer}>
<Products />
</View>
) : (
<Loading />
)}
</View>
),[allIndex])
{allProducts.map(renderItem)}
Now, renderItem will re-render when 'allIndex' changes. Instead of 'PagerView', I still recommend 'FlatList' with 'horizontal={true}' and 'some styles'. If still wanna use 'PagerView', how about 'Lazy' components? 'Lazy components' does not render the components before they came into user view. So, before they are seen, they do not take part in re-redner.
The issue is connected to a way how you edit your data to hold the new value. It looks like the act of adding an item to a wishlist causes all the previous items to re-render.
Therefore the first one works without an issue, but the last one takes a while as it needs to re-render all the other items before.
I would start by changing the key from index to an actual ID of a product block since that could prevent the other "pages" from re-rendering.
If that fails you will probably need to take this block of code into a workshop and check for useless re-renders.

How to map through objects and arrays in translations

I have 2 JSON files with my translation data -> EN file and FR file.
I am using react-i18next to handle my translations this way, which works fine, but since I am having repeatable components I need to map/loop through the translations to get the right output
Example
en:
export const EN = {
page1: {
section1 {
titles: {
title1: "title1_name",
title2: "title2_name",
title3: "title3_name"
},
buttons: {
button1 : "button1_name",
button2 : "button2_name",
button3 : "button3_name",
}
}
section2 { THE SAME AS SECTION 1 }
section3 { THE SAME AS SECTION 1 }
page2 { SAME AS PAGE 1 }
The same thing applies for FR file (with french translations instead)
How can achieve to mapping all e.g titles from section1 and page1. Would that be even correct to use map() ?
Right now my solution is just to use {t page1.section1.0.titles.title1} which of course print the same title everywhere - in this case title1_name
Current Output with using {t page1.section1.0.titles.title1} :
slider1: title1_name
slider2: title1_name
slider3: title1_name
slider4: title1_name
and so on so on...
Expected output:
slider1: title1_name, button1_name
slider2: title2_name, button2_name
slider3: title4_name, button3_name
slider4: title4_name, button4_name
This works, you'll need to do the translation, but this gives you access to the title object in each page and section:
Object.entries(EN).map(([key,object]) => {
Object.entries(object).map(([token, value]) => {
console.log(`${token} : ${value}`);
Object.keys(value).map((key, index) => {
console.log(value[key]); // titles access here
});
});
});
When you are iterating over an object you'll want to use a function that gets data from your object in an array. Traditionally that is Object.keys(), but newer versions of Javascript introduced Object.values() and Object.entries() which can be very helpful depending on the situation. You can access a value from its key like myObject[myKey] so Object.keys() can work in every situation.
The current structure of your JSON file is not ideal because you have totally separate objects for titles and buttons so you can't ensure that you have the same amount of title texts as button texts. I'll address this in a moment, but first here is one way that you can use the current structure.
const MySlider = () => {
const currentlang = ... // get the EN or FR object based on your current language
// an array of all the title texts
const titles = Object.values(currentlang.page1.section1.titles);
// an array of all the button texts
const buttons = Object.values(currentlang.page1.section1.buttons);
return (
<Slider>
{titles.map((title, i) => ( // map through the titles
<Slide
title={title}
buttonText={buttons[i]} // get the button text with the same index -- how do we know this is valid?
key={i} // i as a key is not great
/>
))}
</Slider>
);
};
With some dummy components so that you can render it:
const Slider: React.FC = ({ children }) => <div>{children}</div>;
interface SliderProps {
title: string;
buttonText: string;
}
const Slide: React.FC<SliderProps> = ({ title, buttonText }) => {
return (
<div>
<h2>{title}</h2>
<button>{buttonText}</button>
</div>
);
};
I would recommend grouping the labels by slide rather than by titles and buttons. This ensures that titles and buttons match up, allows easy access by the slide key if you want to customize the order, and gives us a unique key property for our components.
export const EN = {
page1: {
section1: {
slides: {
sale: {
title: "title1_name",
button: "button1_name"
},
featured: {
title: "title2_name",
button: "button2_name"
},
promo: {
title: "title3_name",
button: "button3_name"
},
}
}
}
};
const MySlider = () => {
const currentlang = ... // get the EN or FR object based on your current language
// keyed object of slides
const slides = currentlang.page1.section1.slides;
return (
<Slider>
{Object.entries(slides).map(
// Object.entries gives us an array with elements key and value
([key, value]) => (
<Slide
title={value.title}
buttonText={value.button}
key={key}
/>
)
)}
</Slider>
);
};
Key-based approach to allow custom ordering:
const MySlider = () => {
const currentlang = ... // get the EN or FR object based on your current language
// keyed object of slides
const slides = currentlang.page1.section1.slides;
// helper function renders the slide for a given key
const renderSlide = (key: keyof typeof slides) => {
const {title, button} = slides[key];
return (
<Slide
title={title}
buttonText={button}
/>
)
}
return (
<Slider>
{renderSlide("promo")}
{renderSlide("sale")}
{renderSlide("featured")}
</Slider>
);
};
There is no option for a .map functions on objects but you can use the Object.keys Option.:
var myObject = { 'a': 1, 'b': 2, 'c': 3 };
Object.keys(myObject).map(function(key, index) {
myObject[key] *= 2;
});
console.log(myObject);
// => { 'a': 2, 'b': 4, 'c': 6 }
map function for objects (instead of arrays)
Medium MultiLanguage Website

How to make child component reactive to state change in mobx using mobx-react-lite

I'm using mobx-state-tree and mobx-react-lite, can someone guide me to a better pattern,
wishlist.js - wishlist store
import { types } from 'mobx-state-tree'
export const WishListItem = types.model('WishListItem', {
name: types.string,
price: types.number,
image: "",
}).actions(self => ({
changeName(newName) {
self.name = newName
},
}))
export const WishList = types.model('WishList', {
items: types.optional(types.array(WishListItem), []),
})
root.js - root store
export const RootStore = types.model('RootStore', {
counter: types.optional(Counter, { count: 0 }),
wishList: types.optional(WishList, {
items: [{ image: '', price: 10, name: 'Yoda' }]
}),
})
I'm updating the store as
setInterval(() => store.wishList.items[0].changePrice(Math.random() * 100), 500)
In my Wishlist view
wishlist.jsx
const WishListItem = ({ image, name, price }) => {
return useObserver(
() =>
<div>
<img src={image} />
<h3>{name}</h3>
<h5>{price}</h5>
</div>
)
}
const WishListView = ({ items }) => {
return useObserver(
() => <>
{
items.map(
(item, key) => <WishListItem {...item} key={key} />
)
}
</>
)
}
export default () => useObserver(() => (
<WishListView items={store.wishList.items} />
))
Here I have to use useObserver or Observer at every level of the component tree, to make it reactive, is there any way to pass a reactive reference to the child?
It works perfectly fine with primitive types like string or number, but with an array or an object, I have to either directly refer changing variables at the parent like store.wishList[0].price or use useObserver in the whole tree.
I want to pass the items array to children, and update children on the changes, just this at the root
export default () => useObserver(() => (
<WishListView items={store.wishList.items} />
))
and no more useObserver at it's childrens
Update
A workaround I found was to destructure the array, now the changes are reactive since we are directly accessing the variables that are changing.
export default () => useObserver(() => {
const items = store.wishList.items.map(item => ({ ...item }))
return <WishListView items={items} />
})
and no more useObserver at it's childrens
It is actually better to mark all components as observer if possible. For example, if you mark each Item as observer and one of the items change its name then only this component will rerender. If you dont make Item observer then your whole List will rerender which is quite bad if have lots of items or deep DOM tree. Also it does not make sense to rerender whole list when just one item changes.
Look here for explanation https://mobx.js.org/refguide/observer-component.html#when-to-apply-observer
So your workaround is a bad pratice and should be used only as last resort if you dont have control over children components and can't make them observer.

Display react components in grid structure

I have a React application that is displaying a number of buttons in a modal that form part of a button group.
The values for these buttons are set up in the state object as shown below.
At the moment these buttons display within my modal each on a separate line one after the other.
I would like to have them displayed in a grid type structure. As there are 5 of them this might be 2 on the first 2 lines and then the last one
on the third line perhaps centered if possible.
How could I go about doing this? Sorry I realise I've not offered a solution attempt for this but I'm not sure where to start. I've been searching online
and can't find any examples that match what I am trying to do i.e. where the actual data for the table is set in state. I will continue to do more research but thought I'd ask on here to see if someone could offer any tips.
const marginSelectionControls = [
{ label: '21+', type: '21plus' },
{ label: '16-20', type: '16to20' },
{ label: '11-15', type: '11to15' },
{ label: '6-10', type: '6to10' },
{ label: '1-5', type: '1to5' }
];
const MarginSelectionControls = (props ) => (
<div className="btn-group">
<ButtonGroup toggle={this.toggle}>
{marginSelectionControls.map( ctrl => (
<MarginSelectionControl
key={ctrl.label}
label={ctrl.label}
selectMargin={(winningMargin) => props.selectMargin(ctrl.label)}
selectedmargin={props.selectedmargin}
/>
))}
</ButtonGroup>
</div>
);
##########################################
const MarginSelectionControl = (props) => {
const MarginSelectionControlClasses = [classes.PredResSelectControl];
if (props.selectedmargin === props.label) {
MarginSelectionControlClasses.push(classes.Less);
}
else {
MarginSelectionControlClasses.push(classes.More);
}
return (
<div className={classes.MarginSelectionControl}>
<button
type="button"
label={props.label}
className={MarginSelectionControlClasses.join(' ')}
onClick={props.selectMargin}
selectedmargin={props.selectedmargin}
>{props.label}</button>
</div>
)
};

Resources