Updating object's state in redux - reactjs

What is the best way to update an objects state in redux using one function for multiple inputs?
I have 3 inputs - if possible, I'd like to update corresponding input field to redux object field using one function... or best practice.
// Contact.js
this.state = {
contactInfo: {
firstName: '',
address: '',
city: '',
}
}
onChangeInfo = (event, action) => {
const { dispatch } = this.props;
const { contactInfo } = this.state;
// Is this an issue?
contactInfo[event.target.name] = event.target.value;
if (action === 'CHANGE') {
dispatch(updateContactInfo(contactInfo));
this.setState({ contactInfo });
} else {
this.setState({ contactInfo });
}
}
render() {
const { firstName, address, city } = this.state.contactInfo;
return (
<div>
<div>
<input placeholder=" " type="text" name='firstName' value={firstName} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
<div className="placeholder"><span>First & last name</span></div>
</div>
<div>
<input placeholder=" " type="text" name="address" value={address} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
<div className="placeholder"><span>Address</span></div>
</div>
<div>
<input placeholder=" " type="text" name="city" value={city} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
<div className="placeholder"><span>City</span></div>
</div>
</div>
)}
// Reducer
const initialState = {
contactInformation: [],
}
export default function (state, action) {
state = state === undefined ? initialState : state;
switch (action.type) {
case 'CONTACT_INFO': {
state.contactInformation.test.info = payload;
return Object.assign({}, state, action.payload);
}
default: return state;
}
}

I don't see the point of using setState in this case
this.state = {
contactInfo: {...this.props}
}
onChangeInfo = ({target: {name, value}}, action) => {
const { dispatch } = this.props;
const contactInfo = {[name]: value};
if (action === 'CHANGE') {
dispatch(updateContactInfo(contactInfo));
}
}
Example
const { Component, useState, useEffect } = React;
const { bindActionCreators, combineReducers, createStore, applyMiddleware, compose } = Redux;
const { connect, Provider } = ReactRedux;
const initalState = {
contactInfo: {
firstName: '',
address: '',
city: ''
}
}
const reducer = (state = initalState, action) => {
switch (action.type) {
case 'CONTACT_INFO': {
const newState = {
...state,
contactInfo: {
...state.contactInfo,
...action.payload.contactInfo
}
};
return newState;
}
default: return state;
}
}
const reducers = combineReducers({
reducer
})
const store = createStore(
reducers
);
const updateContactInfo = (payload) => ({
type: 'CONTACT_INFO', payload
})
const mapStateToProps = state => {
return {
contactInfo: state.reducer.contactInfo
}
}
const mapDispatchToProps = dispatch => ({
updateContactInfo: payload => dispatch(updateContactInfo(payload))
})
class _App extends Component {
constructor(props) {
super(props);
this.state = {...this.props}
this.updateContactInfo = this.props.updateContactInfo;
}
static getDerivedStateFromProps (props, state) {
return {...props}
}
onChangeInfo ({target: {name, value}}, action) {
const contactInfo = { contactInfo: {[name]: value}};
if (action === 'CHANGE') {
this.updateContactInfo(contactInfo);
}
}
render() {
const { firstName, address, city } = this.state.contactInfo;
return <div>
<div>
<input placeholder=" " type="text" name='firstName' value={firstName} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
<div className="placeholder"><span>First & last name</span></div>
</div>
<div>
<input placeholder=" " type="text" name="address" value={address} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
<div className="placeholder"><span>Address</span></div>
</div>
<div>
<input placeholder=" " type="text" name="city" value={city} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
<div className="placeholder"><span>City</span></div>
</div>
{JSON.stringify(this.state)}
</div>
}
}
const App = connect(mapStateToProps, mapDispatchToProps)(_App)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.10.1/polyfill.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/redux#4.0.5/dist/redux.js"></script>
<script src="https://unpkg.com/react-redux#latest/dist/react-redux.js"></script>
<div id="root"></div>

