Can you partially apply a component to some props in React? - reactjs

I have 2 different components AnimeList and MangaList that share similar logic, so I want to share code between them. Here is how I'm currently doing it:
const AnimeList = (props) => <AnimangaList isAnime={true} {...props} />;
const MangaList = (props) => <AnimangaList isAnime={false} {...props} />;
return (
<Tab.Navigator>
<Tab.Screen name="Anime" component={AnimeList} />
<Tab.Screen name="Manga" component={MangaList} />
</Tab.Navigator>
);
Is there a shorter or more convenient way to do this in React / React Native? ie. Something similar to function.bind()? This feels pretty hefty.

I usually go with static array when I need to have different values for few properties on large amount of components.
const pages = [
{
isAnime: true,
name: 'Anime',
},
{
isAnime: false,
name: 'Manga',
},
];
// mocks for some components
const MangaList = ({ isAnime }) => <div>{isAnime}</div>;
const Screen = ({ name, component: Component }) => (
<div>
{name}
<Component />
</div>
);
And then inside Tab.Navigator:
{pages.map((p, i) => (
<Screen key={i} name={p.name} component={(props) => <MangaList isAnime={p.isAnime} {...props} />} />
))}

Related

Question about useContext and re-rendering

Considering a situation where I have an App component, and Contexts components, called 'AuthContext' and 'usersAndProductsContext'.
In the context components I have few states, which are initialized in App component and from there will be given to other components to share the states like so:
return (
<div className="App">
<usersAndProductsContext.Provider
value={{
usersVal: usersVal,
productsVal: productsVal,
toggle_pressed_login_flag: toggle_pressed_login_flag,
handleAddUser: handleAddUser,
}}
>
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
edit_currentLoggedUserId: edit_currentLoggedUserId,
currentLoggedUserId: currentLoggedUserId,
isLoggedInEditVal: isLoggedInEditVal,
}}
>
<Routes>
<Route exact path="/" element={<Login />} />
<Route exact path="farmers" element={<Farmers />} />
<Route exact path="customer" element={<Customer />} />
<Route exact path="register" element={<Register />} />
<Route exact path="farmershop/:id" element={<Farmer />} />
<Route exact path="mystore/" element={<MyStore />} />
</Routes>
</AuthContext.Provider>
</usersAndProductsContext.Provider>
</div>
);
when I pass to other components, as I did using 'Provider', it means I'm sharing with all other components these states which are given within the 'value' prop.
Does that mean that every change of the passed-on-using-provider states, in other component, will mean that the information which was passed on in all components has now changed?
Or did it become some sort of local copy?
To emphasize what I mean:
function MyStore() {
let authCtx = useContext(AuthContext);
let usersAndItemsCtx = useContext(usersAndProductsContext);
...
if I change usersAndItemsCtx from MyStore, will it mean that all other component's state that I have changed?
Hope it made sense?
Regards!
It depends on what you mean by change. If you mean mutating the state then no, but if you mean update the state then yes.
If you do authCtx.someProperty = false;, then do not expect the other components to rerender. The actual way to update the context would by passing a setter function down the context and using it.
Looking at this, there seems to be a handleAddUser, if this is called and it updates the context only then a change will reflected in other components.
value={{
usersVal: usersVal,
productsVal: productsVal,
toggle_pressed_login_flag: toggle_pressed_login_flag,
handleAddUser: handleAddUser,
}}
Here is a sandbox where I have mutated the context. Note, how the setInterval logs the mutated value but there is no update to the UI because the component never rerendered. These kind of bugs are common when mutating state (or context by extension).
The relevant code:
const TextContext = createContext();
function App() {
const [text, setText] = useState({
val: "string",
count: 0
});
return (
<>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<TextContext.Provider value={[text, setText]}>
<Wrapper />
</TextContext.Provider>
</div>
</>
);
}
function Wrapper(props) {
return (
<div style={{ padding: "20px" }}>
<TextControlWithHook />
<br />
<Random />
</div>
);
}
function Random() {
const [text, setText] = useContext(TextContext);
const mutateContext = () => {
text.val = "1231232";
};
return <button onClick={mutateContext}>MUTATE</button>;
}
function TextControlWithHook(props) {
const [text, setText] = useContext(TextContext);
useEffect(() => {
console.log("outside", { text });
const id = setInterval(() => {
console.log(text);
}, 5000);
return () => clearInterval(id);
}, [text]);
return (
<input
className="form-control"
value={text.val}
onChange={(e) => {
const val = e.target.value;
setText((text) => ({ ...text, val }));
}}
/>
);
}

SparklineComponent(...): Nothing was returned from render

I'm building out this project in react and using charts from syncfusion. I'm getting the error that's in the title of this post. It's only when I zoom in and out a bunch or if I close my side bar a few times. The amount of times needed to make this happen is completely random. Here's a few pieces of code
const SparkLine = ({id, height, width, color, data, type, currentColor}) => {
return (
<SparklineComponent
height={height}
width={width}
lineWidth={1}
valueType="Numeric"
fill={color}
border={{ color: currentColor, width: 2 }}
dataSource={data}
xName="xval"
yName="yval"
type={type}
tooltipSettings={{
visible: true,
format: '${xval} : data ${yval}',
trackLineSettings: {
visible: true
}
}}
>
<Inject services={[SparklineTooltip]} />
</SparklineComponent>
)
}
That's the component that returns SparklineComponent which is from the library #syncfusion/ej2-react-charts.
It's saying that it's the actual SparklineComponent that's in that library not any component that I made that's not returning anything. Here's the rest of the chain of code that leads to my index.js
<div className='mt-5'>
<SparkLine
currentColor='blue'
id='line-sparkline'
type='Line'
height='80px'
width='250px'
data={SparklineAreaData}
color='blue'
/>
</div>
This is just a snippet of code from a component called Ecommerce. It returns, at one point in the jsx, the SparkLine component from above. The next one is
<Routes>
{/* Dashbord */}
<Route path='/' element={<Ecommerce />} />
<Route path='/ecommerce' element={<Ecommerce />} />
That's my app.js which is wrapped by a context provider in my index.js
ReactDOM.render(
<ContextProvider>
<App />
</ContextProvider>
, document.getElementById('root')
);
The problem seems to be coming from the activeMenu variable from the state hook I have in my context provider which is used to open and close the side bar when you click the appropriate button or when you zoom in close(small screen size). Here's my ContextProvider
export const ContextProvider = ({children}) => {
const [activeMenu, setActiveMenu] = useState(true);
const [isClicked, setIsClicked] = useState(initialState);
const handleClick = (clicked) => {
setIsClicked({ ...initialState, [clicked]: true})
}
const [screenSize, setScreenSize] = useState(undefined)
console.log(`context provider ${activeMenu}${isClicked.userProfile}${initialState.userProfile}`);
return (
<StateContext.Provider value={{ activeMenu, setActiveMenu, isClicked,
setIsClicked, handleClick, screenSize, setScreenSize,}}>
{children}
</StateContext.Provider>
)
}
export const useStateContext = () => useContext(StateContext);
When I remove the activeMenu variable from app.js that I pull out from useStateContext which I've imported there everything works fine. I have absolutely no idea why.
The Solution is to use "Class Component" and extends "React.PureComponent"
import React from 'react'
import { SparklineComponent, Inject, SparklineTooltip } from '#syncfusion/ej2-react-charts'
export default class SparkLineChart extends React.PureComponent
{
render()
{
const { id, type, height, width, data, currentColor, color } = this.props;
return <SparklineComponent
id={id}
height={height}
width={width}
lineWidth='1'
valueType='Numeric'
type={type}
fill={color}
border={{ color: currentColor, width: 2 }}
dataSource={data}
xName='x-axis'
yName='y-axis'
tooltipSettings={{
visible: true,
format: 'X : ${x-axis} , Y : ${y-axis}',
trackLineSettings: { visible: true }
}}
>
<Inject services={[SparklineTooltip]} />
</SparklineComponent>
}
}

react navigation how to pass navigation object to tabBarButton?

I am using create Bottom Tab Navigator , version 6 , is there a way to pass navigation object from Tab.Screen to TabButton ? taking in account props of tabBarButton that I must also pass them to my custom component (TabButton) , here is my code thanks for your help :
<Tab.Screen key={index} name={item.route} component={item.component}
options={{
headerShown: false,
tabBarButton: (props) => <TabButton {...props} item={item} />
}}
/>
you can pass navigation and tabBarButton props in this way,
The options function receives the navigation prop and the route prop for that screen, so you may also use route there.
<Tab.Screen key={index} name={item.route} component={item.component}
options={({ navigation }) => ({
headerShown: false,
tabBarButton: (props) => <TabButton {...props} navigation={navigation} item={item} />
})
}
/>
Here's the example how you can do it
<Stack.Screen
name={"ComponentName"}
component={ComponentScreen}
options={(props) => {
return {
header: () => (
<CustomHeaderComponent
title={"SomeCoolTitle"}
{...props}
/>
),
};
}}
/>
I just added a global variable and affect to it props.navigation , like so :
return (
<Tab.Screen key={index} name={item.route} component={item.component}
options={( props ) => {
_navigation = props.navigation
return{
tabBarButton: (props) => <TabButton {...props} item={item} navigation={_navigation} />
}
}
}
/>
)

How can I force update React.memo child component?

My main functional component performs a huge amount of useQueries and useMutations on the child component hence I have set it as React.memo so as to not cause re-rendering on each update. Basically, when new products are selected I still see the old products because of memo.
mainFunction.js
const [active, setActive] = useState(false);
const handleToggle = () => setActive(false);
const handleSelection = (resources) => {
const idsFromResources = resources.selection.map((product) => product.variants.map(variant => variant.id));
store.set('bulk-ids', idsFromResources); //loal storage js-store library
handleToggle
};
const emptyState = !store.get('bulk-ids'); // Checks local storage using js-store library
return (
<Page>
<TitleBar
title="Products"
primaryAction={{
content: 'Select products',
onAction: () => {
setActive(!active)
}
}}
/>
<ResourcePicker
resourceType="Product"
showVariants={true}
open={active}
onSelection={(resources) => handleSelection(resources)}
onCancel={handleToggle}
/>
<Button >Add Discount to Products</Button> //Apollo useMutation
{emptyState ? (
<Layout>
Select products to continue
</Layout>
) : (
<ChildComponent />
)}
</Page>
);
ChildComponent.js
class ChildComponent extends React {
return(
store.get(bulk-ids).map((product)=>{
<Query query={GET_VARIANTS} variables={{ id: variant }}>
{({ data, extensions, loading, error }) => {
<Layout>
// Query result UI
<Layout>
}}
</Query>
})
)
}
export deafult React.memo(ChildComponent);
React.memo() is useful when your component always renders the same way with no changes. In your case you need to re-render <ChildComponent> every time bulk-id changes. So you should use useMemo() hook.
function parentComponent() {
... rest of code
const bulkIds = store.get('bulk-ids');
const childComponentMemo = useMemo(() => <ChildComponent ids={bulkIds}/>, [bulkIds]);
return <Page>
... rest of render
{bulkIds ?
childComponentMemo
:(
<Layout>
Select products to continue
</Layout>
)}
</Page>
}
useMemo() returns the same value until buldIds has not changed. More details about useMemo() you can find here.

Pass setState as props or create a handle

Can I pass the setPlaying as props? Or I have to do something like this example code?
Components Two and Three can be in their own file.
export const myComponent = () => {
const [playing, setPlaying] = useState(false);
const handleChange = (show) => {
setPlaying(show);
};
return (
<>
<One />
{!playing ? (
<Two handleChange={handleChange} />
) : (
<Three handleChange={handleChange} />
)}
</>
)
};
const Two = ({ handleChange }) => {
return (
<Container>
<Button onClick={{(e) => handleChange(true)}}>Click to Show Component Three</Button>
</Container>
);
};
const Three = ({ handleChange }) => {
return (
<Container>
<Button onClick={{(e) => handleChange(false)}}>Click to Show Component Two</Button>
</Container>
);
};
Of course. Wrapping setPlaying with handleChange is advantageous if you wish to do any further processing in myComponent. And the conditional rendering will prevent needless re-renders.
Yes, you can do that. Also, it is a good practice that you did with the handler.
ref: Passing setState to child component using React hooks

Resources