JS/React - Why is my array rendering in the wrong order? - javascript

Currently, I am making a private messaging feature for a React app. I'm fairly new to React, so I'm not sure where I'm messing this up. Also, please note: For this particular project, I am unable to use jQuery/Redux/etc... just plain React.
So before I show my code, this is what results I have now:
The sent messages appear ordered properly, and the received messages are ordered properly, BUT it shows all sent messages before then showing all received messages. I would like for them all to display in the order that they were sent, regardless of which user sent it (think of how your average texting app works)
Now for my code (sorry I included a lot, I'm not sure where in the process it is messing up):
data.json
"privateMessages": [
{
"userId": 2,
"receiverId": 3,
"message": "first",
"id": 1
},
{
"userId": 3,
"receiverId": 2,
"message": "second",
"id": 2
},
{
"userId": 2,
"receiverId": 3,
"message": "third",
"id": 3
},
{
"userId": 2,
"receiverId": 3,
"message": "fourth",
"id": 4
},
{
"userId": 3,
"receiverId": 2,
"message": "fifth",
"id": 5
}
],
"users": [
{
"name": "User 1",
"id": 2
},
{
"name": "User 2",
"id": 3
}
MsgCard.js --- I suspect the problem is here but not sure.
export const MsgCard = ({message}) => {
const currentUserId = sessionStorage.getItem("nutshell_user");
if (message.sentBySelf) {
return (
<div className="sentMsg">
<h4>
<span className="nameOfSender">{message.user?.name}: </span>
{`${message.message}`}
</h4>
</div>
)
} else if (message.receiverId === parseInt(currentUserId)) {
return (
<div className="receivedMsg">
<h4>
<span className="nameOfSender">{message.user?.name}: </span>
{`${message.message}`}
</h4>
</div>
)
}
}
MsgList.js
export const MsgList = () => {
const [messages, setMessages] = useState([])
const currentUserId = sessionStorage.getItem("nutshell_user");
const getAllPMs = () => {
return fetch(`${remoteURL}/privateMessages/?_expand=user`)
.then(res=>res.json())
}
const scrollToEnd = () => {
const container = document.querySelector(".messagesContainer")
container.scrollTop = container.scrollHeight
}
const getPMs = () => {
getAllPMs().then(allPMs => {
const sentByCurrent = allPMs.filter(message => message.userId === parseInt(currentUserId))
const sentByOthers = allPMs.filter(message => message.userId !== parseInt(currentUserId))
const newCurrent = sentByCurrent.map(message => {
message.sentBySelf = true
return message
})
const newOthers = sentByOthers.map(message => {
message.sentBySelf = false
return message
})
const allMessages = newCurrent.concat(newOthers)
return allMessages
})
.then(allMsgsArray => {
setMessages(allMsgsArray)
})
.then(() => {
scrollToEnd()
})
}
useEffect(() => {
getPMs()
},[])
if (messages.length > 0) {
return (
<>
<div className="messagesContainer">
{messages.map(message => {
return <MsgCard key={message.id} message={message} />
})}
</div>
IGNORE THIS COMPONENT<MsgInput renderList={getPMs} />
</>
)
} else {
return (
<>
<div className="messagesContainer">
</div>
IGNORE THIS COMPONENT <MsgInput renderList={getPMs} />
</>
)
}
}

Your issue is how you handle adding the sentBySelf attribute to each message:
getAllPMs().then(allPMs => {
const sentByCurrent = allPMs.filter(message => message.userId === parseInt(currentUserId))
const sentByOthers = allPMs.filter(message => message.userId !== parseInt(currentUserId))
const newCurrent = sentByCurrent.map(message => {
message.sentBySelf = true
return message
})
const newOthers = sentByOthers.map(message => {
message.sentBySelf = false
return message
})
const allMessages = newCurrent.concat(newOthers)
return allMessages
})
You filter all sentByCurrent then all sentByOthers then when you rebuild the list you concat current with others. Ensuring all current come before all others.
You can use map instead to add the attribute without affecting the order.
const allPMs = [
{
"userId": 2,
"receiverId": 3,
"message": "first",
"id": 1
},
{
"userId": 3,
"receiverId": 2,
"message": "second",
"id": 2
},
{
"userId": 2,
"receiverId": 3,
"message": "third",
"id": 3
},
{
"userId": 2,
"receiverId": 3,
"message": "fourth",
"id": 4
},
{
"userId": 3,
"receiverId": 2,
"message": "fifth",
"id": 5
}
];
const currentUserId = "2";
const orderMaintained = allPMs.map(curr => ({...curr, sentBySelf: curr.userId === parseInt(currentUserId)}));
console.log(orderMaintained);
In your program this would look something like:
getAllPMs().then(allPMs => {
return allPMs.map(curr => ({...curr, sentBySelf: curr.userId === parseInt(currentUserId)}));
})

Add sentBySelf for the current object, so you will keep the sequence.
getAllPMs()
.then((allPMs) =>
allPMs.privateMessages.map((msg) => ({
...msg,
msg.userId === parseInt(currentUserId),
}))
) [...]
Here a live example:
https://codesandbox.io/s/ecstatic-hamilton-uck5n?fontsize=14&hidenavigation=1&theme=dark

Related

I try to map the content of an Array i mapped but cant reach it (is not defined no-undef)

In my implementation i want to load an array of questions from my JSON database, to play a quiz.
My function loads the questions and maps them. Now after checking if there are still open questions(showscore? in score selection) it should take the questions array and map the answer options. My problem is, that i cant reach the questions('question" is not defined no-undef) because i am asking for showscore in between. Is there a way to get this to compile?
My code looks like this:(the database are just filler questions for now)
import './play_quiz.css';
import React from "react"
class Play_quiz extends React.Component {
state = {
currentQuestion: 0,
showScore: false,
score: 0,
questions: []
}
componentDidMount() {
this.getRandomizedQuestions();
console.log("Current state:")
}
getRandomizedQuestions = () => {
const apiUrl = 'http://localhost:3001/questions'
fetch(apiUrl)
.then((response) => response.json())
.then(
(result) => {
console.log("From database:");
console.log(result);
let amountOfQuestions = result.length;
let randomizedResult = [];
for (let i = 0; i < amountOfQuestions; i++) {
let randomIndex = Math.floor(Math.random() * result.length);
randomizedResult.push(result[randomIndex]);
result.splice(randomIndex, 1);
}
//const randomizedResult = result.sort(() => Math.random() - 0.5)
this.setState({questions: randomizedResult });
},
(error) => {
console.log('An unexpected error occurred', error);
}
);
};
handleAnswerOptionClick = (isCorrect) => {
if (isCorrect) {
this.setState({ score: this.state.score + 1 });
}
const nextQuestion = this.state.currentQuestion + 1;
if (nextQuestion < this.state.questions.length) {
this.setState({
currentQuestion: nextQuestion
})
} else {
this.setState({
showScore: true
})
}
};
updateCurrentQuestion = () => {
this.setState({ currentQuestion: this.state.currentQuestion + 1 })
}
render() {
return (
<div className='quiz-window'>
{this.state.questions.map((question, index) => (
<div key={index}>
{question.title}
</div>)
)
}
{this.state.showScore ? (
<div className='score-section'>
korrekt beantwortet: {this.state.score} von {this.state.questions.length}
</div>
) : (
<div>
<div className='question-section'>
<div className='question-count'>
<span>Frage {this.updateCurrentQuestion}</span>/{this.state.questions.length}
</div>
</div>
<div className='answer-section'>
{question.answers.map(answer => (
<button key={answer.number} onClick={() => this.handleAnswerOptionClick(answer.isCorrect)}>
{answer.answer}
</button>
))}
</div>
</div>
)
}
</div>
)
}
}
export default Play_quiz;
{
"questions": [
{
"id": 0,
"title": "Pi Dezimal",
"author": "Timo",
"isMC": false,
"question": "Wie lauten die 4 ersten Dezimalstellen von Pi?",
"answers": "1415",
"category": null
},
{
"id": 1,
"title": "Längster Fluss",
"author": "Timo",
"isMC": true,
"question": "Welcher ist der längte Fluss der Welt?",
"answers": [
{
"number": 1,
"answer": "Donau",
"isCorrect": false
},
{
"number": 2,
"answer": "Nil",
"isCorrect": true
},
{
"number": 3,
"answer": "Wolga",
"isCorrect": false
},
{
"number": 4,
"answer": "Amazonas",
"isCorrect": false
}
],
"category": null
},
{
"id": 2,
"title": "Energieaufnahme Pflanzen",
"author": "Timo",
"isMC": false,
"question": "Durch welchen Vorgang gewinnen Pflanzen Energie?",
"answers": "Photosynthese",
"category": null
}
],
"quizzes": [
{
"id": 0,
"player": "Emil",
"questions" : [
{
"number" : 0,
"referenceID" : 1,
"isAnswered" : false,
"isCorrectlyAnswered" : false
},
{
"number" : 1,
"referenceID" : 0,
"isAnswered" : false,
"isCorrectlyAnswered" : false
}
],
"grade" : null,
"editingTime" : null,
"isFinished" : false
}
],
"profile": {
"name": "typicode"
}
}
I don't know if you wanted the question.answers.map inside this.state.questions.map and forgot you close the {} for that, but basically your question is not defined without mapping over your questions array
Try changing this:
{question.answers.map(answer => (
<button key={answer.number} onClick={() => this.handleAnswerOptionClick(answer.isCorrect)}>
answer.answer
</button>
))}
to this:
{this.state.questions.map((question) => {
return (
<>
{question.answers.map(answer =>
<button
key={answer.number}
onClick={() => this.handleAnswerOptionClick(answer.isCorrect)}>
{answer.answer}
</button>
)}
</>
);
})}

Filter the JSON - Angular 8

{"data": [
{
"client": {
"name": "TEST NAME",
"client_id": "id_client_boU6cpFX55vkLCXt52TSVB"
},
"Pay_list": [
{
"pay_id": "id_pay_aAW6mEvsCpBVT4aASDERTY"
},
{
"pay_id": "id_pay_aAW6mEvsCpBVT4aTDgVCSD"
}
],
"company": {
"name": "ABCD",
"ID": "1234"
}
},
{
"client": {
"name": "TEST NAME",
"client_id": "id_client_boU6cpFX55vkLCXt52TSVB"
},
"Pay_list": [
{
"pay_id": "id_pay_aAW6mEvsCpBVT4aASQQQW"
},
{
"pay_id": "id_pay_aAW6mEvsCpBVT4aTDDFGE"
},
{
"pay_id": "id_pay_aAW6mEvsCpBVT4aTDgVDFE"
}
],
"company": {
"name": "fBfD",
"ID": "5214"
}
},
]
}
JSON
I need to filter this JSON and get the company name.
this.Detail = result.data.filter(item => item.client.client_id== this.client_client_id && item.Pay_list.pay_id == this.PayId);
I have tried the above code and it trow the error because Pay_list is an array.
How can I filter the above JSON? Please assist me.
You can try this snippet instead. Have attached a Stackblitz Demo for your reference :
Possible variables needed for filter:
const clientId = "id_client_boU6cpFX55vkLCXt52TSVB";
const payId = "id_pay_aAW6mEvsCpBVT4aASDERTY";
Code
const result = response
.data
.filter(({ client: { client_id }, Pay_list }) => client_id === clientId && Pay_list.some(pay => pay.pay_id === payId))[0]
.company
.name;
Result:
ABCD
Here you go:
const client_id = 'id_client_boU6cpFX55vkLCXt52TSVB';
const pay_id = 'id_pay_aAW6mEvsCpBVT4aASQQQW';
const filtered = result.data.filter((item) => {
if (item.client.client_id !== client_id) return false;
if (item.Pay_list.map(({ pay_id }) => pay_id).includes(pay_id)) return false;
return true;
});
try this:
const client_id = 'id_client_boU6cpFX55vkLCXt52TSVB';
const pay_id = 'id_pay_aAW6mEvsCpBVT4aASQQQW';
const result = response
.data
.filter(({ client: { client_id }, Pay_list }) => client_id === clientId && Pay_list.some(pay => pay.pay_id === payId))[0]
.company
.name;

How to change the format of JSON in the front-end in React

I have JSON called by fetch request that looks like this:
[{
"type": "1",
"First": {
"Id": "123456"
}
},
{
"type": "2",
"Second": [{
"Id": "13333"
},
{
"Id": "255555"
},
{
"Id": "37777"
},
{
"Id": "48888"
}
]
}
]
What I did is that I find object with type 1 and add it to object which type 2 and splice my array to just have an object which type 2. Now it looks like this:
[{
"type": "2",
"First": {
"Id": "123456"
},
"Second": [{
"Id": "13333"
},
{
"Id": "255555"
},
{
"Id": "37777"
},
{
"Id": "48888"
}
]
}]
I have two problems. First I want to add obeject type 1 to every objects of array second, like this:
[{
"type": "2",
"Second": [{
"Id": "13333",
"First": {
"Id": "123456"
}
},
{
"Id": "255555",
"First": {
"Id": "123456"
}
},
{
"Id": "37777",
"First": {
"Id": "123456"
}
},
{
"Id": "48888",
"First": {
"Id": "123456"
}
}
]
}]
Secondly I want my JSON to just included Second array, like this:
[{
"Id": "13333",
"First": {
"Id": "123456"
}
},
{
"Id": "255555",
"First": {
"Id": "123456"
}
},
{
"Id": "37777",
"First": {
"Id": "123456"
}
},
{
"Id": "48888",
"First": {
"Id": "123456"
}
}
]
How can I solve these two issues? Here is a piece of my code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
library: null,
perPage: 20,
currentPage: 1,
maxPage: null,
}
}
componentDidMount() {
fetch('/json.bc', {
method: 'get',
})
.then(response => response.text())
.then(text => {
const Maindata = JSON.parse(text.replace(/\'/g, '"'))
const type1 = Maindata.find(({ type }) => type === '1');
const MergedData = Maindata.map(item => item.type === '1' ? item : { ...type1, ...item });
const MergedData2 = MergedData.splice(1)
this.setState(state => ({
...state,
data: MergedData2
}), () => {
this.reorganiseLibrary()
})
}).catch(error => console.error(error))
}
reorganiseLibrary = () => {
const { perPage, data } = this.state;
let library = data;
library = _.chunk(library, perPage);
this.setState({
library,
currentPage: 1,
maxPage: library.length === 0 ? 1 : library.length
})
}
previousPage = event => {
this.setState({
currentPage: this.state.currentPage - 1
})
}
nextPage = event => {
this.setState({
currentPage: this.state.currentPage + 1
})
}
handlePerPage = (evt) =>
this.setState({
perPage: evt.target.value
}, () => this.reorganiseLibrary());
renderLibrary = () => {
const { library, currentPage } = this.state;
if (!library || (library && library.length === 0)) {
return <div>NOResult</div>
}
return library[currentPage - 1].map((item, i) => (
<div className="Wrapper">{item.id}</div>
))
}
render() {
const { library, currentPage, perPage, maxPage } = this.state;
return (
<div>
{this.renderLibrary()}
<ul id="page-numbers">
<li>
{currentPage !== 1 && (
<button onClick={this.previousPage}></button>
)}
</li>
<li>{this.state.currentPage}</li>
<li>{this.state.maxPage}</li>
<li>
{(currentPage < maxPage) && (
<button onClick={this.nextPage}></button>
)}
</li>
</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('Result'))
You could get the First from an item with type: 1 using find. Then filter the type: 2 items and create an new array of objects with First nested in every object in Second
const input=[{"type":"1","First":{"Id":"123456"}},{"type":"2","Second":[{"Id":"13333"},{"Id":"255555"},{"Id":"37777"},{"Id":"48888"}]}]
const { First } = input.find(a => a.type === "1") || {}
const output1 = input.filter(a => a.type === "2")
.map(a => {
const Second = a.Second.map(b => ({ ...b, First }))
return { ...a, Second }
})
console.log(JSON.stringify(output1, null, 3))
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can easily get the second output like output1[0].Second. Depends on how many items with type: 2 will be there in the array.

How i can get data from another object?

Plunker
I have two structures - ingredients and recipes
[{
"id":"1",
"name": "Cucumber"
},
..
]
and
[{
"id":"1",
"name": "Salad1",
"recipein":[1, 3, 5]
}, {
...
}
]
and i want to show names of ingredients in each salad by press a button.
I filtered object to get ID of object, then i try to get a array of ingredients
getSalad(param:number) {
this.saladId = this.recipe.filter(rec => {
return rec.id.includes(param);
})
this.getNameOfIngredients(this.saladId)
}
getNameOfIngredients(saladArray:any) {
var ingredientsId = saladArray.map(function(num) {
return num.recipein;
});
i getting array [1,2,4] now i want to show all names of ingredients from this.ingredients with this array of id's.
How can i do this?
Plunker
I made updates in your plunker. I think thats what are you looking for: Plunker
getSalad(param:number) {
this.saladId = this.recipe.filter(rec => +rec.id === param )[0];
if(!this.saladId){
this.currentSalad = "Salad not found";
return;
}
this.currentSalad = this.getNameOfIngredients(this.saladId)
}
getNameOfIngredients(saladArray:any) {
return this.ingredients.filter( ing => {
return saladArray.recipein.indexOf(+ing.id) !== -1;
});
let _ingredients = []
this.ingredients.foreach((ingr)=>{
if(this.ingreIDArry.indexof(ingr.id) > -1){
_ingredients.push(ingr.name)
}
})
return _ingredients
is this what you want?
if you can flatten the array, it would be very straightforward for us to do lookups.
Here is what you could do.
const salads = [{
"id": "1",
"name": "Salad1",
"recipein": [1, 3, 5]
}];
const ingredients = [{
"id": "1",
"name": "Cucumber"
},
{
"id": "2",
"name": "Cucumber2"
},
{
"id": "3",
"name": "Cucumber3"
},
{
"id": "4",
"name": "Cucumber4"
},
{
"id": "5",
"name": "Cucumber5"
}
];
const flattenIngredients = (() => {
const output = {};
ingredients.forEach((ingredient) => {
output[ingredient.id] = ingredient;
});
return output;
})();
const getSalad = (saladId) => {
const filteredSalad = salads.filter((salad) => {
return saladId == salad.id;
});
if (filteredSalad.length > 0) {
const salad = filteredSalad[0];
return salad.recipein.map((receip) => flattenIngredients[receip].name);
}
}
console.log(getSalad(1));

Passing list of values as filtertable content

I'm attempting to filter using a list of values with React.
All my "tags" have a "taglevel" to indicate their relevance.
I want it to "cancel out" tags which are the same (ie don't repeat the tag if its' the same).
I want the first row to show all tag.name with "taglevel" of 1.
I want the second row to show all tag.name with "taglevel" of 2 or more.
I am unable to show and filter on the value "tags". Possibly it is around line 145 of my codepen where I have made the error.
Here is what I am trying to achieve:
I've put this together in a codepen.
http://codepen.io/yarnball/pen/GqbyWr?editors=1010
Without success, I have now tried the following:
I tried filtering using this using:
var LevelFilter = React.createClass({
render: function(){
if (!this.props.tags) return null;
return this.props.tags.filter(tag => tag.taglevel === this.props.targetLevel).map(tag => <a onClick={this.props.onClick}>{tag.name}</a>);
}
});
Then trying to get it in my return here:
render: function(){
...
var getUniqueCategories=[];
PHOTODATA.forEach(function(el){
if(getUniqueCategories.indexOf(el.tag) === -1 ) getUniqueCategories.push(el.tag);
})
return (
<div className="overlay-photogallery">
<div className="filter-panel"><b>Tags with taglevel 1 only to be displayed</b>
{
getUniqueCategories.map(function(el,i){
var boundClick = titleToSelect.bind(null,el);
return <LevelFilter onClick={boundClick} targetLevel={1} tags={el.tag} />
})
}
<a className="resetBtn" onClick={this.resetFilter}> Reset Filter </a>
</div>
My data looks like this:
"title": "Into the Wild",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Book",
"taglevel": 1,
"id": 2
}
],
"info": []
}
TL;DR
You have some serious issues with your array manipulation and your React components.
Remember that React advocates a specific top down structure and you should read up on it some more. Each React Component should use props as much as possible and ideally only 1 top-level component should hold state.
QUICK ways forward:
Pass all the data down and let each level filter make the list unique.
Seriously, split up your components and let them depend on props as much as possible.
Give variables meaningful names. el is not meaningful and in your case refers to PHOTO items in the PHOTODATA array, tags in a PHOTO and then you use element to mean something else again. Don't go to over the top, but at least be able to identify what the variable is supposed to do.
I've given in and made a codepen with a much updated structure. The behaviour may not be exactly what you're looking for, but look at the code and how it is organised and how information is shared and passed between components.
http://codepen.io/anon/pen/AXGGLy?editors=1010
UPDATE
To allow multiple filters two methods should be updated:
selectTag: function (tag) {
this.setState({
displayedCategories: this.state.displayedCategories.concat([tag])
});
}
tagFilter: function (photo) {
return this.props.displayedCategories.length !== 0 &&
this.props.displayedCategories.every(function(thisTag) {
return photo.tag.some(function (photoTag) {
return photoTag.id === thisTag.id &&
photoTag.taglevel === thisTag.taglevel;
});
});
},
selectTag now appends to the displayedCategories array rather than replacing it.
tagFilter now checks that at least one filter has been applied (remove this.props.displayedCategories.length !== 0 to disable this) so that it doesn't display all by default and then checks that every selected filter is present in each photo, thus making the components additive.
There are further improvements that could be made, such as to disable a level when a filter is applied at that level (one choice per level) or to show a list of applied filters, either through colour on the buttons or a tag list above the results.
(codepen updated with these latest changes)
Ok, there are a few problems with your codepen.
First, on line 137 you extract the tag array from the object:
if(getUniqueCategories.indexOf(el.tag) === -1 ) getUniqueCategories.push(el.tag);
Then, on 146 you extract it again:
return <LevelFilter onClick={boundClick} targetLevel={1} tags={el.tag} />
and again for level 2:
return <LevelFilter onClick={boundClick} targetLevel={2} tags={el.tag} />
For both of these it should be:
return <LevelFilter onClick={boundClick} targetLevel={n} tags={el} />
Which then allows another problem to manifest itself, which is that LevelFilter doesn't return a valid React component (an array is not valid).
return this.props.tags.filter(tag => tag.taglevel === this.props.targetLevel).map(tag => <a onClick={this.props.onClick}>{tag.name}</a>);
should be
return (
<div>
{
this.props.tags
.filter(tag => tag.taglevel === this.props.targetLevel)
.map(tag => <a onClick={this.props.onClick}>{tag.name}</a>)
}
</div>
);
After these changes you should have a much closer attempt to where you want to be.
There are further issues you will need to look into, things like your boundClick function won't work correctly because you only have a list of tags, not PHOTODATA.
However, just a final thought. You might want to break your React components up a little more.
For reference, here is the full code listing from the codepen:
var PHOTODATA = [{
"title": "Into the Wild",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Book",
"taglevel": 1,
"id": 2
}
],
"info": []
},{
"title": "Karate Kid",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Kids",
"taglevel": 3,
"id": 4
}
],
"info": []
},
{
"title": "The Alchemist",
"tag": [
{
"name": "Book",
"taglevel": 1,
"id": 2
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Classic",
"taglevel": 2,
"id": 4
},
{
"name": "Words",
"taglevel": 4,
"id": 4
}
],
"info": []
}];
var PhotoGallery = React.createClass({
getInitialState: function() {
return {
displayedCategories: []
};
},
selectTag: function (tag) {
this.setState({
displayedCategories: this.state.displayedCategories.concat([tag])
});
},
resetFilter: function(){
this.setState({
displayedCategories: []
});
},
render: function(){
var uniqueCategories = PHOTODATA.map(function (photo) {
return photo.tag; // tag is a list of tags...
}).reduce(function (uniqueList, someTags) {
return uniqueList.concat(
someTags.filter(function (thisTag) {
return !uniqueList.some(function(uniqueTag) {
return uniqueTag.id === thisTag.id && uniqueTag.taglevel === thisTag.taglevel
});
})
);
}, []);
return (
<div className="overlay-photogallery">
<div className="filter-panel"><b>Tags with taglevel 1 only to be displayed</b>
<PhotoGalleryLevel level={1} tags={uniqueCategories} displayedCategories={this.state.displayedCategories} selectTag={this.selectTag} />
<a className="resetBtn" onClick={this.resetFilter}> Reset Filter </a>
</div>
<div className="filter-panel"><b>Tags with taglevel 2 only to be displayed</b>
<PhotoGalleryLevel level={2} tags={uniqueCategories} displayedCategories={this.state.displayedCategories} selectTag={this.selectTag} />
</div>
<div className="PhotoGallery">
<PhotoDisplay displayedCategories={this.state.displayedCategories} photoData={PHOTODATA} />
</div>
</div>
);
}
});
var PhotoGalleryLevel = React.createClass({
render: function () {
var filteredTags = this.props.tags.filter(function (tag) {
return tag.taglevel === this.props.level;
}.bind(this));
var disabled = this.props.displayedCategories.some(function (tag) {
return tag.taglevel === this.props.level;
}.bind(this));
return (
<div>
{filteredTags.map(function (tag){
return <PhotoGalleryButton tag={tag} selectTag={this.props.selectTag} disabled={disabled} />;
}.bind(this))}
</div>
);
}
});
var PhotoGalleryButton = React.createClass({
onClick: function (e) {
this.props.selectTag(this.props.tag);
},
render: function () {
return (
<a className={this.props.disabled} onClick={this.onClick}>{this.props.tag.name}</a>
);
}
});
var PhotoDisplay = React.createClass({
getPhotoDetails: function (photo) {
console.log(this.props.displayedCategories, photo);
return (
<Photo title={photo.title} name={photo.name} tags={photo.tag} />
);
},
tagFilter: function (photo) {
return this.props.displayedCategories.length !== 0 &&
this.props.displayedCategories.every(function(thisTag) {
return photo.tag.some(function (photoTag) {
return photoTag.id === thisTag.id &&
photoTag.taglevel === thisTag.taglevel;
});
});
},
render: function () {
return (
<div>
{this.props.photoData.filter(this.tagFilter).map(this.getPhotoDetails)}
</div>
);
}
});
var Photo = React.createClass({
getTagDetail: function (tag){
return (
<li>{tag.name} ({tag.taglevel})</li>
);
},
sortTags: function (tagA, tagB) {
return tagA.taglevel - tagB.taglevel;
},
render: function(){
return (
<div className="photo-container" data-title={this.props.title} >
{this.props.title}
<ul>
{this.props.tags.sort(this.sortTags).map(this.getTagDetail)}
</ul>
</div>
);
}
});
ReactDOM.render(<PhotoGallery />, document.getElementById('main'));
With below react component I was able to do what you are looking for,
and here's what i've done in the code,
i) from the PHOTODATA array i have created taglevel1, taglevel2 array
one the render method at the begining.
ii) show them in two rows in showLevel1, showLevel2 function.
iii) when the tag item will be click it will call handleClick function and filter the data and save it to the filterData state.
import React, { Component } from 'react';
import { pluck } from 'underscore';
class Router extends Component {
constructor(props) {
super(props);
this.state = {
filterData: [],
};
this.filterArray = [];
this.PHOTODATA = [{
"title": "Into the Wild",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Book",
"taglevel": 1,
"id": 2
}
],
"info": []
},{
"title": "Karate Kid",
"tag": [
{
"name": "Movie",
"taglevel": 1,
"id": 1
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Kids",
"taglevel": 3,
"id": 4
}
],
"info": []
},
{
"title": "The Alchemist",
"tag": [
{
"name": "Book",
"taglevel": 1,
"id": 2
},
{
"name": "Adventure",
"taglevel": 2,
"id": 30
},
{
"name": "Classic",
"taglevel": 2,
"id": 4
},
{
"name": "Words",
"taglevel": 4,
"id": 4
}
],
"info": []
}];
}
handleClick(item) {
const findItem = this.filterArray.indexOf(item);
if (findItem === -1) {
this.filterArray.push(item);
} else {
this.filterArray.pop(item);
}
const filterData = [];
if(this.filterArray.length) {
this.PHOTODATA.map((item) => {
const data = pluck(item.tag, 'name');
let count = 0;
// console.log(data);
this.filterArray.map((filterItem) => {
const find = data.indexOf(filterItem);
if(find !== -1) {
count++;
}
});
if(count === this.filterArray.length) {
filterData.push(item);
}
});
}
console.log(this.filterArray);
this.setState({ filterData });
}
render() {
const taglevel1 = [];
const taglevel2 = [];
this.PHOTODATA.map((item) => {
item.tag.map((tagItem) => {
if(tagItem.taglevel === 1) {
const find = taglevel1.indexOf(tagItem.name);
if(find === -1) {
taglevel1.push(tagItem.name);
}
} else {
const find = taglevel2.indexOf(tagItem.name);
if(find === -1) {
taglevel2.push(tagItem.name);
}
}
});
});
const showLevel1 = (item, index) => {
return <span onClick={this.handleClick.bind(this, item)}> {item} </span>
};
const showLevel2 = (item, index) => {
return <span onClick={this.handleClick.bind(this, item)}> {item} </span>
};
const showData = (item, index) => {
return <div>{item.title}</div>
};
return (<div>
<ul>Tag Level 1: {taglevel1.map(showLevel1)}</ul>
<ul>Tag Level 2: {taglevel2.map(showLevel2)}</ul>
<div>Movie Title: {this.state.filterData.map(showData)}</div>
</div>);
}}
and here you can see how my outputs look like

Categories

Resources