Having trouble connecting redux to react and dispatching actions - javascript

I'm new to redux so I might be missing something here...
redux/reducers/schools.js:
export const SET_SELECTED = 'schools/SET_SELECTED';
const initialState = {
selected: {},
schools: []
};
export default function schools(state = initialState, action) {
switch (action.type) {
case SET_SELECTED:
return {
...state,
selected: action.payload
};
default:
return state;
}
}
export function setSelected(school) {
return {
type: SET_SELECTED,
payload: school
};
}
containers/Search.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setSelected } from '../redux/reducers/schools';
import SchoolCard from '../components/SchoolCard';
class Search extends Component {
setSelectedSchool(school) {
this.props.dispatch(setSelected(school)); // Error here
}
renderShools(schools) {
return schools.map(school => {
return (
<div className="column is-8 is-offset-2" key={school.emis}>
<SchoolCard school={school} setSelected={this.setSelectedSchool} />
</div>
);
});
}
render() {
return (
<div className="container">
<div className="columns">{this.renderShools(this.props.schools)}</div>
</div>
);
}
}
export default connect(state => ({
schools: state.schools.schools
}))(Search);
When setSelectedSchool() in Search.js runs I get the following error:
Uncaught TypeError: Cannot read property 'dispatch' of undefined...
What am I doing wrong / what am I missing?

