I created a shopping list. Each item in the list has 2 options: "Remove" (will remove the specific item from the shopping list), and "Edit" (let the user changing the size or/and the quantity of the specific item). My problem is when I'm trying to edit the items by changing the size, quantity, and removing some of them, everything works but after several items that I'm changing/removing, the quantity and the sizing lists aren't updating in the right way. I have been trying so many different approaches but couldn't find out what is the root of the problem.
import React, { Component } from 'react';
import Header from './Components/header';
import Item from './Components/item';
import ItemProperties from './Components/itemProperties';
import { shirtsList } from './shirtsList';
import CustomerHelp from './Components/customerHelp';
import Checkout from './Components/checkout';
import EditPage from './Components/editPage';
class App extends Component {
constructor(props) {
super(props);
this.state = {
quantity: {},
totalItems: 0,
sizing: {},
editItem: [],
cart: shirtsList
}
}
// Item component
handleRemoveItem(shirt) {
const newCart = this.state.cart.filter((item) => {
return shirt.id !== item.id
})
this.setState({cart: newCart})
//--------------------------------------------------------//
const newQuantity = this.state.quantity.filter((item) => {
return shirt.id !== item.id
})
this.setState({quantity: newQuantity})
//--------------------------------------------------------//
const newSizing = this.state.sizing.filter((item) => {
return shirt.id !== item.id
})
this.setState({sizing: newSizing})
//--------------------------------------------------------//
let totall = 0;
for(let i = 0; i < newQuantity.length; i++){
totall = totall + newQuantity[i].amount;
}
this.setState({totalItems: totall})
}
openItemEvent(shirt) {
const itemToEdit = this.state.cart.filter((item) => {
return shirt.id === item.id
})
this.setState({editItem: itemToEdit})
document.querySelector('.mainEditPage').style.display = 'flex';
}
// Checkout component
subTotalPrice = () => {
let total = 0;
let quantity = this.state.quantity;
let cart = this.state.cart;
for(let i = 0; i < quantity.length; i++){
if(cart[i].price === cart[i].oldPrice && cart.length > 0){
total = total + quantity[i].amount * cart[i].price;
} else{
total = total + quantity[i].amount * cart[i].price;
}
}
return total.toFixed(2);
}
// EditPage component
changingSize = (event, shirt) =>{
let currentSize = event.target.value;
this.newSizes = this.state.sizing.map((i, num) => {
if(i.id === shirt.id){
return {size: currentSize,id: num+1}
}else {
return {size: i.size ,id: num+1}
}
})
}
changingQuantity = (event, shirt) =>{
let currentQuantity = event.target.value;
this.newQuantity = this.state.quantity.map((i, num) => {
if(i.id === shirt.id){
return {amount: Number(currentQuantity), id: num+1}
}else {
return {amount: Number(i.amount) ,id: num+1}
}
})
}
editButton = () =>{
let total = 0;
// Setting up "sizing" state after pressing the edit button
if(this.newSizes === undefined) {
this.setState({sizing: this.state.cart.map((shirt, i) => {
return {size: shirt.size,id: i+1}
})})
}else {
this.setState({sizing: this.newSizes})
}
// Setting up "quantity" and "totalItems" state after pressing the edit button
if(this.newQuantity === undefined) {
this.setState({quantity: this.state.cart.map((shirt, i) => {
return {amount: shirt.quantity,id: i+1}
})})
for(let i = 0; i < this.state.quantity.length; i++){
total = total + this.state.quantity[i].amount;
}
this.setState({totalItems: total})
}else {
this.setState({quantity: this.newQuantity})
for(let i = 0; i < this.newQuantity.length; i++){
total = total + this.newQuantity[i].amount;
}
this.setState({totalItems: total})
}
document.querySelector('.mainEditPage').style.display = 'none';
}
exitButton() {
document.querySelector('.mainEditPage').style.display = 'none';
}
// React lifeCycle methods
componentWillMount(){
this.getQuantity();
this.getSize();
}
componentDidMount(){
this.getTotalItems();
}
//--------------------------------------------------------//
getSize = () =>{
const array = shirtsList.map((shirt, i) => {
return {size: shirt.size,id: i+1}
})
this.setState({sizing: array})
}
getQuantity = () =>{
const array = shirtsList.map((shirt, i) => {
return {amount: shirt.quantity,id: i+1}
})
this.setState({quantity: array})
}
getTotalItems = () =>{
let total = 0;
for(let i = 0; i < this.state.quantity.length; i++){
total = total + this.state.quantity[i].amount;
}
this.setState({totalItems: total})
}
render() {
return (
<div>
<Header />
<ItemProperties amount={this.state.totalItems}/>
<div className="itemInTheCart">
{
this.state.cart.map((shirt, i) => {
return (
<Item
name={shirt.name}
price={shirt.price}
oldPrice={shirt.oldPrice}
color={shirt.color}
style1={shirt.style}
image={shirt.image}
quantity={this.state.quantity[i].amount}
size={this.state.sizing[i].size}
key={shirt.id}
handleRemoveItem={() => {this.handleRemoveItem(shirt)}}
openItemEvent={() => {this.openItemEvent(shirt)}}
/>
)
})
}
</div>
<div className="checkout__section">
<CustomerHelp />
<Checkout
change={this.subTotalPrice()}
/>
</div>
<div className="mainEditPage">
{
this.state.editItem.map((shirt) => {
return (
<EditPage
image={shirt.image}
name={shirt.name}
price={shirt.price}
style={shirt.style}
color={shirt.color}
key={shirt.id}
size={shirt.size}
exitButton={() => {this.exitButton()}}
changingSize={(event) =>{this.changingSize(event, shirt)}}
changingQuantity={(event) =>{this.changingQuantity(event, shirt)}}
editButton={() => {this.editButton()}}
/>
)
})
}
</div>
</div>
);
}
}
export default App;
Related
hello i am not sure why i getting this error massage can some one correct my code i can not find key in my new post components
this is my newpost.js code
import React, { useContext } from 'react';
import { useHttpClient } from '../../hooks/useHttpClient';
import useForm from '../../hooks/useForm';
import { AuthContext } from '../../context/auth';
import { useHistory } from 'react-router-dom/cjs/react-router-dom.min';
import { newPostForm } from '../../utils/formConfig';
import { appendData, renderRepeatedSkeletons } from '../../utils';
import ErrorModal from '../../components/Modal/ErrorModal';
import SkeletonElement from '../../components/Skeleton/SkeletonElement';
const NewPost = () => {
const auth = useContext(AuthContext);
const history = useHistory();
const { currentUser } = auth;
const { isLoading, sendReq, error, clearError } = useHttpClient();
const { renderFormInputs, renderFormValues, isFormValid } =
useForm(newPostForm);
const formValues = renderFormValues();
const formInputs = renderFormInputs();
const postSubmitHandle = async (evt) => {
evt.preventDefault(); //otherwise, there will be a reload
const formData = appendData(formValues);
formData.append('author', currentUser.userId);
try {
await sendReq(
`${process.env.REACT_APP_BASE_URL}/posts`,
'POST',
formData,
{
Authorization: `Bearer ${currentUser.token}`,
}
);
history.push('/');
} catch (err) {}
};
return (
<>
<ErrorModal error={error} onClose={clearError} />
{isLoading ? (
renderRepeatedSkeletons(<SkeletonElement type='text' />, 20)
) : (
<div className='container-create-page'>
<form className='form form__create'>
<h2>Create a new post</h2>
{formInputs}
<button
onClick={postSubmitHandle}
className='btn'
disabled={!isFormValid()}
>
Submit <span>→</span>
</button>
</form>
</div>
)}
</>
);
};
export default NewPost;
and this is my useform.js code
import { useState, useCallback } from 'react';
//"signupForm" => "formObj" (name, email, password) => "form"
const useForm = (formObj) => {
const [form, setForm] = useState(formObj);
const renderFormInputs = () => {
//renders an [] of <Input> for all input fields
return Object.values(form).map((inputObj) => {
const { value, label, errorMessage, valid, renderInput } = inputObj;
return renderInput(
onInputChange,
value,
valid,
errorMessage,
label,
onCustomInputChange
);
});
};
const renderFormValues = () => {
let values = {};
Object.keys(form).forEach((inputObj) => {
values[inputObj] = form[inputObj].value;
});
return values;
};
const isInputFieldValid = useCallback(
(inputField) => {
for (const rule of inputField.validationRules) {
if (!rule.validate(inputField.value, form)) {
inputField.errorMessage = rule.message;
return false;
}
}
return true;
},
[form]
);
const onInputChange = useCallback(
(event) => {
const { name, value } = event.target;
let inputObj = { ...form[name], value };
const isValidInput = isInputFieldValid(inputObj);
if (isValidInput && !inputObj.valid) {
inputObj = { ...inputObj, valid: true };
} else if (!inputObj.touched && !isValidInput && inputObj.valid) {
inputObj = { ...inputObj, valid: false };
}
inputObj = { ...inputObj, touched: true };
setForm({ ...form, [name]: inputObj });
},
[form, isInputFieldValid]
);
const onCustomInputChange = useCallback(
(type, value, InputIsValid) => {
setForm({
...form,
[type]: { ...form[type], value, valid: InputIsValid },
});
},
[form]
);
const isFormValid = useCallback(
(customForm) => {
let isValid = true;
const arr = Object.values(customForm || form);
for (let i = 0; i < arr.length; i++) {
if (!arr[i].valid) {
isValid = false;
break;
}
}
return isValid;
},
[form]
);
return {
renderFormInputs,
renderFormValues,
isFormValid,
setForm,
};
};
export default useForm;
submit button does not work . it just a simple form with submit button and 4 input value and one image . if some one need more information . please ask in the comment section
index.js contain renderRepeatedSkeletons
export const checkInArray = (arr, elem) => {
return arr && arr.indexOf(elem) !== -1;
};
export const canModifyComment = (currentUserId, authorId) =>
currentUserId === authorId;
export const canReply = (currentUserId) => !!currentUserId;
export const isReplying = (activeComment, commentId) =>
activeComment &&
activeComment.type === 'replying' &&
activeComment.id === commentId;
export const isEditing = (activeComment, commentId) =>
activeComment &&
activeComment.type === 'editing' &&
activeComment.id === commentId;
export const readingTime = (body) => {
const wpm = 225;
const words = body.trim().split(/\s+/).length;
return `${Math.ceil(words / wpm)} min read`;
};
export const appendData = (data) => {
const formData = new FormData();
for (let [key, value] of Object.entries(data)) {
if (Array.isArray(value)) {
value = JSON.stringify(value);
}
formData.append(`${key}`, value);
}
return formData;
};
export const getReplies = (comments, commentId) => {
return (
comments &&
comments
.filter((comment) => comment && comment.parentId === commentId)
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
);
};
export const formatDate = (date) => {
const options = { year: 'numeric', month: 'short', day: 'numeric' };
const today = new Date(date);
return today.toLocaleDateString('en-US', options);
};
export const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
export const renderRepeatedSkeletons = (element, count) => {
let skeletons = [];
for (let i = 0; i < count; i++) {
skeletons.push(element);
}
return skeletons;
};
export const renderAlternateSkeletons = (elementOne, elementTwo, count) => {
let skeletons = [];
for (let i = 0; i < count; i++) {
if (i % 2 === 0) {
skeletons.push(elementOne);
} else {
skeletons.push(elementTwo);
}
}
return skeletons;
};
Your renderRepeatedSkeletons function have to add a key to elements
export const renderRepeatedSkeletons = (element, count) => {
let skeletons = [];
for (let i = 0; i < count; i++) {
skeletons.push(<React.Fragment key={i} />{element}</React.Fragment>);
}
return skeletons;
};
I am doing a React JS Cart and I am having problems when I try to delete an Item from the there. It has already a function that adds the items and also another for the total quantity and the total price.
This is the ContextProvider:
import { useState } from "react";
import { CartContext } from "./CartContext";
export const CartProvider = ({ children }) => {
const [list, setList] = useState([]);
const addCart = (varietalCount) => {
if (list.find((item) => item.id === varietalCount.id)) {
const newVarietal = list.map((varietal) => {
if (varietal.id === varietalCount.id) {
return { ...varietal, count: varietalCount.count + varietal.count };
}
return varietal;
});
setList(newVarietal);
} else {
setList((state) => {
return [...state, varietalCount];
});
}
};
console.log("list", list);
// const deleteProd = (varietalCount) => {
// if (list.find((item) => item.id === varietalCount.id)) {
// const deleteVarietal = list.map((varietal) => {
// if (varietal.id === varietalCount.id) {
// return { ...varietal, count: null };
// }
// return varietal;
// });
// setList(deleteVarietal);
// } else {
// setList((state) => {
// return [...state, varietalCount];
// });
// }
// };
const totalPrice = () => {
return list.reduce((prev, next) => (prev + (next.count * next.price)), 0)
};
const totalQuantity = () => {
return list.reduce((prev, next) => (prev + (next.count)), 0)
};
return(
<>
<CartContext.Provider value={{ list, addCart, totalPrice, totalQuantity }}>
{children}
</CartContext.Provider>
</>);
};
If it is necessary I can add to the post the Cart.js or the ItemDetail.js. I hope someone can help me. Cheers
I think you can just use filter given that your state has value of an array. Something like:
const deleteProd = (varietalCount) => {
const newItems = list.filter((item) => item.id !== varietalCount.id)
setList(newItems);
};
You can check more array functions from here https://www.w3schools.com/jsref/jsref_obj_array.asp
I am working on solution
I have created basic tree kind of table whenever user click on expand data related to clicked row will appear under it based on row data
I have achieved basic functionality of expand/collapse upto N nested levels.
But i am stuck with only one problem, so basically all row have conditional expand button based on array having multiple values
Lets say it is split array having 3 entries county,city,state
Default loaded data will be fetched from api, now i have to check array that is there any split available! if yes than i make expand button visible
Consider this scenario
const split = ["country","city","state"]
this is Ui will look like
+ Data_1
+ Data_2
on click of button + new data table row will be rendered based on next split available in our case it is country so visual representation will be like
- Data_1
Country_1
Country_2
+ Data_2
Here country does not have expand button as user have not added next split yet, lets add city, and assume user have clicked Country_1 so data will be like
- Data_1
- Country_1
City_1
City_2
+ Country_2
+ Data_2
My solution works fine till this level now lets say user have removed country from split that all nodes of country and city should be removed and - icon of data_1 should be changed to +
Here is my code
import React, {useState, useEffect, useRef, Fragment} from "react";
import _ from "lodash";
import axios from "axios";
class TableRowData extends React.Component {
state = {
showIcon: false,
selection: [],
data: [],
splitOption: ["campid"]
};
constructor(props) {
super(props);
}
componentDidMount() {
const checkIfSplitExistOnMount = (currentSplit) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === currentSplit;
});
if (this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
} else {
return null;
}
}
const getReportData = () => {
axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then((res) => {
const rowData = res.data.map((row) => {
row.name = this.state.splitOption[0];
row.isExpanded = false;
row.currentSplit = this.state.splitOption[0];
row.nextSplit = checkIfSplitExistOnMount(this.state.splitOption[0])
row.parentId = 0;
row.isVisble = true;
//console.log(row)
return row;
});
this.setState({
data: rowData
}, () => { //console.log(this.state.data)
});
});
}
getReportData()
}
render() {
// update state function
const updateState = () => {
this.setState({
data: [...this.state.data],
splitOption: [...this.state.splitOption],
selection: [...this.state.selection],
}, () => {})
}
// recusively update parent and child
const recursion = (obj) => {
let row = obj;
row.isExpanded = row.isExpanded;
row.currentSplit = row.currentSplit;
row.nextSplit = checkIfSplitExist(row.currentSplit)
if (row.children && row.children.length > 0) { // check if has children
row.children.forEach(v => { // if has children do the same recursion for every children
recursion(v);
});
}
return row; // return final new object
}
const recursionDel = (obj,split) => {
var row = obj;
row.currentSplit = row.currentSplit;
row.nextSplit = checkIfSplitExist(row.currentSplit)
if (row.children && row.children.length > 0) { // check if has children
row.children.forEach(v => { // if has children do the same recursion for every children
recursionDel(v);
});
}
return row; // return final new object
}
// function to check if next split is there or not if there than return nextsplit
const checkIfSplitExist = (currentSplit) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === currentSplit;
});
if(i !== -1) {
if (this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
} else {
return null;
}
}
}
// recursive update whenever split added
const recursiveUpdate = (data) => {
const prevData = [...data];
return prevData.map((row) => {
const updatedData = recursion(row);
return row;
});
}
// function to delete child and parent node recursively
const recursiveDelete = (data,split) => {
const prevData = [...data];
return prevData.map((row) => {
const data = recursionDel(row,split);
return row;
});
}
const addNewSplit = (split) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === split;
});
if(i === -1) {
this.setState(
{
splitOption:[...this.state.splitOption,split]
},
()=>{
var rowData = recursiveUpdate(this.state.data)
this.setState({data:rowData})
}
);
} else {
const prevData = [...this.state.splitOption];
var index = prevData.indexOf(split);
prevData.splice(index,1)
if(index!==-1) {
this.setState(
{
splitOption:prevData
},
()=> {
var rowData = recursiveDelete(this.state.data,split)
this.setState({data:rowData})
}
)
}
}
}
// add lazyload expand data
const ExpandableTableRow = ({rows}) => {
const expandRow = (row) => {
row.children = [
{
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: checkIfSplitExist(row.nextSplit),
isVisble:true
}, {
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: checkIfSplitExist(row.nextSplit),
isVisble:true
}
];
row.isExpanded = true;
updateState();
};
// call whenever - click
const collapseRow = (row) => {
delete row.children;
row.isExpanded = false;
updateState();
};
// toggle
const ExpandCollapsToggle = ({row, expandRow, collapseRow}) => {
// display +/- only if nextsplit is not undefined or null
if (row.nextSplit) {
if (row.isExpanded === true) {
return (<button type="button" onClick={() => collapseRow(row)}>
-
</button>);
} else {
return (<button type="button" onClick={() => expandRow(row)}>
+
</button>);
}
} else {
return null;
}
};
if (rows) {
return rows.map((row) => {
// if(!_.isEmpty(row)) {
return (<Fragment key={row.id}>
<tr key={row.id}>
<td>
<ExpandCollapsToggle row={row} expandRow={expandRow} collapseRow={collapseRow}/>{" "}
{row.split}
- {row.id}
</td>
<td>{row.name}</td>
</tr>
<ExpandableTableRow rows={row.children}/>
</Fragment>);
// }
});
} else {
return null;
}
};
const splitData = this.state.splitOption.map((ob) => {
return (<Fragment key={ob}><span>{ob}</span> > </Fragment>)
})
if (this.state.data) {
return (
<Fragment>
{splitData} <br/>
<button onClick = {()=>addNewSplit("name")}>camp name</button>
<button onClick = {()=>addNewSplit("os")}>os</button>
<button onClick = {()=>addNewSplit("country")}>country</button>
<ExpandableTableRow rows={this.state.data} />
</Fragment>
);
} else {
return null;
}
}
}
export default TableRowData;
Also i have create example of codesandbox.io - Link
Here is how you play with ui to replicate scenario
First click on camp name, expand icon will appear
Now expand if you want to, you can see data according split under
Now add one more split OS or Country and you can see expand icon with 2nd level rows
Next step is to remove "Camp Name", Here is issue when camp name is removed, table should be re render according available splits, in our case user's all row should be removed and + icon must be there are we have next split os or country available, i used default split id, it will be there always
import React, { useState, useEffect, useRef, Fragment } from "react";
import axios from "axios";
const test_data = [{
"id":1,
"name":"Leanne Graham",
"username":"Bret",
"email":"Sincere#april.biz",
"address":{
"street":"Kulas Light",
"suite":"Apt. 556",
"city":"Gwenborough",
"zipcode":"92998-3874",
"geo":{
"lat":"-37.3159",
"lng":"81.1496"
}
},
"phone":"1-770-736-8031 x56442",
"website":"hildegard.org",
"company":{
"name":"Romaguera-Crona",
"catchPhrase":"Multi-layered client-server neural-net",
"bs":"harness real-time e-markets"
}
}];
class TableRowData extends React.Component {
constructor(props) {
super(props);
this.state = {
showIcon: false,
selection: [],
data: [],
splitOption: ["campid"]
};
}
// function to check if next split is there or not if there than return nextsplit
checkIfSplitExist = (currentSplit) => {
const i = this.state.splitOption.indexOf(currentSplit);
if (i > -1 && this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
}
return null;
}
getReportData = () => {
// axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then(({data}) => {
this.setState({
data: test_data.map((row) => {
row.name = this.state.splitOption[0];
row.isExpanded = false;
row.currentSplit = this.state.splitOption[0];
row.nextSplit = this.checkIfSplitExist(this.state.splitOption[0])
row.parentId = 0;
row.isVisble = true;
console.log(row)
return row;
})
});
// });
}
componentDidMount() {
this.getReportData()
}
render() {
// update state function
const updateState = () => {
this.setState({
data: [...this.state.data],
splitOption: [...this.state.splitOption],
selection: [...this.state.selection],
}, () => { })
}
const recursionUpdateAndDeleteRow = (parentRow, childRow, split, index = 0) => {
childRow.children && childRow.children.forEach((r) => {
recursionUpdateAndDeleteRow(childRow, r, split, index + 1);
});
if (parentRow && split.indexOf(childRow.currentSplit) == -1) {
delete parentRow.children;
}
childRow.currentSplit = split[index];
childRow.nextSplit = split[index + 1] || null;
if (!childRow.children) {
childRow.isExpanded = false;
}
}
const recursionUpdateAndDeleteRows = (rows, split) => {
const _copy = [...rows];
_copy.forEach((row) => {
recursionUpdateAndDeleteRow(null, row, split);
});
return _copy;
}
const toggleSplit = (split) => {
const index = this.state.splitOption.indexOf(split);
let currentSplitOptions = [...this.state.splitOption];
if (index > -1) {
currentSplitOptions.splice(index, 1)
}
else {
currentSplitOptions.push(split);
}
const _data = recursionUpdateAndDeleteRows(this.state.data, currentSplitOptions);
this.setState({
splitOption: currentSplitOptions,
data: _data
})
}
// add lazyload expand data
const ExpandableTableRow = ({ rows }) => {
const expandRow = (row) => {
row.children = [
{
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: this.checkIfSplitExist(row.nextSplit),
isVisble: true
}, {
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: this.checkIfSplitExist(row.nextSplit),
isVisble: true
}
];
row.isExpanded = true;
updateState();
};
// call whenever - click
const collapseRow = (row) => {
delete row.children;
row.isExpanded = false;
updateState();
};
// toggle
const ExpandCollapsToggle = ({ row }) => {
// display +/- only if nextsplit is not undefined or null
if (row.nextSplit) {
if (row.isExpanded) {
return (
<button type="button" onClick={() => collapseRow(row)}>
-
</button>
);
}
return (
<button type="button" onClick={() => expandRow(row)}>
+
</button>
);
}
return null;
};
if (rows) {
return rows.map((row) => {
return (
<Fragment key={row.id}>
<tr key={row.id}>
<td>
<ExpandCollapsToggle
row={row}
/>
{" "}{row.split} - {row.id}
</td>
<td>{row.name}</td>
</tr>
<ExpandableTableRow rows={row.children} />
</Fragment>
);
});
} else {
return null;
}
};
if (this.state.data) {
return (
<Fragment>
{this.state.splitOption.join(', ')} <br />
<button onClick={() => toggleSplit("name")}>
camp name
</button>
<button onClick={() => toggleSplit("os")}>os</button>
<button onClick={() => toggleSplit("country")}>country</button>
<br />
<ExpandableTableRow rows={this.state.data} />
</Fragment>
);
} else {
return null;
}
}
}
export default function App() {
return (
<div>
<TableRowData />
</div>
);
}
Here working example
I have a parent component which has timer component inside it. Timer starts at 15 minutes and count downs till 0. When my timer shows time as 0 I want to trigger a submit button event, submit button is inside Quiz Component (Quiz Component is also a child component of Parent Component). I found probably I can use MutationObserver when p tag changes. I am not sure whether it's the correct and only approach or there is better way to achieve this.
Parent Component:
import React, { Component } from 'react';
import '../css/App.css'
import Quiz from './Quiz';
import Timer from './Timer';
import { connect } from 'react-redux';
import { ActionTypes } from '../redux/constants/actionTypes';
import { saveQuizAll, getQuizIndex } from '../commonjs/common.js';
const mapStateToProps = state => { return { ...state.quiz, ...state.quizAll } };
const mapDispatchToProps = dispatch => ({
onQuizLoad: payload => dispatch({ type: ActionTypes.QuizLoad, payload }),
onQuizChange: payload => dispatch({ type: ActionTypes.QuizAnswerAll, payload }),
onPagerUpdate: payload => dispatch({ type: ActionTypes.PagerUpdate, payload })
});
class QuizContainer extends Component {
state = {
quizes: [
{ id: 'data/class1.json', name: 'Class 1' },
{ id: 'data/class2.json', name: 'Class 2' },
{ id: 'data/class3.json', name: 'Class 3' },
{ id: 'data/class4.json', name: 'Class 4' },
],
quizId: 'data/class1.json'
};
pager = {
index: 0,
size: 1,
count: 1
}
componentDidMount() {
console.log('componentDidMount');
this.load(this.state.quizId);
}
load(quizId, isValReload) {
console.log('In load');
let url = quizId || this.props.quizId;
if (isValReload) {
let quiz = this.props.quizAll.find(a => url.indexOf(`${a.id}.`) !== -1);
console.log('In load quiz : ', quiz);
this.pager.count = quiz.questions.length / this.pager.size;
this.props.onQuizLoad(quiz);
this.props.onPagerUpdate(this.pager);
}
else {
fetch(`../${url}`).then(res => res.json()).then(res => {
let quiz = res;
quiz.questions.forEach(q => {
q.options.forEach(o => o.selected = false);
});
quiz.config = Object.assign(this.props.quiz.config || {}, quiz.config);
this.pager.count = quiz.questions.length / this.pager.size;
this.props.onQuizLoad(quiz);
this.props.onPagerUpdate(this.pager);
});
}
}
//This event implements restriction to change class without finishing curretnly selectd class
onClassClick = (e) => {
let qus = this.props.quiz.questions;
// console.log(qus);
let isNotAllAns = qus.some((q, i) => {
var isNot = false;
if (q.answerType.id !== 3 && q.answerType.id !== 4) {
isNot = (q.options.find((o) => o.selected === true)) === undefined;
}
else {
// console.log('q', q);
isNot = ((q.answers === "" || q.answers.length === 0));
}
return isNot;
});
if (isNotAllAns) {
alert('Please complete the quiz.');
e.stopPropagation();
}
}
/*
saveQuizAll(_quizAll, _quiz) {
let allQuiz = [];
// , _quizAll, _quiz;
// if (true) {
// _quiz = this.quiz;
// _quizAll = this.quizAll;
// }
console.log(this, _quiz, _quizAll);
if (_quiz.questions.length !== 0) {
if (_quizAll.length !== undefined) {
console.log('Not Initial Setup Splice', _quiz.id);
allQuiz = _quizAll;
const qIndex = this.getQuizIndex(_quiz.id.toString());
if (qIndex > -1) {
allQuiz.splice(qIndex, 1, _quiz);
}
else {
allQuiz.splice(_quizAll.length, 0, _quiz);
// allQuiz.splice(this.props.quizAll.length-1, 0, this.props.quizAll, this.props.quiz);
}
}
else {
allQuiz[0] = _quiz;
}
return allQuiz;
// if (true) {
// this.onQuizChange(allQuiz);
// }
}
}
*/
onChange = (e) => {
// console.log(this.props.quizAll, this.props.quizAll.length);
let allQuiz = [];
allQuiz = saveQuizAll(this.props.quizAll, this.props.quiz);
//below code converted into saveQuizAll funstion
/*
if (this.props.quizAll.length !== undefined) {
console.log('Not Initial Setup Splice', this.props.quiz.id);
allQuiz = this.props.quizAll;
const qIndex = this.getQuizIndex(this.props.quiz.id.toString());
if (qIndex > -1) {
allQuiz.splice(qIndex, 1, this.props.quiz);
}
else {
allQuiz.splice(this.props.quizAll.length, 0, this.props.quiz);
// allQuiz.splice(this.props.quizAll.length-1, 0, this.props.quizAll, this.props.quiz);
}
}
else {
allQuiz[0] = this.props.quiz;
}
*/
// console.log('allQuiz Out - ', allQuiz);
this.props.onQuizChange(allQuiz);
console.log('Check QuizAll - ', this.props.quizAll);
const aQuiz = JSON.parse(JSON.stringify(this.props.quizAll));
this.setState({ quizId: e.target.value });
if (aQuiz.length !== undefined && getQuizIndex(this.props.quizAll, e.target.value) > -1) {
// console.log(aQuiz.findIndex(a => e.target.value.indexOf(`${a.id}.`) !== -1));
this.load(e.target.value, true);
}
else {
this.setState({ quizId: e.target.value });
this.load(e.target.value, false);
}
}
// getQuizIndex(qID) {
// return this.props.quizAll.findIndex(a => (qID.indexOf(`${a.id}.`) !== -1 || qID.indexOf(`${a.id}`) !== -1));
// }
render() {
return (
<div className="container">
<header className="p-2">
<div className="row">
<div className="col-6">
<h3>DADt Application</h3>
</div>
<div className="col-6 text-right">
<label className="mr-1">Select Quiz:</label>
<select onChange={this.onChange} onClick={this.onClassClick}>
{this.state.quizes.map(q => <option key={q.id} value={q.id}>{q.name}</option>)}
</select>
</div>
</div>
</header>
<Timer duration={900}/>
<Quiz quiz={this.state.quiz} quizId={this.state.quizId} saveAll={saveQuizAll} mode={this.state.mode} />
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(QuizContainer);
Here is my Timer Component
import React, { Component } from 'react'
class Timer extends Component {
constructor(props) {
super(props);
this.state = {
seconds: 0
};
}
tick() {
this.setState((prevState) => ({
seconds: prevState.seconds + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { duration } = this.props;
let timeLeft = duration - this.state.seconds;
timeLeft = Number(timeLeft);
let minutes = Math.floor(timeLeft % 3600 / 60);
let seconds = Math.floor(timeLeft % 3600 % 60);
let minutesDisplay = minutes > 0 ? minutes + (minutes === 1 ? " : " : " : ") : "";
let secondsDisplay = seconds > 0 ? seconds + (seconds === 1 ? "" : "") : "";
return <p className="badge badge-success">Time Left: {minutesDisplay}{secondsDisplay}</p>;
}
}
export default Timer;
Quiz Component:
import React, { Component } from 'react';
import { ActionTypes } from '../redux/constants/actionTypes';
import Review from './Review';
import Questions from './Questions';
import Result from './Result';
import { connect } from 'react-redux';
// import { saveQuizAll } from '../commonjs/common.js';
const mapStateToProps = state => { return { ...state.quiz, ...state.mode, ...state.pager, ...state.quizAll } };
const mapDispatchToProps = dispatch => ({
onSubmit: payload => dispatch({ type: ActionTypes.QuizSubmit, payload }),
onQuizChange: payload => dispatch({ type: ActionTypes.QuizAnswerAll, payload }),
onPagerUpdate: payload => dispatch({ type: ActionTypes.PagerUpdate, payload })
});
class Quiz extends Component {
move = (e) => {
let id = e.target.id;
let index = 0;
if (id === 'first')
index = 0;
else if (id === 'prev')
index = this.props.pager.index - 1;
else if (id === 'next') {
index = this.props.pager.index + 1;
}
else if (id === 'last')
index = this.props.pager.count - 1;
else
index = parseInt(e.target.id, 10);
if (index >= 0 && index < this.props.pager.count) {
let pager = {
index: index,
size: 1,
count: this.props.pager.count
};
this.props.onPagerUpdate(pager);
}
}
saveStore(e) {
let allQuiz = [];
console.log(this, e);
allQuiz = this.props.saveAll(e.props.quizAll, e.props.quiz);
console.log(allQuiz);
this.props.onQuizChange(allQuiz);
}
setMode = (e) => this.props.onSubmit(e.target.id);
// setMode(e) {
// console.log('in mode',e);this.props.onSubmit(e.target.id);
// }
renderMode() {
console.log('Inside here', this.props.mode);
if (this.props.mode === 'quiz') {
return (<Questions move={this.move} />)
} else if (this.props.mode === 'review') {
return (<Review quiz={this.props.quiz} move={this.move} />)
} else {
console.log('Before Results');
const divSel = document.querySelector('div.col-6.text-right');
// console.log('divSel', divSel);
if (divSel) {
divSel.style.display = "none";
}
return (<Result questions={this.props.quizAll || []} />)
}
}
render() {
return (
<div>
{this.renderMode()}
{(this.props.mode !== 'submit') &&
<div>
<hr />
<button id="quiz" className="btn btn-primary" onClick={this.setMode}>Quiz</button>
<button id="review" className="btn btn-primary" onClick={this.setMode}>Review</button>
<button id="submit" className="btn btn-primary" onClick={(e) => {this.setMode(e); this.saveStore(this)}}>Submit Quiz</button >
</div >}
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Quiz);
I think you can have two approaches.
1. The "react" way
In the Parent component:
// ...
constructor(props) {
// ...
this.state = {
timeExpired: false
};
}
const onTimeExpired = () => {
this.setState({timeExpired: true});
}
// ...
render() {
return (
<div className="container">
{ // ... }
<Timer duration={900} onTimeExpired={onTimeExpired}/>
<Quiz quiz={this.state.quiz} quizId={this.state.quizId} saveAll={saveQuizAll} mode={this.state.mode} triggerSubmit={this.state.timeExpired} />
</div>
);
}
In the Timer component:
// ...
componentDidUpdate() {
if (this.state.seconds === this.props.duration) {
this.props.onTimeExpired();
}
}
// ...
In the Quiz component:
// ...
componentDidUpdate() {
if (this.props.triggerSubmit) {
// Do whatever you do on submit
}
}
// ...
2. The "quick and dirty" way:
In the Timer component
// ...
componentDidUpdate() {
if (this.state.seconds === this.props.duration) {
const quizForm = document.getElementById('quizFormId');
quizForm && quizForm.submit();
}
}
// ...
Provide a prop method onTimeFinished in your Timer component. Then in your render function you can add
{ !(this.props.duration-this.state.seconds) && this.props.onTimeFinished() }
Reference: React Conditional Rendering
try this:
Parent Component:
// state
state = {
triggerSubmit: false
}
// functions
doSubmit = () => {
this.setState({ triggerSubmit: true });
}
resetSubmit = () => {
this.setState({ triggerSubmit: false });
}
// jsx
<Timer duration={900} doSubmit={this.doSubmit} />
<Quiz
quiz={this.state.quiz}
quizId={this.state.quizId}
saveAll={saveQuizAll}
mode={this.state.mode}
resetSubmit={this.resetSubmit}
triggerSubmit={this.state.triggerSubmit} />
Timer Component:
// function
doSubmit = (timeLeft) => {
if (timeLeft === 0) {
this.props.doSubmit();
}
}
// jsx
<p className="badge badge-success"
onChange={() => {this.doSubmit(timeLeft)}>
Time Left: {minutesDisplay}{secondsDisplay}
</p>
Quiz Component:
// state
state = {
triggerSubmit: this.props.triggerSubmit
}
// function
triggerSubmit = () => {
if (this.state.triggerSubmit) {
your trigger submit code here...
this.props.resetSubmit();
}
}
I struck in between the code where we want to display the products based on search field entered value . for every key stroke it must check whether the product is available or not. if available it must show. can anyone help me on this.
enter codeclass DashView extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
results: []
};
this.getSearchInfo = this.getSearchInfo.bind(this);
}
componentDidMount() {
// const { isChecked } = this.state;
let apiUrl = ` https://api.myjson.com/bins/4xc0c`;
axios.get(apiUrl).then(res => {
// console.log(res.data.products);
this.setState({
products: res.data.products
});
});
}
getSearchInfo = item => {
const { products, results } = this.state;
console.log(products);
if (item.length > 0) {
for (let i = 0; i < products.length; i++) {
// console.log(products[i].title);
if (products[i].title === item) {
results.push(products[i]);
}
}
}
console.log(results);
};
}
componentDidMount() {
let apiUrl = ` https://api.myjson.com/bins/4xc0c`;
axios.get(apiUrl).then(res => {
// console.log(res.data.products);
this.setState({
products: res.data.products,
}, () => {this.getSearchInfo('');});
});
}
getSearchInfo = (item) => {
const { products, results } = this.state;
if (item && item.length > 0) {
results = products.filter(prod => {
if(prod.title.indexOf(item) > -1) {
return prod;
}
});
} else {
Object.assign(results, products);
}
this.setState({
...this.state,
results
})
}
Could you try this?