Date and ToggleButton retrieval on a stepper in react js - javascript

I am working on a small table reservation application for a restaurant.
For this I used e Stepper from Material UI.
On the first step : you have to get the date and the available table and continue the reservation.
The problem is that I could not get the data from the selected table and date time, and I could not move to the second step.
Here is the code of the checkout and the github link of the project.
Anybody have a clearer idea about the solution?
Thanks in advance,
import React, { useState } from 'react';
import {
Stepper,
Step,
StepLabel,
Button,
Typography,
CircularProgress
} from '#material-ui/core';
import { Formik, Form } from 'formik';
import AddressForm from './Forms/AddressForm';
//import PaymentForm from './Forms/PaymentForm';
import ReservationForm from './Forms/ReservationForm';
import Summary from './Forms/Summary';
import CheckoutSuccess from './CheckoutSuccess';
import validationSchema from './FormModel/validationSchema';
import checkoutFormModel from './FormModel/checkoutFormModel';
import formInitialValues from './FormModel/formInitialValues';
import useStyles from './styles';
const steps = ['Book your table', 'Personal information', 'Summary'];
const { formId, formField } = checkoutFormModel;
function _renderStepContent(step) {
switch (step) {
case 0:
return <ReservationForm formField={formField} />;
case 1:
return <AddressForm formField={formField} />;
case 2:
return <Summary />;
default:
return <div>Not Found</div>;
}
}
export default function CheckoutPage() {
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
const currentValidationSchema = validationSchema[activeStep];
const isLastStep = activeStep === steps.length - 1;
function _sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function _submitForm(values, actions) {
await _sleep(1000);
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
setActiveStep(activeStep + 1);
}
function _handleSubmit(values, actions) {
if (isLastStep) {
_submitForm(values, actions);
} else {
setActiveStep(activeStep + 1);
actions.setTouched({});
actions.setSubmitting(false);
}
}
function _handleBack() {
setActiveStep(activeStep - 1);
}
return (
<React.Fragment>
<Typography component="h1" variant="h4" align="center">
Checkout
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map(label => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<CheckoutSuccess />
) : (
<Formik
initialValues={formInitialValues}
validationSchema={currentValidationSchema}
onSubmit={_handleSubmit}
>
{({ isSubmitting }) => (
<Form id={formId}>
{_renderStepContent(activeStep)}
<div className={classes.buttons}>
{activeStep !== 0 && (
<Button onClick={_handleBack} className={classes.button}>
Back
</Button>
)}
<div className={classes.wrapper}>
<Button
disabled={isSubmitting}
type="submit"
variant="contained"
color="primary"
className={classes.button}
>
{isLastStep ? 'Place order' : 'Next'}
</Button>
{isSubmitting && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</div>
</div>
</Form>
)}
</Formik>
)}
</React.Fragment>
</React.Fragment>
);
}

i saw your code and the problem is in the validation schema , you are using the schema of the next step in this step ,
and you should pass name for the input ,
// {...props} => line 43, DatePickerField.jsx file
you made it then you disable it , so just return it back
good luck

Related

Commerce JS, generateToken returning "Material-UI: A component is changing the controlled value state of Select to be uncontrolled."

