I have a Card component and a QuestionCard component.
what I want to do is when a user uploads a post or asks a question, it shows the respective component. And this is my idea of doing it :
const CardList = ({cardList}) => {
return (
<div className="cardWrapper">
<div className="cardColumn">
{cardList.map((card)=>{
if(){
<Card />
} else{
<QuestionCard />
}
})}
</div>
but I don't know what to fill in the expression. Am I have the right idea? Or there are some other ways to do it?
Here is some reference how what it should look like:
reference
Make sure you return the component to be rendered inside map.
if (){
return <Card / > ;
}
else {
return <QuestionCard / > ;
}
If I understand correctly:
If a question is asked by the user — we wish to render <QuestionCard />;
If a post is uploaded by the user — we wish to render <Card />.
First off, you need a way to determine if the looped-over object is a question or a post:
One way of doing so is adding 1 or 2 boolean properties to your cardList array of objects:
isQuestion — if set to true then the card is a question;
isPost — if set to true then the card is an uploaded post.
In fact, one of the two is plenty enough for this use case.
card.isQuestion ? (Render <QuestionCard/>) : (Render <Card />)
I am using a ternary operator because it is easier to read than an if statement.
Provided that's what you want to do, your test would look like this:
{cardList.map((card, id) =>
card.isPost ? ( // Render <Card /> if the card is a post
<Card key={id} />
) : ( // If the card isn't an uploaded post then it is necessarily a question
<QuestionCard key={id} />
)
)}
P.S. You should probably use NextJS' dynamic styling system depending on how much your project is going to scale, to avoid any mix-ups between the same classname in different files in the future.
Related
This isn't as much of a problem I'm having, I'm just trying to clarify why my code works. As is clear, brackets allows me to embed Javascript into the return statement. However, does the parenthesis in my map method 'return' me to JSX and allow me to render components and add other JSX elements? Like I said, this works, I just want to be sure I have a firm understanding of why it works since my Google attempts to confirm my suspicions haven't proven useful
export const TileList = ({tiles}) => {
return (
<div>
{tiles.map((tile, index) => (
<Tile tile={tile} key={index}/>
))
}
</div>
);
};
This is for a simple little React site that creates a 'tile' for every Contact information the user inputs. The code allows me to map through the array of contacts and create a tile for each input. Here's the subsequent component (that also works):
export const Tile = ({tile}) => {
let tiles = Object.values(tile)
return (
<div className="tile-container">
{tiles.map((tile, index) => (
<p key={index}
className={index === 0 ? 'tile-title' : 'tile'}
>{tile}</p>
))}
</div>
);
};
I've been trying to find an answer to this for a while now and the library, while being very nice and well done... lack very much in clear documentation. I'm hoping to partipate in improving it once I get a clear understanding of it.
Here's what I'm trying to accomplish.
I have a hierarchy of object that is quite complex and multiple draggable type can be mixed in the same level and some of these can even have childrens that are these same dragable.
That render the "type" property not working for me. I want to combine "IsDropDisabled" with "draggingOverWith" to make it happen instead and manage the complexity there.
Basically, the idea is that when the item I'm currently dragging is passing "over" a potential dropable, I want to check the type against an "allowed" array of type and allow it if the type is right.
For that, I want to access "snapshot.draggingOverWith" from the Droppable but the issue is... that "IsDropDisabled" is above in the code hierarchy so I'm kinda of lost as to how the code in the library actually does that comparison.
The idea would be something like that:
<Droppable droppableId={props.verticalgroup.id} isDropDisabled={() => {CompareTypes(snapshot.draggingOverWith, ['Type1', 'Type3'])}>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className={snapshot.isDraggingOver ? 'changeBackground' : ''}
>
...[Other Code]
</div>
)}
</Droppable>
Thanks for helping.
I think you could try to use the onDragStart method from the DragDropContext and send required information to Droppable for isDropDisabled to work conditionally.
Like they do here on their egghead course video.
const [isDropDisabled, setIsDropDisabled] = useState(false);
const onDragStart = (task) => {
setIsDropDisabled(task.something === 'xyz') // <= your condition goes here
}
and
<DragDropContext onDragStart={this.onDragStart} ...>
...
</DragDropContext>
lastly, in your Droppable use that as value
<Droppable droppableId={props.verticalgroup.id} isDropDisabled={isDropDisabled}>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className={snapshot.isDraggingOver ? 'changeBackground' : ''}
>
...[Other Code]
</div>
)}
</Droppable>
I'm building an app that will need a complex form generation (from json/js object).
Here's an example of what I'm building: https://codesandbox.io/s/formik-form-wizard-test-vcj1t
My problem is the following:
all the input fields lose focus on every value change
The folder structure of the CodeSandbox project is the following (for easier code comprehension):
./src:
App.js - main file with all the form generation (generateWizardSteps is the main function that does all the generation).
formSetup.js - that's an object that defined form configuration. From objects like this I need to build dynamic forms. This object has the array of pages (these are the steps of the wizard), and each page has an array of fields (like input, select, checkbox etc). In its turn, each field has props property. props is what I pass to the React component as props.
formComponents.js - this file contains all the form field React components that I use for generating my forms.
decorateWithFormik.js - this file is just to make App.js a bit smaller. It's just the useFormik decorator.
The form is built using the formik library. Also, as I need a wizard-like form, I've found a nice library for it: formik-wizard-form.
I've looked through the stackoverflow questions on similar topics but couldn't find something that could fit my needs. Many questions/answers are about the problem with dynamic key props, but mine are static as far as I can tell (they all are taken from the initial formSetup object).
Another thing that I've noticed is that my form gets re-rendered on every value change, but I'm not sure if this is a problem at all.
Could you help me to figure out what the problem is, why does it happend and how to make my form fields not lose focus?
Solved the issue
I've removed all the possible component creation code and moved everything inside the component props of the Step component provided by the formik-wizard-form library.
Here's my working solution: https://codesandbox.io/s/formik-form-wizard-test-lz3x7 (hope this will help somebody)
Many thanks to everyone in the comments section!
Main insights:
Losing focus means that the component unmounts and remounts every time.
This unmounting/remounting behaviour on every change was caused by the creation of components inside the render function of another component. Consequently, on every re-render the input components were created anew.
My final working code:
const MyForm = props => {
return (
<FormikWizardProvider {...props}>
{renderProps => (
<Wizard {...renderProps}>
<StepsList>
{formSetup.pages.map(page => (
<Step
title={page.name}
key={page.name}
component={() =>
page.fields.map(field => {
if (
field.props &&
field.props.visibilityCheckbox &&
!props.values[field.props.visibilityCheckbox]
) {
return null;
}
const fieldProps = {
formik: props,
key: field.props.name,
...field.props
};
switch (field.type) {
case "input":
return <MyTextInput {...fieldProps} />;
case "radio":
return <RadioButtonGroup {...fieldProps} />;
case "checkbox":
return <MyCheckbox {...fieldProps} />;
case "select":
return <MySelect {...fieldProps} />;
default:
return null;
}
})
}
/>
))}
</StepsList>
<ButtonsList>
<PreviousButton />
<NextButton />
<SubmitButton />
</ButtonsList>
</Wizard>
)}
</FormikWizardProvider>
);
};
export default decorateWizardWithFormik(MyForm);
To get my desired behavior I ended up nesting ternarys inside of my Context.Consumer and it looks pretty ugly.
I have tried refactoring out some of the logic, but I have been unable to get rerenders to trigger when the props/state change unless I use Context.Consumer.
Working on an app for a project. It uses SWAPI to search for pretty much anything in the Star Wars universe. I have a that changes depending on the user's input, and then loads the results of their search.
I started out just trying to get the results to render after the fetch finished and was having some issues of handling the props/state update of the component not triggering a re-render. We recently learned about React's Context api so I decided to try using that in this simple project even though it's 100% not necessary. I did some reading and ended up with the Context.Consumer. It works great for what I want, and triggers re-renders when I need it to.
render() {
return (
<AppContext.Consumer>
// value here is: characterData: this.state.characterData from App.js
// characterData is in App.js' state and is updated after the fetch is successful.
{value => (
<section className="results-container" style={this.styles}>
{/* FIX refactor this \/ */}
{/*
If they haven't searched for anything, characterData doesn't exist yet,
well it's an empty {object} so it renders the 'Enter a name and hit submit!'
If they search and the results are empty, it tells them to try another name.
If the search works it renders the results.
*/}
{value.characterData.results ? (
value.characterData.results.length > 0 ? (
value.characterData.results.map(item => (
<p style={this.styles} key={item.url} className={item.index}>
{item.name ? item.name : item.title}
</p>
))
) : (
<span>Coulnd't find anything! Try another name or topic.</span>
)
) : (
<span className="default-results-text">
Enter a name and hit submit!
</span>
)}
{/* FIX refactor this /\ */}
</section>
)}
</AppContext.Consumer>
);
}
I am not getting any errors, just trying to figure out a way to clean this up.
When trying to move logic outside the Context.Consumer I have to fall back to using state/props and then it never rerenders.
I've tried using componentWillReceiveProps() and static getDerivedStateFromProps() but again couldn't get rerenders to trigger.
Hosted on Zeit: https://starwars-search-nm7mk0268.now.sh/
I ended up refactoring all of the logic to the parent component and just returning the value of all the logic. This allowed me to get away from nested ternarys and clean up this component's render method
Background
I'm working on a final project for school that displays a list of tv shows/programs using cards. I am trying to set up a way to delete a card. I'm getting all sorts of turned around trying to get this delete to work on my frontend. I've spent so long researching the issue that every solution I think of now seems both right and wrong.
First, there was a problem where programId was undefined which I fixed. Now I can't figure out how make make my this in this.handleClick work. This solution made me think that I might need to use bind. But I am not sure.
Code
This is a section of my Program.js file, where the card is styled and props passed in to populate fields like name, network, and image. I use props.match.params because I've implemented React Router and need to access each card's data for the ternary statements inside. EDIT: Program.js is a functional component. I've included a link.
let handleClick = (id) => {
this.props.deleteProgram(id)
}
let program = props.program ? props.program : props.programs[props.match.params.id - 1]
let programId = program.id
return(
<Grid.Column>
<Card onClick={(_) => this.handleClick(programId)}>
<Image src={program ? program.image : null} wrapped ui={false} />
<Card.Content>
<Card.Header>{program ? program.name: null}</Card.Header>
<Card.Meta>
<span className='date'>{program ? program.network : null
</span>
</Card.Meta>
Here is my Programs.js file, which creates each Program component. I am unsure whether I should import my {deleteProgram} action/function in this file where I am creating each Program, or if I need to import it directly in Program.
EDIT: Programs.js is a functional component. I've added a link.
<Grid columns='six' divided='vertically'>
<Grid.Row >
{props.programs.map((program) => <Program key={program.id} program={program} />)}
</Grid.Row>
</Grid>
This is the programsReducer file with the case statement for deleting a program.
case 'DELETE_PROGRAM':
const programs = state.programs.filter(program => program.id !==
action.id)
return {programs}
Here is my deleteProgram action/function.
return (dispatch) => {
fetch(`http://localhost:3000/api/v1/programs/${id}`,{
method: 'DELETE',
headers: {'Content-Type': 'application/json'}
})
.then(res => res.json())
.then(res => dispatch({type: 'DELETE_PROGRAM', id: id}))
}
}
Recap of Questions
Do you know why I am getting an error that this in my this.handleSubmit is undefined?
Which file --Programs.js (with the .map) or Program.js (which each individual card -- should I import my {deleteProgram} function/action into?
Bonus Round
If memory serves, I need to put this onClick method on the entire card. I would love to call it on a delete button I made within the card. Can someone confirm whether this is correct or not?
Thank you very much for your time! :-)
Change your code to the following:
let handleClick = (id) => {
props.deleteProgram(id)
}
let program = props.program ? props.program : props.programs[props.match.params.id - 1]
let programId = program.id
return(
<Grid.Column>
<Card onClick={(_) => handleClick(programId)}>
<Image src={program ? program.image : null} wrapped ui={false} />
<Card.Content>
<Card.Header>{program ? program.name: null}</Card.Header>
<Card.Meta>
<span className='date'>{program ? program.network : null
</span>
</Card.Meta>
Question 1: One of the ways to bind this is in the constructor of the component.
this.function = this.function.bind(this);
Regarding Question 2: I would send the deleteCardFunction() in as a prop for each individual card. Then call it from the card with the id when you want to delete it.
Since this is a function component, not a class, you shouldn't use this there. Just use the function name.
<Card onClick={() => handleClick(programId)}>
should even work without binding it in there, since you are already doing it with an arrow function in the function declaration:
<Card onClick={handleClick(programId)}>
I would import delete into Program.js, since it makes sense to have the delete button inside that.
Another neat trick, instead of using the ? : shorthand, you can do it even shorter, using ||, like:
let program = props.program || props.programs[props.match.params.id - 1]
To be able to put another onClick inside the card, you will have to stop the event from bubbling up, calling event.stopPropagation() before or inside your delete method.