I would suggest you to maintain three different actions as best practise
as an action is always a pure function and specific to one particular operation.
export const firstName = (firstName) => ({
type : 'FIRST_NAME',
payload : firstName
});
export const address = (address) => ({
type : 'ADDRESS',
payload : address
});
export const city = (city) => ({
type : 'CITY',
payload : city
});
And, in the reducer, update the store based on action.type

Great question.
There are a few things I have to point out before I give you an answer. There is generally 2 types of state that exists in any application: long lived state and short lived (a.k.a ephemeral) state. Redux is meant to serve you as a container to place all your long term states that is of general concern to potentially different parts of your application.
With that said, I can see that the only thing you are doing in your app is updating the state with the user input. I bet you then use that state to do something when the user clicks a submit button. If that is your case, the inputs by definition are ephemeral and I would NOT place the input states in Redux at all. Instead, I'd only fire 1 action when the user submits the form.
<form onSubmit={onSubmitHandler}>
<input name="name" type="text" />
<input name="hobby" type="text" />
<button type="submit" />
<form />
------
// this is slight pseudo-code, but hopefully you get the gist
const onSubmitHandler = (event) => {
const myFields = // get fields from event object.
dispatch({type: 'SOME-ACTION', fields: myFields})
}
I'd also advise you to consider changing how you are generally modelling your actions. You can watch this video that goes over what I mean.

Related

An object from arrays of objects - React - useState

help please I want to add value to state without overwriting. Currently, when adding a value, the array is overwritten. I want to use useState and I want to use the value from the form.
import {useState} from 'react';
const initialState = {
people: [
{email: 'Jan'},
{email: 'Izabela'},
{email: 'Michael'}
] }
const StateModification = () => {
const [names,setNames] = useState(initialState)
const handleSubmit = (e) => {
e.preventDefault();
}
const handleChange2 = (e) => {
setNames({
...names,
people: [{[e.target.name]: e.target.value}]
})
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>E-mail</label>
<input
id='email'
type='text'
name='email'
value={names.email}
onChange={handleChange2}
/>
<button type='submit'>Add</button>
</form>
</div>`enter code here`
) }
export default StateModification;
I think you need to add an email in your data and after click on add button that email will store in people variable with your previous data so i have update your code and it should work for you.
import {useState} from 'react';
const initialState = {
people: [
{email: 'Jan'},
{email: 'Izabela'},
{email: 'Michael'}
] }
const StateModification = () => {
const [names,setNames] = useState(initialState)
const [email,setEmail] = useState("")
const handleSubmit = (e) => {
e.preventDefault();
setNames({
people: [...names.people, { email }]
})
}
const handleChange2 = (e) => {
e.preventDefault();
setEmail(e.target.value)
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>E-mail</label>
<input
id='email'
type='text'
name='email'
value={email}
onChange={handleChange2}
/>
<button type='submit'>Add</button>
</form>
</div>
) }
export default StateModification;

Child component is not updating parent's state in React.js functional component