This is where i generate the token
import React, { useState, useEffect } from 'react';
import { Paper, Stepper, Step, StepLabel, Typography, CircularProgress, Divider, Button } from '#material-ui/core';
import { commerce } from '../../../lib/commerce';
import useStyles from './styles';
import AddressForm from '../AddressForm';
import PaymentForm from '../PaymentForm';
const steps = ['Shipping address', 'Payment details'];
const Checkout = ({ cart }) => {
const [activeStep, setActiveStep] = useState(0);
const [checkoutToken, setCheckoutToken] = useState(null);
const classes = useStyles();
useEffect(() => {
if (cart.id) {
const generateToken = async () => {
try {
const token = await commerce.checkout.generateToken(cart.id, { type: 'cart' });
setCheckoutToken(token)
} catch (error){
console.log(error);
}
};
generateToken();
}
}, [cart]);
const Confirmation = () => (
<div>
Confirmation
</div>
)
const Form = () => activeStep === 0
? <AddressForm checkoutToken={checkoutToken} />
: <PaymentForm />
return (
<>
<div className={classes.toolbar} />
<main className={classes.layout} >
<Paper className={classes.paper}>
<Typography variant='h4' align='center'>Checkout</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((step) => (
<Step key={step}>
<StepLabel>{step}</StepLabel>
</Step>
))}
</Stepper>
{activeStep === steps.length ? <Confirmation /> : checkoutToken && <Form />}
</Paper>
</main>
</>
)
}
export default Checkout
Here is my App.js
import React, { useState, useEffect, Fragment } from 'react'
import { commerce } from './lib/commerce';
import { Products, Navbar, Cart, Checkout } from './components';
import { BrowserRouter as Router, Routes, Route} from 'react-router-dom';
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
}
const fetchCart = async () => {
setCart(await commerce.cart.retrieve())
}
const handleAddToCart = async ( productId, quantity) =>{
const { cart } = await commerce.cart.add(productId, quantity);
setCart(cart);
}
const handleUpdateCartQty = async (productId, quantity) => {
const { cart } = await commerce.cart.update(productId, { quantity });
setCart(cart);
}
const handleRemoveFromCart = async (productId) => {
const { cart } = await commerce.cart.remove(productId);
setCart(cart);
}
const handleEmptyCart = async () => {
const { cart } = await commerce.cart.empty();
setCart(cart);
}
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
return (
<Router>
<div>
<Navbar totalItems={cart.total_items} />
<Routes>
<Route exact path='/' element={<Products products={products} onAddToCart={handleAddToCart} />} />
<Route exact path='/cart' element={<Cart cart={cart} handleUpdateCartQty={handleUpdateCartQty} handleAddToCart={handleAddToCart} handleRemoveFromCart={handleRemoveFromCart} handleEmptyCart={handleEmptyCart} />} />
<Route exact path='/checkout' element={ <Checkout cart={cart} />} />
</Routes>
</div>
</Router>
)
}
export default App;
And here is my cart.jsx incase their is anything relevant there
import React from 'react'
import { Container, Typography, Button, Grid} from '#material-ui/core';
import { Link } from 'react-router-dom';
import useStyles from './styles';
import CartItem from './CartItem/CartItem';
const Cart = ({ cart, handleUpdateCartQty, handleRemoveFromCart, handleEmptyCart }) => {
const classes = useStyles();
const EmptyCart = () => (
<Typography variant='subtitle1'>
You have no items in your shopping cart.
<Link to='/' className={classes.link}>Add Items!</Link>
</Typography>
);
const FilledCart = () => (
<>
<Grid container spacing={3}>
{ cart.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<CartItem item={item} onUpdateCartQty={handleUpdateCartQty} onRemoveFromCart={handleRemoveFromCart} />
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant='h4'>
Subtotal: {cart.subtotal.formatted_with_symbol}
<div>
<Button className={classes.emptyButton} size='large' type='button' variant='contained' color='secondary' onClick={handleEmptyCart}>
Empty Cart
</Button>
<Button component={Link} to='/checkout' className={classes.checkoutButton} size='large' type='button' variant='contained' color='primary'>
Checkout
</Button>
</div>
</Typography>
</div>
</>
);
// Wait for cart to load items
if(!cart.line_items){
return '...loading';
}
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} varaint='h3' gutterBottom >Your Shopping Cart</Typography>
{ !cart.line_items.length ? <EmptyCart /> : <FilledCart />}
</Container>
)
}
export default Cart
[error messages][1]
[1]: https://i.stack.imgur.com/vlard.png
Warning: Expected onSubmit listener to be a function, instead got a
value of string type. form
FormProvider#http://localhost:3000/static/js/bundle.js:76722:7
AddressForm#http://localhost:3000/static/js/bundle.js:1096:7 Form div
Paper#http://localhost:3000/static/js/bundle.js:12332:17
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 main
Checkout#http://localhost:3000/static/js/bundle.js:1332:7
Routes#http://localhost:3000/static/js/bundle.js:67209:7 div
Router#http://localhost:3000/static/js/bundle.js:67146:7
BrowserRouter#http://localhost:3000/static/js/bundle.js:65952:7
App#http://localhost:3000/static/js/bundle.js:347:82
Warning: A component is changing a controlled input to be
uncontrolled. This is likely caused by the value changing from a
defined to undefined, which should not happen. Decide between using a
controlled or uncontrolled input element for the lifetime of the
component. More info: https://reactjs.org/link/controlled-components
input SelectInput#http://localhost:3000/static/js/bundle.js:13482:19
div InputBase#http://localhost:3000/static/js/bundle.js:8257:25
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25
Input#http://localhost:3000/static/js/bundle.js:9146:26
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25
Select#http://localhost:3000/static/js/bundle.js:13182:26
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 div
Grid#http://localhost:3000/static/js/bundle.js:7352:29
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 div
Grid#http://localhost:3000/static/js/bundle.js:7352:29
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 form
FormProvider#http://localhost:3000/static/js/bundle.js:76722:7
AddressForm#http://localhost:3000/static/js/bundle.js:1096:7 Form div
Paper#http://localhost:3000/static/js/bundle.js:12332:17
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 main
Checkout#http://localhost:3000/static/js/bundle.js:1332:7
Routes#http://localhost:3000/static/js/bundle.js:67209:7