Code below represents an example how to map to props and then call dispatch :
import { addItem } from './actions/items';
class App extends Component {
render() {
return (
<div className="App">
// some more tags
</div>
);
}
};
const mapStateToProps = (state) => {
return {
items: state.items
};
};
const mapDispatchToProps = dispatch => {
return {
addItem: () => {
dispatch(addItem())
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Incase you do not do mapDispatchToProps then this.props.dispatch will be undefined.

export default connect(state => ({
schools: state.schools.schools
}),
(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}}
)(Search);
You can give 2 objects to connect
1. mapStateToProps {
schools: state.schools.schools
}
2(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}
You required dispatch (2) also in order to dispatch function
So change last line to(where you use connect) to
export default connect(state => ({
schools: state.schools.schools
}),
(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}}
)(Search);
and the function call to
setSelectedSchool(school) {
this.props.setSelected(school); // Error here
}

You need to refract your code a bit
containers/Search.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as schoolActions from '../actions/schoolActions;
import SchoolCard from '../components/SchoolCard';
class Search extends Component {
setSelectedSchool=(school)=> {
this.props.setSelectedschool(school)
}
renderShools(schools) {
return schools.map(school => {
return (
<div className="column is-8 is-offset-2" key={school.emis}>
<SchoolCard school={school} setSelected={this.setSelectedSchool} />
</div>
);
});
}
render() {
return (
<div className="container">
<div className="columns">{this.renderShools(this.props.schools)}</div>
</div>
);
}
}
export default connect(
state => ({
schools: state.schoolReducer.schools,
}),
{ ...schoolActions }
)(Search);
Create actions/schoolActions.js
export function setSelectedschool(school){
return (dispatch)=>{
dispatch({'SET_SELECTED',school})
}
}
create reducers/schoolReducer.js
const initialState={
school:[] //assuming it's an array
}
const schoolReducer =(state=initialState,action)=>{
switch(action.type){
case 'SET_SELECTED':{
return {
...state,
school:action.school
}
}
}
}

Related

How to set loader for a promise.all action in React Redux?

So when I check Redux dev-tools i see that I've received my data and they are a part of the state, but when I try to use conditional rendering it wont render the page and gives error TypeError: Cannot read property 'Global Quote' of undefined !
If I just use this.props.data.TSLA it works fine and the page renders...
When I use this.props.data.TSLA["Global Quote"]["01. symbol"] page won't render! (the keys are strings in the JSON so I need to use square brackets).
I am also using Redux-Thunk !
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { START_FETCH_DATA } from './redux/dataReducer';
class Fetcher extends Component {
componentDidMount() {
this.props.START_FETCH_DATA()
}
render() {
const { data, dataLoading } = this.props;
return (
<li className="tesla-container">
{ this.props.dataLoading ?
(<div className="ticker"> Loading! </div>)
:
(<div className="ticker">{
this.props.data.TSLA["Global Quote"]["01. symbol"] }</div>) }
</li>
)
}
const mapStateToProps = (state) => {
return {
data: state.data,
dataLoading: state.dataLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
START_FETCH_DATA: bindActionCreators(START_FETCH_DATA, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Fetcher)
and here is the reducer + actions...
export const dataReducer = (state = {dataLoading: true}, action) => {
switch(action.type) {
case "START_FETCH_DATA":
return {...state, dataLoading: true}
case "FINISH_FETCH_DATA":
return {...state, dataLoading: false, data: action.payload}
default:
return state;
}};
export const START_FETCH_DATA = () => {
return (dispatch) => {
Promise.all(
[
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=TSLA&apikey=LOL`).then(data => data.json()),
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=AMZN&apikey=LOL`).then(data => data.json())
]
)
.then(([TSLA, AMZN]) => {
dispatch({ type: "FINISH_FETCH_DATA", payload: {TSLA, AMZN} })
})
}};
DEVTOOLS SCREENSHOT
https://imgur.com/a/2Tcrdpe
For starters, you should use the data provided by redux in render():
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { START_FETCH_DATA, dataReducer } from './redux/dataReducer';
class Fetcher extends Component {
componentDidMount() {
this.props.START_FETCH_DATA()
}
render() {
return (
<li className="tesla-container">
{ this.props.dataLoading ?
(<div className="ticker"> Loading! </div>)
:
(<div className="ticker">{
this.props.data.TSLA["Global Quote"]["01. symbol"] }</div>) }
</li>
)
}
const mapStateToProps = (state) => {
return {
data: state.data,
dataLoading: state.dataLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
START_FETCH_DATA: bindActionCreators(START_FETCH_DATA, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Fetcher)
I fixed my issue.
Fixed code below:
`const mapStateToProps = (state) => {
return {
dataLoading: state.dataReducer.dataLoading,
data: state.dataReducer.data
}
}`
instead of
`const mapStateToProps = (state) => {
return {
dataLoading: state.dataLoading,
data: state.data
}
}`

Increase Like count if post id is equal to action.payload

I am working on a social network app and I want to toggle the like count on clicking(it should increment by 1 on first click and should go back to null to when pressed again) for a particular post. But now when i click on the like button, nothing happens and the screen gets vanish. I am unable to get what is wrong with my code.
Here are my files-> action creator
export const fetchPosts = () => async dispatch => {
const request = await axios.get(`${ROOT_URL}/post`, {
headers: { Authorization: `${token}` }
});
dispatch({
type: FETCH_POSTS,
payload: request
});
};
export const incrementLikesCount = id => {
return {
type: INCREMENT_LIKES_COUNT,
payload: id
};
};
index.js(reducer)
import auth from "./authReducer";
import user from "./userReducer";
import post from "./postReducer";
export default combineReducers({
auth,
user,
post,
form: formReducer
});
postreducer.js
import _ from "lodash";
import { FETCH_POSTS, INCREMENT_LIKES_COUNT } from "../actions/types";
const initialState = {
postDetail: "",
likesCount: null
};
const post = (state = initialState, action) => {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
postDetail: _.mapKeys(action.payload.data.data, "_id")
};
case INCREMENT_LIKES_COUNT:
return _.values(state.postDetail)
.reverse()
.map(post => {
if (action.payload === post._id) {
if (state.likesCount === null) {
console.log("I got executed");
return { ...state, likesCount: state.likesCount + 1 };
} else {
return {
...state,
likesCount: null
};
}
} else {
return {
state
};
}
});
default:
return state;
}
};
export default post;
and my react Component
import _ from "lodash";
// import uuid from "uuid";
import { connect } from "react-redux";
import React, { Component } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faHeart,
faCommentAlt,
faShareAlt
} from "#fortawesome/free-solid-svg-icons";
import { fetchPosts, incrementLikesCount } from "../../../actions/FeedPost";
import "./FeedPosts.css";
class FeedPosts extends Component {
componentDidMount() {
if (!this.props.fetchPosts) {
return <div>Loading...</div>;
}
this.props.fetchPosts();
}
renderPosts = () => {
return _.values(this.props.post)
.reverse()
.map(post => (
<div key={post._id} className="post-content">
<img
src={require("../../../img/blue.jpeg")}
alt="user"
className="user-image"
/>
<span>{post.postBy}</span>
<span>{post.userDesignation}</span>
<li>{post.postText}</li>
<div className="fontawesome-icons">
<div className="like-font">
<FontAwesomeIcon
icon={faHeart}
onClick={() => this.props.incrementLikesCount(post._id)}
/>
<span>{this.props.likesCount}</span>
</div>
<div className="comment-font">
<FontAwesomeIcon icon={faCommentAlt} />
</div>
<div className="share-font">
<FontAwesomeIcon icon={faShareAlt} />
</div>
</div>
</div>
));
};
render() {
return (
<div>
<ul className="posts">{this.renderPosts()}</ul>
</div>
);
}
}
const mapStateToProps = state => ({
post: state.post.postDetail,
likesCount: state.post.likesCount
});
export default connect(
mapStateToProps,
{ fetchPosts, incrementLikesCount }
)(FeedPosts);
So, Basically my question is how can I increase the like count just for a particular post, because I was able to toggle the like button but it was increasing the like count of all the posts.
The following should kind of work but it would be easier to have state.posts as array instead of converting from array to object and object to array every time.
To be sure it'll work you need to show the code where you set state.posts
case INCREMENT_LIKES_COUNT:
return {
...state,
likesCount:state.likesCount+1,
//not sure why posts need to be an object instead of it being an array
posts:Object.entries(state.posts).reduce(
(result,[key,value])=>{
if(value._id===action.payload){
//you probably didn't set the initial likes but the reducer
// where you set state.posts isn't in your question
result[key]= {...value,likes:value.likes+1};
}else{
result[key]=value;
}
return result;
},
{}
)
}
Although after seeing this again I realize the posts is an object where the id is the key so you can make it simpler:
case INCREMENT_LIKES_COUNT:
return {
...state,
likesCount:state.likesCount+1,
//not sure why posts need to be an object instead of it being an array
posts:{
...state.posts,
[action.payload]:{
...state.posts[action.payload],
likes:state.posts[action.payload].likes+1
}
}
}

Cannot change redux boolean state

I feel little confused, the problem is defineAvailableTouch action and state update connected to it.
Here is my code:
Actions/index.js
import {
ANIMATE_HELLO,
HANDLE_SCROLL,
IS_TOUCH_DEVICE,
SET_ABOUT_TOP,
SET_CONTACT_TOP,
SET_PORTFOLIO_TOP
} from "../Constants/ActionTypes";
export const animateHello = hello => ({
type: ANIMATE_HELLO,
payload: hello
});
export const handleScroll = scrollDelta => ({
type: HANDLE_SCROLL,
payload: scrollDelta
});
export const defineTouchAvailable = isTouchDevice => ({
type: IS_TOUCH_DEVICE,
payload: isTouchDevice
});
export const setAboutTop = aboutTop => ({
type: SET_ABOUT_TOP,
payload: aboutTop
});
export const setContactTop = contactTop => ({
type: SET_CONTACT_TOP,
payload: contactTop
});
export const setPortfolioTop = portfolioTop => ({
type: SET_PORTFOLIO_TOP,
payload: portfolioTop
});
Reducers/index.js
import {
IS_TOUCH_DEVICE,
} from "../Constants/ActionTypes";
import { initialState } from "../Constants/InitialState/InitialState";
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case ANIMATE_HELLO:
return {
...state,
hello: action.payload
};
case HANDLE_SCROLL:
return {
...state,
scrollState: action.payload
};
case IS_TOUCH_DEVICE:
console.log(action.payload); //!!!!!! THIS PRINTS EXPECTED VALUE !!!!!!!!!!
return {
...state,
isTouchDevice: action.payload
};
case SET_ABOUT_TOP:
return {
...state,
aboutTop: action.payload
};
case SET_CONTACT_TOP:
return {
...state,
contactTop: action.payload
};
case SET_PORTFOLIO_TOP:
return {
...state,
portfolioTop: action.payload
};
default:
return state
}
};
InitialState.js
export const initialState = {
scrollState: 0,
hello: 'H',
aboutTop: 0,
portfolioTop: 0,
contactTop: 0,
isTouchDevice: true
};
App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import About from "./Containers/About";
import Contact from "./Containers/Contact";
import Page from "./Containers/Page";
import Projects from "./Containers/Projects";
import {
defineTouchAvailable,
handleScroll
} from "./Actions";
window.onbeforeunload = () => {
handleScroll(0);
document.documentElement.scrollTop = 0;
};
const mapStateToProps = state => {
return {
isTouchDevice: state.isTouchDevice
}
};
const dispatchStateToProps = dispatch => {
return {
defineTouchAvailable: isTouchDevice =>
dispatch(defineTouchAvailable(isTouchDevice)),
handleScroll: scrollState => dispatch(handleScroll(scrollState))
}
};
class App extends Component {
componentDidMount() {
try {
document.createEvent('touchevent');
this.props.defineTouchAvailable(true);
} catch(e) {
this.props.defineTouchAvailable(false);
}
console.log(this.props.isTouchDevice); //!!!!!!!!!!!!!!! THIS ALWAYS PRINTS VALUE FROM initialState !!!!!!!!!!!!!!
if(this.props.isTouchDevice) {
document.documentElement.scroll(0, 1);
}
document.addEventListener('scroll', () => {
if (document.documentElement.scrollTop === 0) {
this.props.handleScroll(0);
}
});
}
render() {
return (
<div>
<Page/>
<Projects/>
<About/>
<Contact/>
</div>
);
}
}
export default connect(mapStateToProps, dispatchStateToProps)(App);
I really can't figure out whats wrong here.
As I commented
reducer console.log prints correct value that is expected to be assigned to my state (isTouchDevice field), but
after assigning it in dispatch action nothing changes - it is always value from initialState.
Can someone please explain it to me? Do I change my redux state uncorrectly? Then why other actions work as they're expected to?
The updated value of isTouchDevice will be available in componentDidUpdate, render or componentWillReceiveProps, not in componentDidMount.
componentDidMount will only be called one time when your component is mounted.
Note: componentWillReceiveProps is deprecated, better to not use it.

mapDispatchToProps dispatch action not working to update State

In my index.js the addCoin action is working.
import { addCoin } from './reducer/portfolio/actions'
const element = document.getElementById('coinhover');
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
store.dispatch(addCoin('bitcoin'));
When store.dispatch is called I can see the updated state here.
However I do not want to call dispatch actions from my index.js, but from within my components.
My SearchCoin component:
import React from 'react'
import { connect } from 'react-redux'
import * as R from 'ramda'
import * as api from '../../services/api'
import { addToPortfolio, findCoins } from '../../services/coinFactory'
import { addCoin } from '../../reducer/portfolio/actions'
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => {
dispatch(addCoin(coin))
}
}
});
class SearchCoin extends React.Component {
constructor(props) {
super(props)
this.state = {
searched: []
};
// console.log('props', props);
this.close = this.close.bind(this);
}
componentDidMount() {
this.coinInput.focus();
this.handleChange = this.handleChange.bind(this);
this.clickCoin = this.clickCoin.bind(this);
}
handleChange() {
const text = document.getElementById('coin-search').value;
const search = (text) => this.setState({ searched: findCoins(text) });
const clearSearch = () => this.setState({ searched: [] });
text.length > 1 ? search(text) : clearSearch();
}
clickCoin(coin) {
console.log('clickCoin', coin);
// api.getCoin(coin.id).then((res) => {
// const apiCoin = R.head(res.data);
// addToPortfolio(apiCoin);
// });
this.props.selectCoin(coin);
this.props.closeSearch();
}
close() {
this.props.closeSearch();
}
render() {
const searched = this.state.searched.map((coin) => {
return (
<li key={ coin.id } onClick={ ()=> this.clickCoin(coin) }>
<div className="coin-logo">
<img src={ coin.logo }/>
</div>
<span>{ coin.name }</span>
</li>
);
});
return (
<div id="search-coin-component">
<input type="text"
id="coin-search"
className="coin-search-input fl"
placeholder="Search"
onChange={ ()=> this.handleChange() }
ref={ (input) => { this.coinInput = input; } } />
<div className="icon-cancel-outline fl" onClick={ this.close }></div>
<div className="coin-select">
<ul>
{ searched }
</ul>
</div>
</div>
)
}
}
export default connect(null, mapDispatchToProps)(SearchCoin)
This is the onClick:
<li key={ coin.id } onClick={ ()=> this.clickCoin(coin) }>
At the bottom of the file I am using connect to add mapDispatchToProps
export default connect(null, mapDispatchToProps)(SearchCoin)
Here is the class method clickCoin which calls this.props.selectCoin
clickCoin(coin) {
console.log('clickCoin', coin);
this.props.selectCoin(coin);
this.props.closeSearch();
}
Finally selectCoin
import { addCoin } from '../../reducer/portfolio/actions'
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => {
dispatch(addCoin(coin))
}
}
});
However when I click the button it seems like the dispatch is not fired as nothing happens to the redux state.
import * as R from 'ramda'
import * as api from '../../services/api'
import { addToPortfolio } from '../../services/coinFactory'
export const ADD_COIN = 'ADD_COIN'
export function addCoin(coin) {
console.log('addCoin', coin);
return dispatch =>
api.getCoin(coin)
.then((res) => addToPortfolio(R.head(res.data)))
.then((portfolio) => dispatch(add(portfolio)));
}
// action creator
export function add(portfolio) {
return {
type: ADD_COIN,
portfolio
}
}
The reducer
import { ADD_COIN } from './actions'
const initialState = [];
export default (state = initialState, action) => {
switch(action.type) {
case ADD_COIN:
return action.portfolio;
default:
return state;
}
}
the reducer/index.js
import { combineReducers } from 'redux'
import portfolio from './portfolio'
export default combineReducers({
portfolio
});
Apart from azium answer, you can use actions like this. It saves you some writing,
export default connect(null, {addCoin})(SearchCoin)
and you can use it like this,
clickCoin(coin) {
console.log('clickCoin', coin);
this.props.addCoin(coin);
this.props.closeSearch();
}
The problem is that you are wrapping your function with an extra function.
Change:
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => { <--- returning extra function
dispatch(addCoin(coin))
}
}
})
to:
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) { dispatch(addCoin(coin)) }
})