I have a component that renders a child component. The child component manages its own state, and I am trying to pass the updated state to the parent, using a callback, to no success. I have tried for hours to identify the problem but haven't been able to figure it out. My parent component is this (without imports):
const formReducer = (state, action) => {
switch (action.type) {
case 'SET_CATEGORY':
return {
category: action.category
};
default:
return state;
}
};
function LogService() {
const initialFormState =
{
category: ''
};
const [formState, dispatch] = useReducer(formReducer, initialFormState);
const getCategoryState = useCallback(category => {
dispatch({
action: 'SET_CATEGORY',
category: category
}, []);
}
);
const submitHandler = event => {
event.preventDefault();
console.log(formState);
};
return (
<>
<form onSubmit={submitHandler}>
<CategoryOne sendState={getCategoryState} />
<button>send</button>
</form>
</>
);
};
export default LogService;
And this is my child component:
const selectionReducer = (state, action) => {
switch (action.type) {
case 'DISPLAY_SELECTION':
return {
...state,
selectionIsVisible: true
};
case 'LOG_SELECTION':
return {
...state,
category: action.category,
selectionIsVisible: false
}
default:
return state;
}
};
function CategoryOne(props) {
const [selectionState, dispatch] = useReducer(selectionReducer, {});
const { category } = selectionState;
const { sendState } = props;
useEffect(() => {
sendState(category);
}, [category, sendState])
const displaySelectionHandler = event => {
dispatch({
type: 'DISPLAY_SELECTION'
});
};
const selectCategoryHandler = event => {
dispatch({
type: 'LOG_SELECTION',
category: event.target.id
});
};
return (
<>
<div className="container">
<div className={`options-container ${selectionState.selectionIsVisible && 'active'}`}>
<div className="option">
<input className="radio" type="radio" id="One" name="category" onChange={selectCategoryHandler} />
</div>
<div className="option">
<input className="radio" type="radio" id="Two" name="category" onChange={selectCategoryHandler} />
</div>
<div className="option">
<input className="radio" type="radio" id="Three" name="category" onChange={selectCategoryHandler} />
</div>
<div className="option">
<input className="radio" type="radio" id="Four" name="category" onChange={selectCategoryHandler} />
</div>
</div>
<div className="selected" id="category" onClick={displaySelectionHandler}>
Category
</div>
</div>
</>
);
};
export default CategoryOne;
I am not getting Errors, just a Warning:
React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?
I don't understand that warning, I do have an array of dependencies. other than that, selectionIsVisible: true (in the 'DISPLAY_SELECTION' action) works, but selectionIsVisible: false (in the 'LOG_SELECTION' action).
Could someone please help me out? I am very frustrated.
Because use put [] wrong place. Just update like this:
const getCategoryState = useCallback((category) => {
dispatch({
action: "SET_CATEGORY",
category: category,
});
}, []);

Input tag: nothing typed is appearing