push values in to new array only when material ui checkbox is checked

I am new to react and I am making a simple todo app using react js and material ui. I have separate components to take in user input (TodoInput.js) which sends props to a component that renders individual todo tasks and displays a checkbox (TodoCards.js). What I want to do is display the total number of completed tasks onto the page which is updated when the user completes a todo by checking a checkbox. To achieve this, I have an array that stores all the user's completed tasks. At the moment whenever a checkbox is checked, all tasks are added to this array. I ran into a problem where I am unsure of how to only push values into this new array when the checkbox of that specific task is checked. Any guidance or explanations towards the right direction is greatly appreciated.
TodoInput.js
import React, { useState } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import { TextField, Button } from '#material-ui/core';
import { TodoCards } from '../UI/TodoCards';
import { Progress } from '../UI/Progress';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
width: '25ch',
textAlign: 'center'
},
},
}));
export default function TodoInput() {
const classes = useStyles();
const [userInput, setUserInput] = useState({
id: '',
task: ''
});
const [todos, setTodos] = useState([])
//state for error
const [error, setError] = useState({
errorMessage: '',
error: false
})
//add the user todo with the button
const submitUserInput = (e) => {
e.preventDefault();
//add the user input to array
//task is undefined
if (userInput.task === "") {
//render visual warning for text input
setError({ errorMessage: 'Cannot be blank', error: true })
console.log('null')
} else {
setTodos([...todos, userInput])
console.log(todos)
setError({ errorMessage: '', error: false })
}
console.log(loadedTodos)
}
//set the todo card to the user input
const handleUserInput = function (e) {
//make a new todo object
setUserInput({
...userInput,
id: Math.random() * 100,
task: e.target.value
})
//setUserInput(e.target.value)
//console.log(userInput)
}
const loadedTodos = [];
for (const key in todos) {
loadedTodos.push({
id: Math.random() * 100,
taskName: todos[key].task
})
}
return (
<div>
<Progress taskCount={loadedTodos.length} />
<form className={classes.root} noValidate autoComplete="off" onSubmit={submitUserInput}>
{error.error ? <TextField id="outlined-error-helper-text" label="Today's task" variant="outlined" type="text" onChange={handleUserInput} error={error.error} helperText={error.errorMessage} />
: <TextField id="outlined-basic" label="Today's task" variant="outlined" type="text" onChange={handleUserInput} />}
<Button variant="contained" color="primary" type="submit">Submit</Button>
{userInput && <TodoCards taskValue={todos} />}
</form>
</div>
);
}
TodoCards.js
import React, { useState } from 'react'
import { Card, CardContent, Typography, FormControlLabel, Checkbox } from '#material-ui/core';
import { CompletedTasks } from './CompletedTasks';
export const TodoCards = ({ taskValue }) => {
const [checked, setChecked] = useState(false);
//if checked, add the task value to the completed task array
const completedTasks = [];
const handleChecked = (e) => {
setChecked(e.target.checked)
for (const key in taskValue) {
completedTasks.push(taskValue[key])
}
console.log(completedTasks.length)
}
return (
< div >
<CompletedTasks completed={completedTasks.length} />
<Card>
{taskValue.map((individual, i) => {
return (
<CardContent key={i}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked[i]}
onClick={handleChecked}
/>
}
label={individual.task} />
</Typography>
</CardContent>
)
})}
</Card>
</div >
)
}
CompletedTasks.js (displays the total number of completed tasks)
import React from 'react'
import InsertEmoticonOutlinedIcon from '#material-ui/icons/InsertEmoticonOutlined';
import { Typography } from '#material-ui/core';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
paper: {
padding: theme.spacing(2),
marginTop: '20px',
textAlign: 'center',
color: theme.palette.text.secondary,
},
}));
export const CompletedTasks = ({ completed }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<InsertEmoticonOutlinedIcon fontSize="large" />
<Typography variant="h6">
Completed tasks:{completed}
</Typography>
</div>
)
}
One issue I see here is that you start with a boolean type checked state in TodoCards, and only ever store a single boolean value of the last checkbox interacted with. There's no way to get a count or to track what's previously been checked.
Use an object to hold the completed "done" checked values, then count the number of values that are checked (i.e. true) after each state update and rerender. Use the task's id as the key in the checked state.
export const TodoCards = ({ taskValue = [] }) => {
const [checked, setChecked] = useState({});
const handleChecked = id => e => {
const { checked } = e.target;
setChecked((values) => ({
...values,
[id]: checked
}));
};
return (
<div>
<CompletedTasks
completed={Object.values(checked).filter(Boolean).length}
/>
<Card>
{taskValue.map(({ id, task }) => {
return (
<CardContent key={id}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked[id]}
onClick={handleChecked(id)}
/>
}
label={task}
/>
</Typography>
</CardContent>
)
})}
</Card>
</div >
)
}
To do this, you first need to only push the checked key to your array:
First send your key to the eventHandler:
{taskValue.map((individual, i) => {
return (
<CardContent key={i}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked[i]}
onClick={() => handleChecked(individual)}
/>
}
label={individual.task} />
</Typography>
</CardContent>
)
})}
Then in your Handler, push it into the array:
const handleChecked = (key) => {
//setChecked(e.target.checked)
completedTasks.push(key)
console.log(completedTasks.length)
}
BUT
Because you are not modifying any state so the changes won't be updated to the UI, you need to use a state to store your completedTasks.
const [completedTasks, setCompletedTasks] = useState([]);
const handleChecked = (key) => {
setCompletedTasks([...completedTasks, key])
}
Please note that this is only a guide so you can get to the right way, not a complete working example
In the hopes that someone else may find this useful, I was able to come up with a solution thanks to the suggestions given. Below is the updated code for the TodoCards.js component:
import React, { useState } from 'react'
import { Card, CardContent, Typography, FormControlLabel, Checkbox } from '#material-ui/core';
import { CompletedTasks } from './CompletedTasks';
export const TodoCards = ({ taskValue }) => {
const [checked, setChecked] = useState(false);
//if checked, add the task value to the completed task array
const [completedTasks, setCompletedTasks] = useState([]);
const handleChecked = key => {
setCompletedTasks([...completedTasks, key])
completedTasks.push(key)
console.log(completedTasks.length)
setChecked(true)
};
if (taskValue.length === completedTasks.length) {
console.log('all tasks complete')
}
return (
<div>
<CompletedTasks completed={completedTasks.length} />
<Card>
{taskValue.map((individual, i) => {
return (
<CardContent key={i}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked[i]}
onClick={() => handleChecked(individual)}
/>
}
label={individual.task}
/>
</Typography>
</CardContent>
)
})}
</Card>
</div >
)
}
Only the checked todo items are pushed into the new array (completedTasks) and this is updated using useState.