react redux UI not updating after store change

Im relatively new to React and Redux, and I created a simple ajax Email form for learning. The issue i'm having is that after form submission I set the store state back to Initialstate, which should clear all fields but it doesn't. I can see the store changes in redux logger, *see image attached but these changes are not showing on the ui. Is my store not mapping to state correctly? Or am I mutating state somewhere?
My reducer looks like the following:
export default function contactForm(state = initialState.formValues, action) {
switch (action.type) {
case types.FORM_RESET:
return initialState.formValues;
case types.FORM_SUBMIT_SUCCESS:
return Object.assign({}, action.message);
default:
return state;
}
}
Combine Reducers:
import { combineReducers } from 'redux';
import message from './formReducer';
import ajaxCallsInProgress from './ajaxStatusReducer';
const rootReducer = combineReducers({
message,
ajaxCallsInProgress
});
My initialstate looks like:
export default {
formValues: {
name: '', email: '', message: '',
},
ajaxCallsInProgress: 0,
};
My Actions Look like this:
export function messageSuccess(message) {
return { type: types.FORM_SUBMIT_SUCCESS, message };
}
export function resetForm() {
return { type: types.FORM_RESET };
}
export function saveMessage(message) {
return function (dispatch) {
dispatch(beginAjaxCall());
return messageApi.saveMessage(message)
.then(() => {
dispatch(messageSuccess(message));
dispatch(resetForm());
}).catch((error) => {
dispatch(ajaxCallError(error));
throw (error);
});
}
}
In the view I am mapping state to props via:
constructor(props, context) {
super(props, context);
this.state = {
message: Object.assign({}, this.props.message),
}
}
render() {
return (
<ContactForm
onChange={this.updateMessageState}
onSubmit={this.submitForm}
message={this.state.message}
/>
);
}
function mapStateToProps(state) {
return {
message: state.message,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(formActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ContactSection);
Log showing store changes
I would be very grateful to any advice.
I've updated my answer with the code that I think should work for your example. You were pretty close, however based on your comments on trying to combine two reducers, I've created two reducers so you can see how it works.
/* constants.js */
export default {
FORM_RESET: 'FORM_RESET',
FORM_SUBMIT: 'FORM_SUBMIT',
AJAX_REQUEST: 'AJAX_REQUEST'
};
/* form-values-reducer.js */
const initialState = {
name: '',
email: '',
message: ''
};
export default const formValuesReducer = (state = initialState, action) => {
switch (action.type) {
case Constants.FORM_SUBMIT:
return {
...state,
message: action.message
};
case Constants.FORM_RESET:
return {
..state,
name: '',
email: '',
message: ''
};
default:
return state;
}
};
/* ajax-request-reducer.js */
const initialState = {
ajaxRequestCount: 0
};
export default const ajaxRequestReducer = (state = initialState, action) => {
switch (action.type) {
case Constants.AJAX_REQUEST:
return {
...state,
ajaxRequestCount: state.ajaxRequestCount + 1
};
default:
return state;
}
};
/* action-creators.js */
export const resettedForm = () => {
return {
type: Constants.FORM_RESET
}
};
export const submittedForm = (message) => {
return {
type: Constants.FORM_SUBMIT,
message
}
};
export const ajaxRequested = () => {
return {
type: Constants.AJAX_REQUEST
}
};
/* actions */
export const resetForm = (dispatch) => {
return () => {
dispatch(resettedForm());
}
};
export const submitForm = (dispatch) => {
return (message) => {
dispatch(ajaxRequested());
dispatch(submittedForm(message));
}
};
/* reducers.js */
import { combineReducers } from 'redux';
import ajaxRequest from './ajax-request-reducer';
import formValues from './form-values-reducer';
export default combineReducers({
ajaxRequest,
formValues
});
/* Component */
import React from 'react';
import { connect } from 'react-redux';
import { resetForm, submitForm } from './actions';
const App = (props) => (
<div>Your app UI stuff goes here</div>
);
const mapStateToProps = (state) => {
return {
name: state.formValues.name,
email: state.formValues.email,
message: state.formValues.message,
ajaxRequestCount: state.ajaxRequest.ajaxRequestCount
};
};
const mapDispatchToProps = (dispatch) => {
return {
resetForm: resetForm(dispatch),
submitForm: submitForm(dispatch)
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
I've not run this through anything, so there may be some mistakes in the code here and there.
I added the following which updated the state. I'm not sure if this is best practise with Redux, but it worked
componentWillReceiveProps(nextProps) {
this.setState({ message: nextProps.mail });
}

Categories

Resources