I am trying to build a section list where the list header is the name of a class (like a school class) and the section list is the assignments that are assigned to that class.
This is my section list component:
<SectionList
sections={classData}
keyExtractor={item => item.class.id}
renderSectionHeader={({ section: classProps }) => {
return (
<Text style={styles.textStyle}>{classProps.name}</Text>
);
}}
renderItem={({ item }) => {
return (
<HomeworkItem
homeworkItem={item}
deleteHomework={() => this.props.removeHomework(item)}
/>
);
}}
/>
This is the code to get classData:
const classData = [];
for (let x = 0; x < this.props.classes.length; x++) {
classData.push({
classProps: this.props.classes[x],
assignments: this.filterAssignments(this.props.classes[x], this.props.homework)
});
}
And the filter assignments function is this:
filterAssignments(c, hw) {
return hw.filter(item => item.className === c.name);
}
I am receiving an error that items.length is undefined at the beginning of the list. The data appears to be working. If anyone knows how I can do this correctly your help would be greatly appreciated.
The react native docs state the following format:
const DATA = [
{
title: 'Main dishes',
data: ['Pizza', 'Burger', 'Risotto'],
},
{
title: 'Sides',
data: ['French Fries', 'Onion Rings', 'Fried Shrimps'],
}
];
this format can be achieved with the following loop:
const classData = this.props.classes.map(item => ({
title: item.name
data: this.filterAssignments(item, this.props.homework)
}));
the classData can now be passed to <SectionList as such:
<SectionList
sections={classData}
Related
I'm using React Hook Form to build a basic page builder application and it's been brilliant so far, I've been using the useFieldArray hook to create lists that contain items, however, I haven't found a way to move items between lists.
I know I can use the move() function to reorder items within the same list, however, since each list has its own nested useFieldArray I can't move the item from one list component to another list component.
If anyone knows of a way around this it would be much appreciated!
Here is a very simplified example of my current setup:
export const App = () => {
const methods = useForm({
defaultValues: {
lists: [
{
list_id: 1,
items: [
{
item_id: 1,
name: 'Apple'
},
{
item_id: 2,
name: 'Orange'
}
]
},
{
list_id: 2,
items: [
{
item_id: 3,
name: 'Banana'
},
{
item_id: 4,
name: 'Lemon'
}
]
}
]
}
});
return (
<FormProvider {...methods}>
<Page/>
</FormProvider>
)
}
export const Page = () => {
const { control } = useFormContext();
const { fields } = useFieldArray({
control,
name: 'lists'
})
return (
<ul>
{fields?.map((field, index) => (
<List listIdx={index} />
))}
</ul>
)
}
export const List = ({ listIdx }) => {
const { control, watch } = useFormContext();
const { fields, move } = useFieldArray({
control,
name: `lists[${sectionIdx}].items`
})
const handleMove = (prevIdx, nextIdx) => {
// this allows me to move within lists but not between them
move(prevIdx, nextIdx);
}
return (
<li>
<p>ID: {watch(lists[${listIdx}].list_id)}</p>
<ul>
{fields?.map((field, index) => (
<Item listIdx={index} itemIdx={index} handleMove={handleMove}/>
))}
</ul>
</li>
)
}
export const Item = ({ listIdx, itemIdx, handleMove }) => {
const { control, register } = useFormContext();
return (
<li>
<p>ID: {watch(lists[${listIdx}].items[${itemIdx}].item_id)}</p>
<label
Name:
<input { ...register('lists[${listIdx}].items[${itemIdx}]) }/>
/>
<button onClick={() => handleMove(itemIdx, itemIdx - 1)}>Up</button>
<button onClick={() => handleMove(itemIdx, itemIdx + 1)}>Down</button>
</div>
)
}
Thanks in advance!
If you'd not like to alter your default values (your data structure), I think the best way to handle this is using update method returning from useFieldArray. You have the data of both inputs that are going to be moved around, knowing their list index and item index, you could easily update their current positions with each other's data.
so i have a store that looks like this:
export class NoteStore {
notes = [
{
id: 1,
name: 'Shopping list',
children: [
{
id: 2,
name: 'Sweet',
items: [
{
id: 3,
name: 'Chocolate',
},
{
id: 4,
name: 'Fudge'
},
{
id: 5,
name: 'Cookies'
}
]
}
]
}
}
i am trying to remove an item from items by pressing a button. so far i have managed to make a remove function that does not throw an error but it doesn't do anything. i have spent so much time with this but i just can't figure out what i need to do to get the removing working properly. this is what i'm working with:
const store = useContext(NoteStore);
function removeItems(id) {
store.notes = store.notes.filter(item => item !== id);
}
//Prints notes content to the screen
const NotesArray = () => {
return store.notes[0].children[0].items.map((item) =>
<View key={item.id} style={{ flexDirection: 'row' }}>
<Text style={styles.note}>{item.name}</Text>
<TouchableOpacity style={{ justifyContent:'center', paddingLeft:5 }} onPress={() => {
removeItems(item.id);
}}>
<Text>
<Icon name={noteDeleteMode ? 'close-circle-outline': null} style={styles.deleteIcon} />
</Text>
</TouchableOpacity>
</View>
)
};
You currently comparing the id with the whole item, not with it's id.
Replace
store.notes = store.notes.filter(item => item !== id);
with
store.notes = store.notes.filter(item => item.id !== id);
(item.id instead of just item)
Probably the issue is that you're saving the reference to the old store.notes somewhere. Fortunately, you don't need to create a new array; you can just modify the old one. Use this:
function removeItems(id) {
store.notes.splice(store.notes.findIndex(item => item.id === id), 1);
}
I have an array of objects which I'm rendering by section - see title of each object "Price", "Sectors and Charges" etc.
This populates a mini modal where users can select options to update rendered columns basically a filter.
The selection of the items are working however if I make a selection of the first item "0" all sections with the first option are selected.
How can I store the selection from each object into the selectedOptions array?
Please note I'm using react js and styled components, I've not added the styled component code.
Data:
const columnsData = [
{
title: 'Price',
options: [
{
label: 'Daily Change'
},
{
label: 'Price'
},
{
label: 'Price Date'
},
{
label: 'Volatility Rating'
}
],
},
{
title: 'Sectors and Charges',
options: [
{
label: 'Sector'
},
{
label: 'Asset Class'
},
{
label: 'AMC'
},
],
},
{
title: 'Cumulative Performance',
options: [
{
label: '1 month'
},
{
label: '6 months'
},
{
label: '1 year'
},
],
},
]
Code:
const EditColumns = ({active, onClick}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const update = () => {
onClick();
}
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList>
{item.options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionList>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
export default EditColumns;
Your section lists are all sharing the same state variable, so any changes will be applied to all of them. You could fix this either by constructing a more complex state object which more closely resembles the structure of columnsData, or making each SectionList its own component with its own state. What you decide to do will depend on the degree to which the EditButtons component actually needs access to the whole state.
The second approach might look something like this:
const EditColumns = ({active, onClick}) => {
const update = () => {
onClick();
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList options={item.options}/>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
const SectionList = ({options}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<SectionListContainer>
{options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionListContainer>
)
}
I am trying to use JsonSchema-Form component but i ran into a problem while trying to create a form that, after choosing one of the options in the first dropdown a secondary dropdown should appear and give him the user a different set o options to choose depending on what he chose in the first dropdown trough an API call.
The thing is, after reading the documentation and some examples found here and here respectively i still don't know exactly how reference whatever i chose in the first option to affect the second dropdown. Here is an example of what i have right now:
Jsons information that are supposed to be shown in the first and second dropdowns trough api calls:
Groups: [
{id: 1,
name: Group1}
{id: 2,
name: Group2}
]
User: [User1.1,User1.2,User2.1,User2.2,User3.1,User3.2, ....]
If the user selects group one then i must use the following api call to get the user types, which gets me the the USER json.
Component That calls JSonChemaForm
render(){
return(
<JsonSchemaForm
schema={someSchema(GroupOptions)}
formData={this.state.formData}
onChange={{}}
uiSchema={someUiSchema()}
onError={() => {}}
showErrorList={false}
noHtml5Validate
liveValidate
>
)
}
SchemaFile content:
export const someSchema = GroupOptions => ({
type: 'object',
required: [
'groups', 'users',
],
properties: {
groups: {
title: 'Group',
enum: GroupOptions.map(i=> i.id),
enumNames: GroupOptions.map(n => n.name),
},
users: {
title: 'Type',
enum: [],
enumNames: [],
},
},
});
export const someUISchema = () => ({
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
},
types: {
'ui:options': {
size: {
lg: 15,
},
},
},
});
I am not really sure how to proceed with this and hwo to use the Onchange method to do what i want.
I find a solution for your problem.There is a similar demo that can solve it in react-jsonschema-form-layout.
1. define the LayoutField,this is part of the demo in react-jsonschema-form-layout.To make it easier for you,I post the code here.
Create the layoutField.js.:
import React from 'react'
import ObjectField from 'react-jsonschema-form/lib/components/fields/ObjectField'
import { retrieveSchema } from 'react-jsonschema-form/lib/utils'
import { Col } from 'react-bootstrap'
export default class GridField extends ObjectField {
state = { firstName: 'hasldf' }
render() {
const {
uiSchema,
errorSchema,
idSchema,
required,
disabled,
readonly,
onBlur,
formData
} = this.props
const { definitions, fields, formContext } = this.props.registry
const { SchemaField, TitleField, DescriptionField } = fields
const schema = retrieveSchema(this.props.schema, definitions)
const title = (schema.title === undefined) ? '' : schema.title
const layout = uiSchema['ui:layout']
return (
<fieldset>
{title ? <TitleField
id={`${idSchema.$id}__title`}
title={title}
required={required}
formContext={formContext}/> : null}
{schema.description ?
<DescriptionField
id={`${idSchema.$id}__description`}
description={schema.description}
formContext={formContext}/> : null}
{
layout.map((row, index) => {
return (
<div className="row" key={index}>
{
Object.keys(row).map((name, index) => {
const { doShow, ...rowProps } = row[name]
let style = {}
if (doShow && !doShow({ formData })) {
style = { display: 'none' }
}
if (schema.properties[name]) {
return (
<Col {...rowProps} key={index} style={style}>
<SchemaField
name={name}
required={this.isRequired(name)}
schema={schema.properties[name]}
uiSchema={uiSchema[name]}
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
formData={formData[name]}
onChange={this.onPropertyChange(name)}
onBlur={onBlur}
registry={this.props.registry}
disabled={disabled}
readonly={readonly}/>
</Col>
)
} else {
const { render, ...rowProps } = row[name]
let UIComponent = () => null
if (render) {
UIComponent = render
}
return (
<Col {...rowProps} key={index} style={style}>
<UIComponent
name={name}
formData={formData}
errorSchema={errorSchema}
uiSchema={uiSchema}
schema={schema}
registry={this.props.registry}
/>
</Col>
)
}
})
}
</div>
)
})
}</fieldset>
)
}
}
in the file, you can define doShow property to define whether to show another component.
Next.Define the isFilled function in JsonChemaForm
const isFilled = (fieldName) => ({ formData }) => (formData[fieldName] && formData[fieldName].length) ? true : false
Third,after you choose the first dropdown ,the second dropdown will show up
import LayoutField from './layoutField.js'
const fields={
layout: LayoutField
}
const uiSchema={
"ui:field": 'layout',
'ui:layout': [
{
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
}
},
{
users: {
'ui:options': {
size: {
lg: 15,
},
},
doShow: isFilled('groups')
}
}
]
}
...
render() {
return (
<div>
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields}
/>
</div>
)
}
I am having issues on even trying to get started with doing pagination without the use of any packages. I am pulling data from a JSON file that contains about 30-32 quotes. I need 15 quotes per page to be displayed and have no idea how to even do that using React. So far what I have is all the quotes being displayed by default. I have three buttons, each filters through the JSON to provide quotes by the theme of the quote which is displayed by the button. This is how far I got:
class App extends Component {
constructor(props) {
super(props);
this.state ={
results: quotes,
search: ""
}
}
gameFilterClick = event => {
event.preventDefault();
const games = [];
for(let i = 0; i < quotes.length; i++){
if (quotes[i].theme === "games"){
games.push(quotes[i])
}
}
this.setState({results: games})
}
movieFilterClick = event => {
event.preventDefault();
console.log('blah!!')
const movies = [];
for(let i =0; i < quotes.length; i++){
if(quotes[i].theme === 'movies'){
movies.push(quotes[i])
}
}
this.setState({results: movies})
}
allButtonClick = event => {
this.setState({results: quotes})
}
quoteSearch = query => {
let search = quotes.map
}
render() {
return (
<div className="App">
<h1>Quotes</h1>
<Search />
<div id='buttons'>
Filters:
<button onClick={this.allButtonClick}>All Quotes</button>
<button onClick={this.gameFilterClick}>Games</button>
<button onClick={this.movieFilterClick}>Movies</button>
</div>
<div id='resultsDiv'>
<Results
results={this.state.results}
/>
</div>
</div>
);
}
}
export default App;
I would recommend using react-bootstrap for this. You'll need to install two packages (they use to come in one, but now pagination package is separated):
react-bootstrap-table-next
react-bootstrap-table2-paginator
So, let's install them:
npm i --save react-bootstrap-table-next
npm i react-bootstrap-table2-paginator
And here goes a simple example of implementation:
import BootstrapTable from 'react-bootstrap-table-next';
import paginationFactory from 'react-bootstrap-table2-paginator';
// Let's imagine this is your JSON data
const yourJsonData = [{id: 1, author: "David Goggins", quote: "Life goes on"},
{ id: 2, author: "Robert Green", quote: "yes it does"}]:
// Here we define your columns
const columns = [{
dataField: 'author',
text: 'AUTHOR'
}, {
dataField: 'quote',
text: 'QUOTE'
}];
// Give it an option to show all quotes
let allQuotes = Number(yourJsonData.length);
// Set all of the major pagination options. You can reduce them if you want less
const options = {
paginationSize: 15,
pageStartIndex: 0,
firstPageText: 'First',
prePageText: 'Back',
nextPageText: 'Next',
lastPageText: 'Last',
nextPageTitle: 'First page',
prePageTitle: 'Pre page',
firstPageTitle: 'Next page',
lastPageTitle: 'Last page',
sizePerPageList: [{
text: 'show 15', value: 15
}, {
text: 'show 30', value: 30
}, {
text: 'Show all', value: allQuotes
}]
};
... and then somewhere later in your code where you want to display the table with pagination you just insert this:
<BootstrapTable
keyField='rowNumber'
data={ yourJsonData }
columns={ columns }
pagination={ paginationFactory(options) } />
I hope this solves your problem.
I've simplified your filtering logic and added client side pagination. Check out this simple working example (i've set item per page to 3, you can add more data and change it to 15 const QUOTES_PER_PAGE = <number of quotes per page>;)
const QUOTES_PER_PAGE = 3;
const Quote = ({text}) => <li>{text}</li>;
const Pagination = ({pages, goTo}) => (
<div>
{pages.map((p, i) => (
<button key={i} onClick={goTo} value={i}>{i+1}</button>
))}
</div>
)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
page: 0,
pagedQuoutes: this.divideQuoutesIntoPages(props.quotes)
};
}
divideQuoutesIntoPages = (quotes => {
const pagedQuotes = [];
[...Array(Math.ceil(quotes.length/QUOTES_PER_PAGE))].forEach((q, i) => {
pagedQuotes.push(quotes.slice(0 + QUOTES_PER_PAGE*i, QUOTES_PER_PAGE + QUOTES_PER_PAGE*i))
})
return pagedQuotes;
})
filterQuoutes = (evt) => {
const filterValue = evt.target.value;
const filteredQuoutes = this.props.quotes.filter(q => !filterValue || q.theme === filterValue);
this.setState({
pagedQuoutes: this.divideQuoutesIntoPages(filteredQuoutes)
})
}
goToPage = (evt) => {
this.setState({
page: evt.target.value
})
}
render() {
return (
<div>
<h1>Quotes</h1>
<div>
Filters:
<button onClick={this.filterQuoutes}>All Quotes</button>
<button onClick={this.filterQuoutes} value="games">Games</button>
<button onClick={this.filterQuoutes} value="movies">Movies</button>
</div>
{this.state.pagedQuoutes[this.state.page]
.map(q => (
<ul>
<Quote {...q} />
</ul>
))}
<Pagination pages={this.state.pagedQuoutes} goTo={this.goToPage} />
</div>
);
}
}
const exampleQuotes = [{
theme: 'games',
text: 'games q1'
}, {
theme: 'games',
text: 'games q2'
}, {
theme: 'games',
text: 'games q3'
}, {
theme: 'games',
text: 'games q4'
}, {
theme: 'games',
text: 'games q5'
}, {
theme: 'movies',
text: 'movies q1'
}, {
theme: 'movies',
text: 'movies q2'
}, {
theme: 'movies',
text: 'movies q3'
}]
ReactDOM.render(<App quotes={exampleQuotes} />, document.getElementById("el"))
<div id="el"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>