My first React session data storage, so thanks. I am trying to set up inputing data and then placing it in session storage, so it can be edited, viewed or deleted later. There are 2 pieces of data, "title" and "note" to be inputed into a form. Nothing happens when I type into the form inputs. Any other help welcome also.
class AddNote extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
content: ''
}
}
componentDidMount() {
this.getFormData();
}
//let notes = getSessionItem(keys.notes);
//if (!notes) { notes = ""; }
onTitleChange(event) {
this.setState({ title: event.target.value }, this.storeFormData);
this.storeFormData();
}
onContentChange(event) {
this.setState({ content: event.target.value }, this.storeFormData);
}
storeFormData() {
const form = {
title: this.state.title,
content: this.state.content
}
setSessionItem(keys.user_form, form);
}
getFormData() {
const form = getSessionItem(keys.user_form);
if (form) {
this.setState({
title: form.name,
content: form.content
});
}
}
render() {
return (
<div>
<div>
<h2>ADD NOTE PAGE</h2>
</div>
<form classname="nav1">
<div>
<label><b>Title</b></label>
<input type="text"
value={this.state.title}
onchange={this.onTitleChange.bind(this)}
/>
</div>
<div>
<label><b>Content</b></label>
<input type="text"
value={this.state.content}
onchange={this.onContentChange.bind(this)}
/>
</div>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default AddNote;
and the storage file:
export const keys = {
title: 'title',
notes: 'notes'
}
export const getSessionItem = function (key) {
let item = sessionStorage.getItem(key);
item = JSON.parse(item);
return item;
}
export const setSessionItem = function (key, value) {
value = JSON.stringify(value);
sessionStorage.setItem(key, value);
}
export const removeSessionItem = function (key) {
sessionStorage.removeItem(key);
}
No need to have 2 change handler for your input. You can do it using a common change handler.
<form classname="nav1">
<div>
<label><b>Title</b></label>
<input type="text"
value={this.state.title}
name="title" <---- Provide name here
onChange={this.onChange}
/>
</div>
<div>
<label><b>Content</b></label>
<input type="text"
value={this.state.content}
name="content" <---- Provide name here
onChange={this.onChange}
/>
</div>
<button type="submit">Submit</button>
</form>
Your onChange function should be, and use callback in setState to call your storeFormData function.
onChange = (e) => {
this.setState({
[e.target.name] : e.target.value
}, () => this.storeFormData())
}
Note: In React we use camelCase, for example, onchange should be onChange and classname should be className.
Also make sure you bind this to storeFormData and getFormData functions, or you can use arrow function's to automatically bind this.
Demo

ReactJS + Redux Edit form

I have the following form:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { updateExpert, fetchExpert } from "../../store/actions/expertActions";
class ExpertForm extends Component {
state = {
expert: {}
};
componentWillMount() {
console.log("ComponentWillMount");
const id = this.props.match.params.id;
console.log("Will fetch expert with id", id);
this.props.fetchExpert(id);
}
handleChange = e => {
console.log(e);
this.setState({
expert: {
...this.state.expert,
[e.target.id]: e.target.value
}
});
};
componentWillReceiveProps(nextProps) {
const newExpert = nextProps.expert;
console.log("got new expert ", newExpert);
this.setState({
expert: nextProps.expert
});
}
handleSubmit = e => {
e.preventDefault();
const originalExpert = this.props.expert;
console.log("Expert before", originalExpert);
// const updatedExpert = {
// firstName: this.state.expert.firstName,
// lastName: this.state.expert.lastName,
// bio: this.state.expert.bio,
// country: originalExpert.country,
// interestIds: originalExpert.interestIds,
// city: originalExpert.city,
// summary: originalExpert.summary,
// websiteText: originalExpert.websiteText,
// websiteUrl: originalExpert.websiteUrl
// };
const updatedExpert = this.state.expert;
console.log("Expert after", updatedExpert);
//call action
this.props.updateExpert(originalExpert.userId, updatedExpert);
};
render() {
const { expert } = this.props;
return (
<div className="container">
<div className="card">
<form onSubmit={this.handleSubmit} className="white">
<div className="card-content">
<h5 className="grey-text text-darken-3">Update expert</h5>
<div className="row">
<div className="input-field col s6">
<label htmlFor="firstName">First Name</label>
<input
onChange={this.handleChange}
type="text"
id="firstName"
/>
</div>
<div className="input-field col s6">
<label htmlFor="lastName">Last Name</label>
<input
onChange={this.handleChange}
type="text"
id="lastName"
/>
</div>
</div>
<div className="input-field">
<label htmlFor="bio">Bio</label>
<textarea
className="materialize-textarea"
id="bio"
onChange={this.handleChange}
/>
</div>
<div className="input-field">
<label htmlFor="summary">Summary</label>
<textarea
className="materialize-textarea"
id="summary"
onChange={this.handleChange}
/>
</div>
<div className="row">
<div className="input-field col s6">
<label htmlFor="country">Country</label>
<textarea
className="materialize-textarea"
id="country"
onChange={this.handleChange}
/>
</div>
<div className="input-field col s6">
<label htmlFor="city">City</label>
<textarea
className="materialize-textarea"
id="city"
onChange={this.handleChange}
/>
</div>
</div>
<div className="row">
<div className="input-field col s6">
<label htmlFor="websiteText">Website text</label>
<textarea
className="materialize-textarea"
id="websiteText"
onChange={this.handleChange}
/>
</div>
<div className="input-field col s6">
<label htmlFor="websiteUrl">Website URL</label>
<textarea
className="materialize-textarea"
id="websiteUrl"
onChange={this.handleChange}
/>
</div>
</div>
</div>
<div className="card-action">
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">Update</button>
</div>
</div>
</form>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
expert: state.experts.item
});
const mapDispatchToProps = dispatch => {
return {
updateExpert: (id, expert) => dispatch(updateExpert(id, expert)),
fetchExpert: id => dispatch(fetchExpert(id))
};
};
export default connect(
mapStateToProps, //mapstatetoprops
mapDispatchToProps //mapdispatchtoprops
)(ExpertForm);
Now this form is used mostly to edit an item of the Expert type, not adding it. Which means I should prefill it with the information already stored in the database.
However when I try to set the value directly on an input like so:
<input
value={expert.firstName}
onChange={this.handleChange}
type="text"
id="firstName"
/>
I get the following error:
index.js:1452 Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
This is the ExpertList component from which the user accesses this ExpertForm:
import React, { Component } from "react";
import PropTypes from "prop-types";
import ExpertItem from "./expert-item";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { fetchExperts } from "../../store/actions/expertActions";
class ExpertList extends Component {
componentWillMount() {
console.log("ComponentWillMount");
this.props.fetchExperts();
}
componentWillReceiveProps(nextProps) {
console.log("Rceived new props");
}
render() {
const { experts } = this.props;
const expertsDom = experts.map(expert => (
<Link to={"/expert/edit/" + expert.userId}>
<ExpertItem key={expert.userId} expert={expert} />
</Link>
));
return <div className="expert-list section">{expertsDom}</div>;
}
}
const mapStateToProps = state => ({
experts: state.experts.items
});
export default connect(
mapStateToProps,
{ fetchExperts }
)(ExpertList);
These are my actions :
import {
FETCH_EXPERTS,
UPDATE_EXPERT,
ADD_EXPERT,
FETCH_EXPERT
} from "./types";
import axios from "../../network/axios";
export const createExpert = expert => {
return (dispatch, getState) => {
//make async call to database
dispatch({ type: ADD_EXPERT, expert: expert });
// type: ADD_EXPERT;
};
};
export const fetchExpert = id => {
return (dispatch, getState) => {
console.log("fetching expert with id ", id);
axios
.get("/connections/experts")
.then(response => {
const selectedExpert = response.data.filter(e => {
return e.userId === id;
})[0];
console.log("ExpertsData ", selectedExpert);
// const newState = Object.assign({}, this.state, {
// experts: newExperts
// });
dispatch({
type: FETCH_EXPERT,
payload: selectedExpert
});
})
.catch(error => {
console.log(error);
});
};
};
//Thunk allows us to call dispatch directly so that we can make async requests
//We can consider dispatch a resolver/promise, calling dispatch is just sending
//the data back
export const fetchExperts = () => {
return (dispatch, getState) => {
console.log("fetching");
console.log("getstate ", getState());
const accessToken = getState().auth.authInfo.accessToken;
console.log("authToken ", accessToken);
axios
.get("/connections/experts")
.then(response => {
const newExperts = response.data;
console.log("ExpertsData ", newExperts);
// const newState = Object.assign({}, this.state, {
// experts: newExperts
// });
dispatch({
type: FETCH_EXPERTS,
payload: newExperts
});
})
.catch(error => {
console.log(error);
});
};
};
export const updateExpert = (id, expertData) => {
return dispatch => {
console.log("updating expert", id, expertData);
axios
.put("/experts/" + id, expertData)
.then(response => {
const updatedExpert = response.data;
dispatch({
type: UPDATE_EXPERT,
payload: updatedExpert
});
})
.catch(error => {
console.log(error);
});
};
};
And this is my reducer:
import {
FETCH_EXPERTS,
UPDATE_EXPERT,
FETCH_EXPERT
} from "../../store/actions/types";
const initialState = {
items: [],
item: {}
};
const expertReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_EXPERT:
console.log("reducer fetch by id");
return {
...state,
item: action.payload
};
case FETCH_EXPERTS:
console.log("reducer fetch");
return {
...state,
items: action.payload
};
case UPDATE_EXPERT:
console.log("reducer update");
return {
...state,
item: action.payload
};
default:
return state;
}
};
export default expertReducer;
Instead of using value property, You need to use defaultValue as described here in Default Values section if You want to have a default value for input field.
The problem is that your value is undefined before Redux's state is loaded. You can solve this by giving it an empty string by default, something like this:
<input
value={typeof expert.firstName === 'undefined' ? '' : expert.firstName}
onChange={this.handleChange}
type="text"
id="firstName"
/>

unable to throw SubmissionError in redux form

I'm trying to make a SubmissionError show up in my redux-form form (if backend server response isn't status code 200), but I'm not sure how to probably due to my beginner level experience in React, Redux, React Redux, Redux Form, and Axios. I've already tried a lot of things (and currently reverted back from them) by looking through various SO posts and Github issues. If someone/people can help me out, that'd be greatly appreciated!
The current error I'm getting is this promise uncaught message:
error screenshot
Although there are more files in my project, here are the relevant ones here (minus the import statements):
ssoLoginPage.js
const renderField = (field) => {
const { input, label, name, type, meta: { touched, error } } = field;
const placeholder = `Enter ${label} here`;
return(
<div className="slds-form-element slds-m-bottom--large">
<label className="slds-form-element__label" htmlFor={label}>
{label}
</label>
<div className="slds-form-element__control">
<input
key={name}
id={name}
className="-input"
type={type}
placeholder={placeholder}
{...field.input}
/>
</div>
{touched && error && <span>{error}</span>}
</div>
);
};
class SsoLoginPage extends React.Component {
constructor(props) {
super(props);
}
onSubmit(values) {
this.props.ssoLogin(values, () => {
throw new SubmissionError({_error: "One or more credentials are incorrect"});
});
}
render() {
console.log("ssoLoginStatus:",this.props.ssoLoginStatus);
const { error, handleSubmit} = this.props;
return (
<IconSettings iconPath="/slds-static-2.6.1/assets/icons">
<div>
<Modal
title="SSO Login"
isOpen={!this.props.ssoLoginStatus}
onRequestClose={this.toggleOpen}
dismissOnClickOutside={false}
>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
name="username"
type="text"
component={renderField}
label="Username"
/>
<Field
name="password"
type="password"
component={renderField}
label="Password"
/>
<Field
name="verificationCode"
type="password"
component={renderField}
label="Verification Code"
/>
{error && <strong>{error}</strong>}
<Button type="submit" label="Login" variant="brand" />
</form>
</Modal>
</div>
</IconSettings>
);
}
}
function mapStateToProps({ssoLoginStatus}) {
return ssoLoginStatus;
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ssoLogin},dispatch);
}
export default reduxForm({
form: "ssoForm" // a unique identifier for this form
})(
connect(mapStateToProps,mapDispatchToProps)(SsoLoginPage)
);
settings.setAppElement("#root");
reducersIndex.js
const rootReducer = combineReducers({
ssoLoginStatus: ssoReducer,
form: formReducer
});
export default rootReducer;
ssoReducer.js
const ssoProcess = (state, action) => {
console.log("ssoLogin action:",action);
switch(action.payload.status) {
case 200:
//everything's okay
console.log("reaching 200 case?");
return {...state, "ssoLoginStatus": true};
default:
//not sure what to do here currently
return state;
}
};
export default function(
state={"ssoLoginStatus": false}, action) {
console.log("action type:",action.type);
switch (action.type) {
case SSO_LOGIN:
console.log("return something here hopefully");
return ssoProcess(state, action);
default:
return state;
}
}
actionsIndex.js
export const SSO_LOGIN = "SSO_LOGIN";
export const BASE_URL = "http://localhost:8080";
export function ssoLogin (values, callback) {
console.log("went inside ssologin action creator!",
values.username,
values.password,
values.verificationCode);
const url = `${BASE_URL}/ssologin?username=${values.username}&password=${values.password}&verificationCode=${values.verificationCode}`;
console.log("url w/ params:",url);
const request = axios
.post(url,{responseType: "json"})
.then(response => {
console.log("axios response: ",response);
return response;
})
.catch(error => {
console.log("sso error: ",error);
return callback();
});
return {
type: SSO_LOGIN,
payload: request
};
}

Resources