React.Children.count or children.length? - reactjs

I'm doing on migration from class based components to functional components.
My problem is legacy codes counts react comopnents using children.length which is different my previous use React.Children.count(children).
Any differences between two of these? which one is better?
...
const SomeParentFC = () => {
const arrays = [1,2,3,4,5]
function renderSomething() {
return arrays.map((value,index) => {
<SomeReactComponent key={index}>value</SomeReactComponent>
})
}
return(
<SomeChildFC>
{ renderSomething() }
</SomeChildFC>
)
}
const SomeChildFC = ({children}) => {
const getMyChildrenCount = () => {
? ???
}
return (
<>
</>
)

you can use React.Children.count , documentation here: reactjs.org/docs/react-api.html#reactchildrencount
const getMyChildrenCount = () => React.Children.count(children);

Related

How to use different Context on same Component (reactjs)

I want to use different context in a common component.
There are 2 providers and 1 common component.
The common component uses different data, and 2 providers have each data indivisually.
e.g.
const useMyProvider1 = () {
return useContext(MyContext1)
}
const MyProvider1 = ({children}) => {
const [data1, setData1] = useState(initial1)
return <MyContext1.Provider value={[data1, setData1]}>{children}</MyContext1.Provider>
}
const useMyProvider2 = () {
return useContext(MyContext2)
}
const MyProvider2 = ({children}) => {
const [data2, setData2] = useState(initial2)
return <MyContext2.Provider value={[data2, setData2]}>{children}</MyContext2.Provider>
}
const Component1 = () => {
return (
<MyProvider1>
<CommonComponent /> // CommonComponent uses data1
</MyProvider1>
)
}
const Component2 = () => {
return (
<MyProvider2>
<CommonComponent /> // Same component but this uses data2
</MyProvider2>
)
}
const CommonComponent = () => {
const {data, setData} = /* I want to use only one provider useMyProvider1 or useMyProvider2 depends on wrapped provider */
return (
<div>this is one of {data}</div>
)
}
It is an idea but not good I think, because CommonComponent depends on each providers.
I want to depend on only wrapped provider.
const CommonComponent = ({providerType}) => {
const {data, setData} = providerType === 'type1' ? useMyProvider1() : useMyProvider2()
return (
<div>this is one of {data}</div>
)
}
Is there any good solutions?

Is useMemo usefull rendering a prop value?

What's the difference between those two patterns ? Is the useMemo usefull ?
Will the number of rerenders change ?
1 - Just rendering a modified prop
const Component = ({ myObject }) => {
const computedValue = changeMyValueWithSomeCalculus(myObject.value)
return (
<div>
{computedValue}
</div>
)
};
2 - Using useMemo
const Component = ({ myObject }) => {
const computedValue = useMemo(() => {
return changeMyValueWithSomeCalculus(myObject.value);
}, [myObject.value]);
return (
<div>
{computedValue}
</div>
)
};
I've ran into this SO question very instructive but doesn't help me out with this choice !

Modifying object inside array with useContext

I've been having trouble using React's useContext hook. I'm trying to update a state I got from my context, but I can't figure out how. I manage to change the object's property value I wanted to but I end up adding another object everytime I run this function. This is some of my code:
A method inside my "CartItem" component.
const addToQuantity = () => {
cartValue.forEach((item) => {
let boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
setCartValue((currentState) => [...currentState, item.quantity++])
} else {
return null;
}
});
};
The "Cart Component" which renders the "CartItem"
const { cart, catalogue } = useContext(ShoppingContext);
const [catalogueValue] = catalogue;
const [cartValue, setCartValue] = cart;
const quantiFyCartItems = () => {
let arr = catalogueValue.map((item) => item.name);
let resultArr = [];
arr.forEach((item) => {
resultArr.push(
cartValue.filter((element) => item === element.name).length
);
});
return resultArr;
};
return (
<div>
{cartValue.map((item, idx) => (
<div key={idx}>
<CartItem
name={item.name}
price={item.price}
quantity={item.quantity}
id={item.id}
/>
<button onClick={quantiFyCartItems}>test</button>
</div>
))}
</div>
);
};
So how do I preserve the previous objects from my cartValue array and still modify a single property value inside an object in such an array?
edit: Here's the ShoppingContext component!
import React, { useState, createContext, useEffect } from "react";
import axios from "axios";
export const ShoppingContext = createContext();
const PRODUCTS_ENDPOINT =
"https://shielded-wildwood-82973.herokuapp.com/products.json";
const VOUCHER_ENDPOINT =
"https://shielded-wildwood-82973.herokuapp.com/vouchers.json";
export const ShoppingProvider = (props) => {
const [catalogue, setCatalogue] = useState([]);
const [cart, setCart] = useState([]);
const [vouchers, setVouchers] = useState([]);
useEffect(() => {
getCatalogueFromApi();
getVoucherFromApi();
}, []);
const getCatalogueFromApi = () => {
axios
.get(PRODUCTS_ENDPOINT)
.then((response) => setCatalogue(response.data.products))
.catch((error) => console.log(error));
};
const getVoucherFromApi = () => {
axios
.get(VOUCHER_ENDPOINT)
.then((response) => setVouchers(response.data.vouchers))
.catch((error) => console.log(error));
};
return (
<ShoppingContext.Provider
value={{
catalogue: [catalogue, setCatalogue],
cart: [cart, setCart],
vouchers: [vouchers, setVouchers],
}}
>
{props.children}
</ShoppingContext.Provider>
);
};
edit2: Thanks to Diesel's suggestion on using map, I came up with this code which is doing the trick!
const newCartValue = cartValue.map((item) => {
const boolean = Object.values(item).includes(props.name);
if (boolean && item.quantity < item.available) {
item.quantity++;
}
return item;
});
removeFromStock();
setCartValue(() => [...newCartValue]);
};```
I'm assuming that you have access to both the value and the ability to set state here:
const addToQuantity = () => {
cartValue.forEach((item) => {
let boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
setCartValue((currentState) => [...currentState, item.quantity++])
} else {
return null;
}
});
};
Now... if you do [...currentState, item.quantity++] you will always add a new item. You're not changing anything. You're also running setCartValue on each item, which isn't necessary. I'm not sure how many can change, but it looks like you want to change values. This is what map is great for.
const addToQuantity = () => {
setCartValue((previousCartValue) => {
const newCartValue = previousCartValue.map((item) => {
const boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
return item.quantity++;
} else {
return null;
}
});
return newCartValue;
});
};
You take all your values, do the modification you want, then you can set that as the new state. Plus it makes a new array, which is nice, as it doesn't mutate your data.
Also, if you know only one item will ever match your criteria, consider the .findIndex method as it short circuits when it finds something (it will stop there), then modify that index.

Map an array in a functional component

function TypeArticleOne(props) {
let apiData = props.apiData;
const [ therapists, setTherapists ] = useState(apiData.topProfiles.therapists);
const [speciality, setSpeciality]= useState('ABA');
const [pageLoading, setPageLoading]= useState(true);
const topProfilesUrl = 'therapists/top/profiles'
useEffect(() => {
console.log(speciality);
getTopTherapists();
window.scrollTo(0, 0);
}, []);
const getTopTherapists = () => {
setPageLoading(true);
loadTopTherapists();
};
const loadTopTherapists = () => {
console.log("second");
props.actions.reqGetTherapistsTopProfiles({
body: {},
headers: null,
resource: `${topProfilesUrl}`
})
};
useEffect(() => {
if (apiData.topProfiles && apiData.topProfiles.success) {
const therapistsLoad = apiData.topProfiles.therapists;
setPageLoading(false);
setTherapists([therapists].concat(therapistsLoad));
}
}, []);
How to map an array in a functional component? I want to map the therapists array from the functional component above.
I call the therapists in an array from an database and I need to map them to render in a card, inside an functional component.
const renderTherapists = (props) => {
const items = props.therapists.map( (t, idx) => (
<TherapistCard therapist={t} key={idx} />
))
return (
<div ref={0} className="therapist-list">
{ items }
</div>
)
}
EDIT: You probably don't need the React Fragment as you already have a as pointed out in the comments. Possibly the point on destructuring might still help.
ORIGINAL:
You might need to wrap the array in a React Fragment (<>{array}</>). This is required as you cannot directly return an array of components.
Also not sure what the structure of your therapist object is, but if you want to destructure it then it should be ({t, idx}) => instead of (t, idx) => (add the curly braces).
const renderTherapists = (props) => {
const items = props.therapists.map( ({t, idx}) => (
<TherapistCard therapist={t} key={idx} />
))
return (
<div ref={0} className="therapist-list">
<>
{ items }
</>
</div>
)
}
This is for React 16.2.0 and later. See the blog post here: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html

Component render twice with a reselect

The following selectManageAdvancedUserFilters selector cause to render my component twice, but without that selector it renders only one time
export const selectManageAdvancedUserFilters = typeCode => {
return createSelector([selectUserFilters(typeCode)], userFilters => {
const manageAdvancedFilters = userFilters.map((filter, index) => {
return {
index: index + 1,
label: filter.name,
value: filter.name,
id: filter.id
};
});
return manageAdvancedFilters;
});
};
export const selectUserFilters = typeCode => {
return createSelector([selectAllUserFilters], allUserFilters =>
allUserFilters.filter(allUserFilter => allUserFilter.type === typeCode)
);
};
export const selectAllUserFilters = createSelector(
[selectControls],
controls => {
return controls && controls.advancedFilters ? controls.advancedFilters : [];
}
);
export const selectControls = state => {
return state.controls.data;
};
here is the usage of selector
const unallocatedFilters = useSelector(
selectDropdownSuggestionFilters('AF1')
)
What is the reason cause to rerender with this selector?
I think the re-render is caused by the implementation details of the dynamic parameter, typeCode.
See the link below:
https://github.com/reduxjs/reselect/issues/392

Resources