State is empty even have setted up with data

I am trying to fetch the data from the database and storing it in a state but when I console log my state, no data has been stored in the state but when I console the data itself, it returns what I expected to have. This is my code;
import React, { useContext, useEffect, useState } from 'react';
import { Field, Form, Formik, useField } from 'formik';
import * as Yup from 'yup';
import {
Button,
Box,
makeStyles,
Grid,
Typography,
Divider,
FormHelperText,
Select,
MenuItem,
InputLabel,
FormControl,
} from '#material-ui/core';
import {
MuiPickersUtilsProvider,
KeyboardDateTimePicker,
} from '#material-ui/pickers';
import DateFnsUtils from '#date-io/date-fns';
import 'date-fns';
// Yup validation Schema
const validationSchema = Yup.object().shape({
date: Yup.date().required('Please enter valid date').nullable(),
teamOne: Yup.string().required('Please select team'),
teamTwo: Yup.string().required('Please select team'),
});
// This is a custom date field using Formik
const DateTimeField = ({ field, form, ...props }) => {
const currentError = form.errors[field.name];
return (
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDateTimePicker
clearable
disablePast
name={field.name}
value={field.value}
format="MM/dd/yyyy"
helperText={currentError}
error={!!currentError}
onError={(error) => {
if (error !== currentError) {
form.setFieldError(field.name, error);
}
}}
onChange={(date) => form.setFieldValue(field.name, date, true)}
{...props}
/>
</MuiPickersUtilsProvider>
);
};
// Custom <Select> field
const TeamSelection = ({ name, ...props }) => {
const [field, meta] = useField(name);
const errorText = meta.error && meta.touched ? meta.error : '';
return (
<>
<Select
name={field.name}
{...field}
fullWidth
error={!!errorText}
{...props}
>
{props.children}
</Select>
<FormHelperText error>{errorText}</FormHelperText>
</>
);
};
export default function AddMatch() {
const classes = useStyles();
const fetchContext = useContext(FetchContext);
const [team, setTeam] = useState([]);
useEffect(() => {
const getTeam = async () => {
try {
const { data } = await fetchContext.authAxios.get('get-all-teams');
setTeam(data);
} catch (error) {
console.log(error);
}
};
getTeam();
}, [fetchContext]);
console.log(team);
return (
<Formik
initialValues={{ date: new Date(), teamOne: '', teamTwo: '' }}
validationSchema={validationSchema}
onSubmit={(values) => {
if (values.teamOne === values.teamTwo) {
return console.log('Teams must not be the same!');
}
return console.log(values);
}}
>
{() => (
<Form noValidate>
<Grid>
<Typography>
Add Match
</Typography>
</Grid>
<Divider />
<Box>
<Grid>
<Grid>
<Field
name="date"
label="Set date and time"
component={DateTimeField}
/>
</Grid>
<Grid>
<Grid>
<FormControl>
<InputLabel>
Team A
</InputLabel>
<TeamSelection name="teamOne">
<MenuItem value=""> </MenuItem>
{team.map((item) => (
<MenuItem value={item.teamName} key={item._id}>
{item.teamName}
</MenuItem>
))}
</TeamSelection>
</FormControl>
</Grid>
<Grid>
<FormControl>
<InputLabel>
Team B
</InputLabel>
<TeamSelection name="teamTwo">
<MenuItem value=""> </MenuItem>
{team.map((item) => (
<MenuItem value={item.teamName} key={item._id}>
{item.teamName}
</MenuItem>
))}
</TeamSelection>
</FormControl>
</Grid>
</Grid>
<Box>
<Button type="submit">
Submit
</Button>
</Box>
</Grid>
</Box>
</Form>
)}
</Formik>
)}
I'm using Material UI and Formik.
It only logged an empty array "[]" or it makes an error, (.map is not a function). I tried to set the state as an Object but there is still no data being stored with the state.
Check the post request in your res.status(200).json() or res.send() or res.json(). Don't use brackets inside .json() or .send(), in your res or response, for example:
.json({}) // Don't use brackets if you don't want to send your data as an Object
If ever you want it to be object, then call the array which is called data like
useEffect(() => {
const getTeam = async () => {
try {
const { teamsData } = await fetchContext.authAxios.get('get-all-teams');
setTeam(teamsData.data);
} catch (error) {
console.log(error);
}
};
getTeam();
}, [fetchContext]);
destructure the teamsData if you have passed it as an Object on purpose.

