Commit 8554022b authored by Pınar Adıgüzel's avatar Pınar Adıgüzel
Browse files

State added

parent 542094ec
No related merge requests found
Showing with 17474 additions and 0 deletions
+17474 -0
.project 0 → 100644
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>reactjs-state</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>
# State
Props and state are both plain JavaScript objects. While both hold information that influences the output of render, they are different in one important way: props get passed to the component whereas state is managed within the component. So, state values are held and used only within the component. In order to change the state value and update the component, `setState` method should be called. `setState()` schedules an update to a component’s state object. When state changes, the component responds by re-rendering.
### Adding state to a component
Define the initial state within the `App` component's constructor:
```
constructor(props) {
super(props);
this.state = {
productList: data.productList
}
}
```
The `App` component now can access this data through its state, and send it to the `Table` component.
```
<Table data={this.state.productList} />
```
### Handling state changes
In this part, deleting an item from the list and updating the UI through state will be covered.
* In `TableBody.js`, add a button for deleting an item to each table row. This button basically calls the prop `onDelete` method with tihe right index parameter when clicked.
```
<th>
<button className="btn-icon" onClick={() => this.props.onDelete(index)}>
<i className="material-icons">delete</i>
</button>
</th>
```
&nbsp;&nbsp;&nbsp;&nbsp;*Note:* For a better visual representation, [Google Material Icons](https://material.io/tools/icons/) have been used in buttons. It can be includad with one line in the `index.html`:
`<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">`
* Table component will directly pass `onDelete` method to `TableBody`. Note how it is retrieved from props and passed to the component in the `index.js` of `Table` component:
```
export default class Table extends React.Component {
render() {
const { data, onDelete } = this.props;
return (
<table>
<TableHeader />
<TableBody list={data} onDelete={onDelete}/>
</table>
);
}
}
```
* The next step is createing the actual `onDelete` method in the `App.js` and handling state change within this method. The following method basically uses [filter](https://developer.mozilla.org/tr/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) method to eliminate the item to be deleted. The filtered new list is assigned to the state via `setState()` method. As mentioned earlier, setState will re-render the component, thus updating the view with the new list.
```
onDelete(index) {
const { productList } = this.state;
this.setState({ productList: productList.filter((product, i)=> {
return i !== index;
}) });
}
```
* An important point here is that the method shoud be bound to the component instance, in order to use `this` keyword correctly. It's done via using `bind` on the method within the constructor:
```
constructor(props) {
super(props);
this.state = {
productList: data.productList
}
this.onDelete = this.onDelete.bind(this);
}
```
### Adding more state changes
In the same way deleting an item is implemented, adding an item will be handled too. For this, a `Form` component is implemented which also has a state inside:
```
import React from 'react';
import './style.scss';
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
formOpen: false,
img: '',
name: '',
price: '',
description: ''
};
this.onClick = this.onClick.bind(this);
}
onClick() {
this.setState({ formOpen: !this.state.formOpen });
const { img, name, price, description } = this.state;
const product = { img, name, price, description };
this.props.onSubmit(product);
}
render() {
const button = <button className="fab" onClick={() => { this.setState({ formOpen: !this.state.formOpen }) }}>
<i className="material-icons">add_circle</i></button>;
const form = (
<div className="form-container">
<label>Image</label><input value={this.state.img} onChange={e => this.setState({ img: e.target.value })} />
<label>Name</label><input value={this.state.name} onChange={e => this.setState({ name: e.target.value })} />
<label>Price</label><input value={this.state.price} onChange={e => this.setState({ price: e.target.value })} />
<label>Description</label><input value={this.state.decription} onChange={e => this.setState({ description: e.target.value })} />
<button onClick={this.onClick}>Add</button></div>
);
return this.state.formOpen ? form : button;
}
}
```
* `Form` component has a state value `formOpen`, which is initially false. This value controls whether the modal will be opened or hidden. When the user clicks add icon, `setState` is called and formOpen is inverted, so that it becomes true, resulting the modal to open, and vice versa. The following line renders the modal or the button depending on the `formOpen` value:
`return this.state.formOpen ? form : button;`
* Input changes are handled via state. The initial value to be shown in an `input` is sent via value prop. At each change in the input, `onChange` prop is invoked. Thus, `setState` should be called in `onChange`, setting the new text to the state. Examine how the inputs are bound to the state in the following line:
`<input value={this.state.img} onChange={e => this.setState({ img: e.target.value })} />`
* When the user enters sufficient information, `this.props.onSubmit` method will be invoked with the product parameter that is composed with the values in the state as follows:
```
onClick() {
this.setState({ formOpen: !this.state.formOpen });
const { img, name, price, description } = this.state;
const product = { img, name, price, description };
this.props.onSubmit(product);
}
```
* The next step is to add the `Form` component to `App.js`
```
<div className="App">
<Heading title="Products" />
<Table data={this.state.productList} onDelete={this.onDelete} />
<Form onSubmit={this.onAdd}/>
</div>
```
* `onSubmit` will call `onAdd` method that is defined in `App.js`. This method basially gets the productList, pushes the new product and calls `setState`
```
onAdd(product) {
let { productList } = this.state;
productList.push(product);
this.setState({ productList });
}
```
* Also, don't forget to bind the method in the constructor, since it's using `this`
`this.onAdd = this.onAdd.bind(this);`
\ No newline at end of file
This diff is collapsed.
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"node-sass": "^4.11.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "2.1.8"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
setup/public/favicon.ico

3.78 KB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
import React, { Component } from 'react';
import Heading from './components/Heading'
import Table from './components/Table'
import * as data from './data.json';
import './app.scss';
class App extends Component {
render() {
return (
<div className="App">
<Heading title="Products"/>
<Table data={data.productList}/>
</div>
);
}
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
@import './theme/colors.scss';
h1 {
font-family: 'Open Sans', sans-serif;
font-weight: 300;
color: $primary-dark;
};
.App {
padding: 10px;
}
.text-center {
text-align: center;
}
\ No newline at end of file
import React from 'react';
const Heading = (props) => {
return <h1 className="text-center">{props.title}</h1>;
}
export default Heading;
\ No newline at end of file
import React from 'react';
import Thumbnail from '../Thumbnail';
import images from '../../images/index';
export default class TableBody extends React.Component {
render() {
const { list } = this.props;
return (
<tbody>
{list.map((item, index) => {
return (<tr key={index}>
<th><Thumbnail src={images[item.img]} alt={item.alt} /></th>
<th>{item.name}</th>
<th>{item.price}</th>
<th>{item.description}</th>
</tr>);
})}
</tbody>);
}
}
\ No newline at end of file
import React from 'react';
export default class TableHeader extends React.Component {
render() {
return (
<thead>
<tr>
<th>Image</th>
<th>Name</th>
<th>Price</th>
<th>Description</th>
</tr>
</thead>);
}
}
\ No newline at end of file
import React from 'react';
import TableHeader from './TableHeader';
import TableBody from './TableBody';
import './style.scss'
export default class Table extends React.Component {
render() {
const { data } = this.props;
return (
<table>
<TableHeader />
<TableBody list={data}/>
</table>
);
}
}
\ No newline at end of file
@import '../../theme/colors.scss';
th {
font-family: 'Open Sans', sans-serif;
color: $primary-dark;
padding: 10px;
}
thead th {
font-weight: 600;
}
tbody th {
font-weight: 300;
}
table {
border: 1px solid $primary-light;
border-radius: 2px;
border-spacing: 0
}
tr {
background-color: $white;
}
tbody tr:nth-child(odd){
background-color: $light-gray;
}
import React from 'react';
const Thumbnail = (props) => {
return <img src={props.src} alt={props.alt} width="100" height="100"/>
}
export default Thumbnail;
\ No newline at end of file
{
"productList": [
{
"img": "tablet",
"alt": "img-tablet",
"name": "10-Inch Tablet",
"price": "269",
"description": "Android 4.3 Jelly Bean, 10.1-inch Full HD (1920 x 1200) Display"
},
{
"img": "shoe",
"alt": "img-shoe",
"name": "Running Shoe",
"price": "48",
"description": "Synthetic and Mesh, Imported, Rubber sole, Flex Film welded upper, HydraMAX moisture-wicking collar lining"
},
{
"img": "watch",
"alt": "img-watch",
"name": "Slim Bracelet Watch",
"price": "27",
"description": "A narrow gold-tone bracelet supports the round case of this watch, which features three rhinestones marking each hour and a sparkling halo on the bezel"
}
]
}
\ No newline at end of file
const tablet = require('./tablet.jpg');
const shoe = require('./shoe.jpg');
const watch = require('./watch.jpg');
const images = {
tablet, shoe, watch
};
export default images;
\ No newline at end of file
setup/src/images/shoe.jpg

3.55 KB

Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment