Stripe ElementsConsumer with TypeScript: Pass prop? - javascript

I'm using TypeScript 3.8 with ReactJs, using class-style components, and I'm following an example here: https://stripe.com/docs/stripe-js/react
In these docs, they show the use of the ElementsConsumer as such:
const InjectedCheckoutForm = () => {
return (
<ElementsConsumer>
{({elements, stripe}) => (
<CheckoutForm elements={elements} stripe={stripe} />
)}
</ElementsConsumer>
);
};
This works fine. But I'd like to pass my own prop into the inner like :
<InjectedCheckoutForm backgroundColor={"Green"} />
Does anyone have any suggestions on how to achieve this? I can't tell if I should add a parameter here:
const InjectedCheckoutForm = (backgroundColor: string) => {
return (
....???
Also, does this qualify as a "high order component?" I haven't seen this style of wrapping a component before. The closest thing I've seen is the Redux 'connect' function.

import React, { FC } from 'react';
interface TestInterface {
backgroundColor: string
}
const InjectedCheckoutForm: FC<TestInterface> = props => {
...
const x = props.backgroundColor;
...
return (
<View style={{ 'backgroundColor': x }}>Hello boy<View>
)
}
OR SIMPLE
const InjectedCheckoutForm = ({backgroundColor}) => {
...
const x = backgroundColor
...
}

It took writing this out for me to figure it out. I'm less experienced with functional components, but the key was moving from
const InjectedCheckoutForm = () => {
return (
<ElementsConsumer>
{({ elements, stripe }) => (
<CreditCardForm backgroundColor={???} elements={elements} stripe={stripe} />
)}
</ElementsConsumer>
);
};
to
const InjectedCheckoutForm = (props) => {
return (
<ElementsConsumer>
{({ elements, stripe }) => (
<CreditCardForm backgroundColor={props.backgroundColor} elements={elements} stripe={stripe} />
)}
</ElementsConsumer>
);
};
(Props are wrapped in a single 'props' parameter.)
I hope this is helpful to others who may have had the same question.

Related

How to return a component in an async method within a FlatList

I have a functional component
const Foo = () => {
const _renderSomething = async id => {
const data = await database.get(SOME_TABLE).Where(someColumnValue = id);
return Promise.resolve(
<AnotherComponent
data={data} />
);
};
const _renderCard = ({item}) => {
const {code, id} = item;
...
return (
<Card
index={code}>
{_renderSomething(id)}
</Card>
);
};
return (
<FlatList
data={rawData}
initialNumToRender={rawData.length}
keyExtractor={item => item.code}
renderItem={_renderCard}
/>
);
Now, this gives me
ERROR Error: Objects are not valid as a React child (found: object with keys {_U, _V, _W, _X}). If you meant to render a collection of children, use an array instead.
Which I do not quite understand.
First, I notice that there is a syntax error in renderCard. This is not valid JSX since the ending tag doesn't match the opening tag and props should be passed into the component like propName={propValue}.
<Card
index={code}
{_renderSomething(id)}
</FlipCard>
I assume that maybe you intended to write this
<Card index={code}>
{_renderSomething(id)}
</Card>
Where you are passing the result of _renderSomething as the Card component's children prop. Which explains that error that you get as async functions are not valid as React child element.
Instead you can refactor _renderSomething into a separate React component and do the data loading within a useEffect hook.
const Something = ({ id }) => {
const [data, setData] = useState(null);
useEffect(() => {
database.get(...).then(setData);
}, [id])
return (
<AnotherComponent data={data} />
);
};
This can then be used within _renderCard like so
const _renderCard = ({item}) => {
const {code, id} = item;
...
return (
<Card index={code}>
<Something id={id} />
</Card>
);
};

React + TypeScript: Passing components in an Array as props

I am looking for a solution in TypeScript React to pass multiple components in an array. I have declared interfaces and importing it inside my component. But it gives me an error as 'Type 'Element' has no properties in common with type 'IDataView''.
I have added code snippet to what steps i am following. I am not sure what am i missing in the below code.
My Parent Component:
const DataViewContainer:React.FC = () => {
return (
<ViewCollection views={[<BarChart />, <LineChart />]} />
)
}
Child Component :
interface IViewCollection {
views: IDataView[]
}
interface IDataView {
barChartView?: React.ReactNode;
lineChartView?: React.ReactNode;
}
const ViewCollection = (views: IViewCollection): JSX.Element => {
return (
<Carousel>
{views.map((cmp) => { return (cmp) }}
</Carousel>
)
}
According to your type, you suppose to pass an object. Notice the fixes of props, views and the usage inside ViewCollection component.
const DataViewContainer: React.FC = () => {
return (
<ViewCollection
views={{ barChartView: <BarChart />, lineChartView: <LineChart /> }}
/>
);
};
const ViewCollection = (props: IViewCollection): JSX.Element => {
return (
<Carousel>
{props.views.barChartView}
{props.views.lineChartView}
</Carousel>
);
};

How can I fix eslint: react/destructuring-assignment error?

const Container = (props) => {
return (
<StyledDiv>
<SmallScreenDiv>{props.children}</SmallScreenDiv>
</StyledDiv>
);
};
This is my code and error happens at {props.children}
how can I fix this eslint error?
There are two solution for this
Disable the prefer-destructuring feature on the eslint
destructure your props
const Container = ({children}) => {
return (
<StyledDiv>
<SmallScreenDiv>{children}</SmallScreenDiv>
</StyledDiv>
);
};
or
const Container = (props) => {
const {children} = props
return (
<StyledDiv>
<SmallScreenDiv>{children}</SmallScreenDiv>
</StyledDiv>
);
};
I will recommend to use second option.
You can also destruct your props like this:
const Container = ({children}) => {
return (
<StyledDiv>
<SmallScreenDiv>{children}</SmallScreenDiv>
</StyledDiv>
);
};

Force a single re-render with useSelector

This is a follow-up to Refactoring class component to functional component with hooks, getting Uncaught TypeError: func.apply is not a function
I've declared a functional component Parameter that pulls in values from actions/reducers using the useSelector hook:
const Parameter = () => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter)
const dispatch = useDispatch();
const drawerOpen = useSelector(state => state.filterIconClick);
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
return (
prevState => ({
...prevState,
parameterCurrent: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
//some code describing an alert
});
})
.otherwise(function (err) {
alert(
//some code describing a different alert
);
});
}
);
};
const classes = useStyles();
return (
<div>
{drawerOpen ? (
Object.keys(parameterSelect).map((key, index) => {
return (
<div>
<FormControl component="fieldset">
<FormLabel className={classes.label} component="legend">
{key}
</FormLabel>
{parameterSelect[key].map((valKey, valIndex) => {
return (
<RadioGroup
aria-label="parameter"
name="parameter"
value={parameterCurrent[key]}//This is where the change should be reflected in the radio button
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
<FormControlLabel
className={classes.formControlparams}
value={valKey}
control={
<Radio
icon={
<RadioButtonUncheckedIcon fontSize="small" />
}
className={clsx(
classes.icon,
classes.checkedIcon
)}
/>
}
label={valKey}
/>
</RadioGroup>
);
})}
</FormControl>
<Divider className={classes.divider} />
</div>
);
})
) : (
<div />
)
}
</div >
)
};
export default Parameter;
What I need to have happen is for value={parameterCurrent[key]} to rerender on handleParameterChange (the handleChange does update the underlying dashboard data, but the radio button doesn't show as being selected until I close the main component and reopen it). I thought I had a solution where I forced a rerender, but because this is a smaller component that is part of a larger one, it was breaking the other parts of the component (i.e. it was re-rendering and preventing the other component from getting state/props from it's reducers). I've been on the internet searching for solutions for 2 days and haven't found anything that works yet. Any help is really apprecaited! TIA!
useSelector() uses strict === reference equality checks by default, not shallow equality.
To use shallow equal check, use this
import { shallowEqual, useSelector } from 'react-redux'
const selectedData = useSelector(selectorReturningObject, shallowEqual)
Read more
Ok, after a lot of iteration, I found a way to make it work (I'm sure this isn't the prettiest or most efficient, but it works, so I'm going with it). I've posted the code with changes below.
I added the updateState and forceUpdate lines when declaring the overall Parameter function:
const Parameter = () => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter);
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const dispatch = useDispatch();
const drawerOpen = useSelector(state => state.filterIconClick);
Then added the forceUpdate() line here:
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
return (
prevState => ({
...prevState,
parameterCurrent: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
//some code describing an alert
});
})
.otherwise(function (err) {
alert(
//some code describing a different alert
);
});
forceUpdate() //added here
}
);
};
Then called forceUpdate in the return statement on the item I wanted to re-render:
<RadioGroup
aria-label="parameter"
name="parameter"
value={forceUpdate, parameterCurrent[key]}//added forceUpdate here
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
I've tested this, and it doesn't break any of the other code. Thanks!