Why doesn't this React component re-render when its prop changes?

I've created a cut-down version of a re-render issue which I'm having with an application I am working on.
In reality DisplayElement1 and DisplayElement2 are two complex components.
DisplayElement2 here is iterating through a simple array of numbers (supplied via its prop numbers) and displaying them.
Problem : When the array behind the numbers prop gets updated in the main component App (in this case by clicking on the Add Number to Array button I would expect DisplayElement2 to re-render with the updated array but it doesn't, why not ??
If I click Show Display 1 and then click back on Show Display 2 the updated array renders.
App.js
import React, { useState, useMemo } from "react";
import "./styles.css";
import DisplayComponent1 from "./DisplayComponent1";
import DisplayComponent2 from "./DisplayComponent2";
import { Button } from "#material-ui/core";
export default function App() {
const [numbersToDisplay, setNumbersToDisplay] = useState([1, 2, 3]);
////////////////////////////////////////////////
const component1 = useMemo(() => {
return <DisplayComponent1 />;
}, []);
const component2 = useMemo(() => {
return (
<DisplayComponent2
style={{ background: "red" }}
numbers={numbersToDisplay}
/>
);
}, [numbersToDisplay]);
////////////////////////////////////////////////
const [currentDisplayComponent, setCurrentDisplayComponent] = useState(
component2
);
return (
<div className="App">
<Button
variant="contained"
color="secondary"
onClick={() => setCurrentDisplayComponent(component1)}
>
Show Display 1
</Button>
<Button
variant="contained"
color="primary"
onClick={() => setCurrentDisplayComponent(component2)}
>
Show Display 2
</Button>
<Button
variant="contained"
style={{ marginLeft: 50 }}
onClick={() => {
let tempArray = Array.from(numbersToDisplay);
tempArray.push(4);
setNumbersToDisplay(tempArray);
}}
>
Add number to array
</Button>
{currentDisplayComponent}
</div>
);
}
DisplayElement1.js and DisplayElement2.js
import React from "react";
import {Paper} from "#material-ui/core";
export default function DisplayComponent1(props) {
return (
<Paper>
<p>This is DisplayComponent1</p>
</Paper>
);
}
import React from "react";
import { Paper } from "#material-ui/core";
export default function DisplayComponent2(props) {
return (
<Paper>
<p>This is DisplayComponent2</p>
{props.numbers.map((currNumber, currIndex) => {
return <div key={currIndex}>{currNumber}</div>;
})}
</Paper>
);
}
The reason your component doens't re-render with updated props is because you have a previous instance of your component stored in the currentDisplayComponent state which is what you use to render
A hacky workaround with your current code would be to make use of useEffect and update the component instance that is active
However the best solution in this scenarios is to take out the component instances outside of the state and render these based on a selected component string state.
To prevent unnecessary updates you can make use of React.memo
export default React.memo(function DisplayComponent2(props) {
return (
<Paper>
<p>This is DisplayComponent2</p>
{props.numbers.map((currNumber, index) => {
return <div key={index}>{currNumber}</div>;
})}
</Paper>
);
});
App.js
export default function App() {
const [numbersToDisplay, setNumbersToDisplay] = useState([1, 2, 3]);
const [currentDisplayComponent, setCurrentDisplayComponent] = useState(
"component1"
);
const getCurrentComponent = currentDisplayComponent => {
switch (currentDisplayComponent) {
case "component1":
return <DisplayComponent1 />;
case "component2":
return (
<DisplayComponent2
style={{ background: "red" }}
numbers={numbersToDisplay}
/>
);
default:
return null;
}
};
return (
<div className="App">
<Button
variant="contained"
color="secondary"
onClick={() => setCurrentDisplayComponent("component1")}
>
Show Display 1
</Button>
<Button
variant="contained"
color="primary"
onClick={() => setCurrentDisplayComponent("component2")}
>
Show Display 2
</Button>
<Button
variant="contained"
style={{ marginLeft: 50 }}
onClick={() => {
let tempArray = Array.from(numbersToDisplay);
tempArray.push(4);
setNumbersToDisplay(tempArray);
}}
>
Add number to array
</Button>
{getCurrentComponent(currentDisplayComponent)}
</div>
);
}
Working demo
Consider something like this for your App.js, where the display states are enumerated and we've removed the useMemo
import React, { useState } from "react";
import "./styles.css";
import DisplayComponent1 from "./DisplayComponent1";
import DisplayComponent2 from "./DisplayComponent2";
import { Button } from "#material-ui/core";
const DisplayStatEnum = {COMPONENT1: 0, COMPONENT2: 1};
export default function App() {
const [numbersToDisplay, setNumbersToDisplay] = useState([1, 2, 3]);
////////////////////////////////////////////////
const component1 = <DisplayComponent1 />;
const component2 = <DisplayComponent2
style={{ background: "red" }}
numbers={numbersToDisplay}
/>;
////////////////////////////////////////////////
const [currentDisplayComponent, setCurrentDisplayComponent]
= useState(DisplayStatEnum.COMPONENT2);
const componentSelected =
currentDisplayComponent === DisplayStatEnum.COMPONENT1
? component1
: component2;
return (
<div className="App">
<div>
<Button
variant="contained"
color="secondary"
onClick={() => setCurrentDisplayComponent(DisplayStatEnum.COMPONENT1)}
>
Show Display 1
</Button>
<Button
variant="contained"
color="primary"
onClick={() => setCurrentDisplayComponent(DisplayStatEnum.COMPONENT2)}
>
Show Display 2
</Button>
</div>
<Button
variant="contained"
style={{ marginLeft: 50 }}
onClick={() => {
let tempArray = Array.from(numbersToDisplay);
tempArray.push(4);
setNumbersToDisplay(tempArray);
}}
>
Add number to array
</Button>
{componentSelected}
</div>
);
}

How do I delete a card component using id from a data object?

I have built a Trello clone using ReactJS, where I have 4 columns called TODO, DOING, DONE and REJECTED, where I can add a card to any column.
In a file I am trying to map over card component and rendering properties from defined dummy data.
What I want to do?
I want to delete a specific card when it is clicked using the card id in some way.
What is the problem?
I do not understand how do I delete an entire card object when it matches a certain id.
My TaskboardList.js component :
import React from "react";
import TaskboardCard from "./TaskboardCard";
import TaskboardActionButton from "./TaskboardActionButton";
import { Droppable } from "react-beautiful-dnd";
const TaskboardList = ({ title, cards, listID }) => {
return (
<Droppable droppableId={String(listID)}>
{provided => (
<div
className="taskboardlist_container"
{...provided.droppableProps}
ref={provided.innerRef}
style={styles.container}
>
<div className="sub-heading">{title}</div>
{cards.map((card, index) => (
<TaskboardCard
key={card.id}
index={index}
text={card.text}
id={card.id}
/>
))}
<TaskboardActionButton listID={listID} />
{provided.placeholder}
</div>
)}
</Droppable>
);
};
const styles = {
container: {
backgroundColor: "#eee",
width: 300,
padding: "0.5rem",
marginRight: "1rem",
height: "100%"
}
};
export default TaskboardList;
My TaskboardCard.js component
import React from "react";
import Card from "#material-ui/core/Card";
import Typography from "#material-ui/core/Typography";
import CardContent from "#material-ui/core/CardContent";
import { Draggable } from "react-beautiful-dnd";
const TaskboardCard = ({ text, id, index, sample }) => {
return (
<Draggable draggableId={String(id)} index={index}>
{provided => (
<div
className="taskboard_container"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Card>
<CardContent>
<Typography style={{ fontSize: "1.5rem" }} gutterBottom>
{text}
</Typography>
</CardContent>
</Card>
</div>
)}
</Draggable>
);
};
export default TaskboardCard;
For further reference to more files, I am attaching my GitHub link. Please consider visiting.
Any help would be much appreciated.
https://github.com/abhinav-anshul/consensolabs
And here is the link for a live demo https://consensolab.abhinavanshul.com/
// src/actions/index.js
export const CONSTANTS = {
ADD_CARD: "ADD_CARD",
ADD_LIST: "ADD_LIST",
DRAG_HAPPENED: "DRAG_HAPPENED",
DELETE_CARD: "DELETE_CARD"
};
// src/actions/cardActions.js
export const deleteCard = cardId => ({
type: CONSTANTS.DELETE_CARD,
payload: { cardId }
});
// src/reducers/listReducers.js
const listReducer = (state = initialState, action) => {
// ...
case CONSTANTS.DELETE_CARD: {
return {
...state,
cards: state.cards.filter(({id}) => id !== action.payload.cardId)
}
}
Then from your delete button, you just need to call dispatch(deleteCard(cardId))
Edit: I've updated your code sandbox to implement this https://codesandbox.io/s/stupefied-golick-7z78k

Categories

Resources