I'm trying to pass some props when I use my custom component with Styled-component.
This is the container I want to focus on, and I want to change the flex-direction via props:
const InputInnerContainer = styled.View`
width: 100%;
height: 42px;
padding-horizontal: 5px;
flex-direction: ${props => props.right ? "row-reverse": "row"};
align-items: center;
justify-content: space-between;
border-radius: 4px;
border-width: 1px;
background-color: ${inputBackgroundColor};
border: 2px solid ${inputBorderColor};
`;
This is my custom component "Input":
const Input = ({ onChangeText, icon, value, label,iconPosition}) => {
return (
<InputContainer>
{label && <LabelText>{label}</LabelText>}
<InputInnerContainer {...iconPosition}>
<View>{icon && <LabelText>{icon}</LabelText>}</View>
<InputField onChangeText={onChangeText} value={value} />
</InputInnerContainer>
</InputContainer>
);
};
And this is where I call the custom component:
<Input
label="Password"
onChangeText={(text) => onChangeText(text)}
value={value}
icon="HIDE"
iconPosition="right"
/>
The props I want to pass is "iconPosition". But I'm not sure how to pass it into the Styled-component. I'm still relatively new to Styled-component, so any ideas are welcome.
Try without destructuring it. Not this way:
<InputInnerContainer {...iconPosition}/>
But this way:
<InputInnerContainer iconPosition={iconPosition}/>
You also need to update your styled component:
const InputInnerContainer = styled.View`
...
flex-direction: ${props => props.iconPosition === "right" ? "row-reverse" : "row"}
...
`
Related
For some reason, the text inside the styled pressable is respecting the padding of its parent if it is not long enough, however, if the text is long enough to wrap to a second line, it goes over the padding of the pressable. I imagine I'm doing something wrong with the flexbox of the Button element, however, I couldn't figure out what it is.
My code looks like this:
const Button: FunctionComponent<Props> = ({ title, icon, children, selected, onPress, ...rest }) => {
return (
<Animated.View
layout={Layout}
>
<StyledButton
accessibilityState={{ selected }}
accessibilityRole="button"
selected={selected}
onPress={onPress}
{...rest}
>
<StyledText variant="b2" accessible={false} selected={selected}>
{title || children}
</StyledText>
</StyledButton>
</Animated.View>
);
};
const StyledButton = styled(Pressable)<{ selected: boolean }>`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border-radius: 100px;
width: auto;
`;
const StyledText = styled(Text)<{ selected: boolean }>`
color: red;
`;
I wanted to know whether my onAdd function in <NewItemButton> will get the latest value of text state.
import { useState } from "react";
import {
NewItemFormContainer,
NewItemInput,
NewItemButton
} from "./styles";
type NewItemFormProps = {
onAdd(text: string): void
}
const NewItemForm = (props: NewItemFormProps) => {
const [text, setText] = useState("");
return (
<NewItemFormContainer>
<NewItemInput
value={text}
onChange={(e) => setText(e.target.value)}
/>
<NewItemButton onClick={() => props.onAdd(text)}>
Create
</NewItemButton>
</NewItemFormContainer>
);
}
export default NewItemForm;
If it does not get the latest value, what other ways can you suggest me? One that comes to my mind to use Refs (forwardRef) and send it directly to the html input element and then call props.onAdd with the current value. But the thing is that I am using styled-components and my NewItemInput looks like this
export const NewItemInput = styled.input`
border-radius: 3px;
border: none;
box-shadow: #091e4240 0px 1px 0px 0px;
margin-bottom: 0.5rem;
padding: 0.5rem 1rem;
width: 100%;
background: #484747;
color: #f1f1f1;
`
So how will do that?
I tried to override the style provided by material ui using css
this is the js file
import TextField from '#material-ui/core/TextField';
import classes from './InputFields.module.css';
export const InputFields = (props) => {
return (
<div className={classes.fields}>
<TextField
id={props.id}
className={classes.field}
label={props.label}
variant="outlined"
type={props.type}
onChange={props.onChange}
value={props.value}
error={props.error}
required
/>
</div>
)
}
export default InputFields
and this is the css file
.fields {
margin: 1rem;
}
.field .MuiInputBase-input{
height: 3rem;
border: 5px solid green;
border-radius: 3px;
font-size: large;
}
any help will be appreciated
Add important key word to a css
.fields {
margin: 1rem !imporant;
}
Add important key word to a css where you wnat to override it will override the predefined css
I don't recommend you to use !important, at least it be necessary:
You need to apply some changes in your css definition to define a selector with a hight level of priority (https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity). In this case you can use input type selector, it has more priority than the class selector:
.fields {
margin: 1rem;
}
.field input { //--> add input selector
height: 3rem;
border: 5px solid green;
border-radius: 3px;
font-size: large;
}
See: https://codesandbox.io/s/material-demo-forked-ko3kl
You can create a custom TextField component with customized input props.
const useStyles = makeStyles(() =>
createStyles({
root: {
height: "3rem",
border: "5px solid green",
borderRadius: 3,
fontSize: "large"
}
}),
);
function MyTextField(props: TextFieldProps) {
const classes = useStyles();
return (
<TextField
variant="outlined"
InputProps={{ classes } as Partial<OutlinedInputProps>}
{...props}
/>
);
}
This is based on 'RedditTextField' example on materialui's customized input documentation here.
I have a parent component that gets data from an API end point using fetch. This data displays like it should. The parent component passes an element of an array of objects to the child component. In the child component, when I do a console log I can see the state when it's undefined and when the state is set. The issue that I am having is when I try to access a key of the state (i.e. ticket.title) I get an error saying that ticket is undefined. Any help with would be great.
TicketList
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import TicketDetails from "./TicketDetails"
export default function TicketList() {
const [tickets, updateTickets] = useState([])
const [ticketIndex, updateticketIndex] = useState("0")
useEffect(() => {
async function fetchTickets() {
const response = await fetch("/api/v1/tickets")
const json = await response.json()
updateTickets(json.data)
}
fetchTickets()
}, [])
return (
<Wrapper>
< div >
<TableTitle>
<h3>Tickets</h3>
<button type="submit">Create A Ticket</button>
</TableTitle>
{
tickets.map((ticket, index) => (
<ListInfo key={ticket._id} onClick={() => updateticketIndex(index)}>
<Left>
<p>{ticket.project}</p>
<p>{ticket.title}</p>
<p>{ticket.description}</p>
</Left>
<Right>
<p>{ticket.ticketType}</p>
<p>{ticket.ticketStatus}</p>
<p>{ticket.ticketPriority}</p>
</Right>
</ListInfo>
))
}
</div>
<TicketDetails key={tickets._id} data={tickets[ticketIndex]} />
</Wrapper>
);
}
const Wrapper = styled.div`
display: flex;
background: white;
grid-area: ticketarea;
height: calc(100vh - 4.25rem);
`
const ListInfo = styled.div`
display: flex;
justify-content: space-between;
width: 100%;
padding: .5rem .75rem;
border-bottom: solid 1px #ccc;
`;
const Left = styled.div`
display: flex;
flex: 2;
flex-direction: column;
p {
padding: .25rem;
}
`;
const Right = styled.div`
display: flex;
flex: 1;
flex-direction: column;
align-items: end;
width: 500px;
p {
padding: .25rem;
}
`;
const TableTitle = styled.div`
display: flex;
justify-content: space-between;
padding: 1rem 1rem;
border-bottom: solid 1px #ccc;
button {
padding: .5rem;
}
`;
TicketDetails
import React, { useEffect, useState } from 'react'
// import TicketInfo from './TicketInfo'
import TicketNotes from "./TicketNotes"
import styled from "styled-components"
export default function TicketDetail(data) {
const [ticket, setTicket] = useState(data)
useEffect(() => {
setTicket(data)
}, [data])
console.log(ticket.data)
return (
<Main>
<TicketInfo key={ticket._id}>
<h2>{ticket.title}</h2>
<Info>
<div>
<InfoItem>
<p>Project</p>
<p>{ticket.project}</p>
</InfoItem>
<InfoItem>
<p>Assigned Dev</p>
<p>{ticket.assignedDev}</p>
</InfoItem>
<InfoItem>
<p>Created By</p>
<p>{ticket.submitter}</p>
</InfoItem>
</div>
<div>
<InfoItem>
<p>Type</p>
<p>{ticket.ticketType}</p>
</InfoItem>
<InfoItem>
<p>Status</p>
<p>{ticket.ticketStatus}</p>
</InfoItem>
<InfoItem>
<p>Priority</p>
<p>{ticket.ticketPriority}</p>
</InfoItem>
</div>
</Info>
<Description>{ticket.description}</Description>
</TicketInfo>
<TicketNotes />
<TicketComment>
<textarea name="" id="" cols="30" rows="10" />
<button type="submit">Submit</button>
</TicketComment>
</Main>
)
}
const TicketInfo = styled.div`
margin: .5rem;
h2{
padding: 0.5rem 0;
}
`;
const Description = styled.p`
padding-top: .5rem;
`;
const Info = styled.div`
display: flex;
justify-content: space-between;
border-bottom: solid 1px #ddd;
`;
const InfoItem = styled.section`
margin: .5rem 0;
p:nth-child(1) {
text-transform: uppercase;
color: #ABB1B6;
font-weight: 500;
padding-bottom: .25rem;
}
`;
const Main = styled.div`
background: white;
`
const TicketComment = styled.div`
display: flex;
flex-direction: column;
width: 40rem;
margin: 0 auto ;
input[type=text] {
height: 5rem;
border: solid 1px black;
}
textarea {
border: solid 1px black;
}
button {
margin-top: .5rem;
padding: .5rem;
width: 6rem;
}
`;
There are a few issues here, let's tackle them in order.
Tickets are undefined
When TicketList is mounted, it fetches tickets. When it renders, it immediately renders TicketDetail. The tickets fetch request won't have finished so tickets is undefined. This is why TicketDetail errors out. The solution is to prevent rendering TicketDetail until the tickets are available. You have a few options.
A bare bones approach is to just prevent rendering until the data is available:
{ !!tickets.length && <TicketDetails key={tickets._id} data={tickets[ticketIndex]} />
This uses how logical operators work in JS. In JS falsey && expression returns falsey, and true && expression returns expression. In this case, we turn ticket.length into a boolean. If it is 0 (i.e. not loaded, therefore false), we return false, which React simply discards. If it is greater than 0 (i.e. loaded, therefore true), we render the component.
This doesn't really result in a positive UX though. Ideally this is solved by showing some kind of Loading spinner or somesuch:
{
!!tickets.length
? <TicketDetails . . . />
: <LoadingSpinner />
}
Child data access
In TicketDetail it seems like you meant to destructure data. Currently you are taking the entire prop object and setting it to ticket. Fixing this should resolve the other half of the issue.
Paradigms
You didn't specifically ask for this, but I’d like to back up and ask why you are putting this prop into state? Typically this only done when performing some kind of ephemeral edit, such as pre-populating a form for editing. In your case it looks like you just want to render the ticket details. This is an anti-pattern, putting it into state just adds more code, it doesn’t help you in any way. The convention in React is to just render props directly, state isn't needed.
I have a simple snapshot test with RTL and Jest, any other prop I use beside type and placeholder I don't see it in the resulting snapshot. This is my input component: (I'm using styled component)
const TextField = ({ className, placeholder, onChange, type }) => {
return (
<Styled>
<Styled.input
type={type}
placeholder={placeholder}
className={className}
onChange={onChange}
/>
</Styled>
);
};
This is my test:
it('shall render correctly', () => {
const { asFragment } = render(
<ThemeProvider theme={theme}>
<TextField placeholder="Ramdom text" type="search" onChange={jest.fn()} />
</ThemeProvider>,
);
expect(asFragment()).toMatchSnapshot();
});
The resulting snapshot:
exports[`TextField component shall render correctly 1`] = `
<DocumentFragment>
.c0 {
position: relative;
}
.c1 {
width: 100%;
outline: none;
border: solid 0px;
border-radius: 10em;
background: #f4f5f8;
padding: 8px 8px 8px NaNpx;
font: 400 1.6rem/normal 'Open Sans','Helvetica','Arial',sans-serif;
-webkit-letter-spacing: normal;
-moz-letter-spacing: normal;
-ms-letter-spacing: normal;
letter-spacing: normal;
text-transform: none;
}
<label
class="c0"
>
<input
class="c1"
placeholder="Ramdom text"
type="search"
/>
</label>
</DocumentFragment>
`;
As you can see there's no onChange prop in the input props.
onChange in your JSX is event handler provided by React and it's not shown in the actual DOM, which RTL renders.