How can I prevent compound component from re-rendering?

I am using react context, and all it contains at the moment are 3 items: contacts and editingContact, and editContact:
interface ContactsContextProps {
contacts: Contact[];
editingContact: Contact;
editContact: (contact: Contact) => () => void // being lazy and this is from an onClick
}
const ContactsContext = React.createContext<Partial<ContactsContextProps>>({
editContact: (contact: Contact) => () => {}
})
const ContactsProvider: React.FunctionComponent = props => {
const [contacts, setContacts] = useState<Contact[]>();
const [editingContact, setEditingContact] = useState<Contact>();
React.useEffect(() => {
// fetch contacts, and setContacts(contacts)
}, [])
const editContact = React.useCallback((contact: Contact) => {
return function() {
setEditingContact(contact);
}
})
return (
<ContactsContext.Provider
value={{
editingContact,
editContact,
contacts
}}
>
{props.children}
</ContactsContext.Provider>
)
}
Here's how it is being used:
const ContactsList: React.FunctionComponent<{
contacts: Contact[];
}> = React.memo(props => {
return (
<>
{props.contacts.map(contact => (
<Card key={contact.id} contact={contact} />
))}
</>
);
});
const Wrapper: React.FunctionComponent = () => {
const contactsCtx = React.useContext(ContactsContext);
return (
<>
<Box className={styles.main}>
<Header />
{contactsCtx.contacts && <ContactsList contacts={contactsCtx.contacts} />}
</Box>
{contactsCtx.editingContact && <EditContactModal />}
</>
);
};
The <Card /> only has an edit button right now, which calls contactsContext.editContact(). However, each time this is called, all the Cards re-render. I placed a console.log('card') in each Card, and it logs card 10 times (I have 10 contacts right now).
What am I doing wrong?
There has been a discussion in a React Github issue, basically there is 3 possible solutions for this:
Option 1 (Preferred): Split contexts that don't change together
Option 2: Split your component in two, put memo in between
Option 3: One component with useMemo inside
You should check the link for examples about it.

Categories