Skip to content

Commit 0c1c40f

Browse files
author
rizovs
committed
initial commit
1 parent fe5ef6b commit 0c1c40f

File tree

19 files changed

+882
-307
lines changed

19 files changed

+882
-307
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@
2121
npm-debug.log*
2222
yarn-debug.log*
2323
yarn-error.log*
24+
25+
/.idea
26+
/ignored

README.md

Lines changed: 0 additions & 44 deletions
This file was deleted.

package.json

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"react": "^16.6.0",
7-
"react-dom": "^16.6.0",
8-
"react-scripts": "2.1.1"
6+
"foobar-ipsum": "^1.0.3",
7+
"immer": "^1.7.4",
8+
"node-sass": "^4.9.4",
9+
"normalize.css": "^8.0.0",
10+
"react": "16.7.0-alpha.0",
11+
"react-dom": "16.7.0-alpha.0",
12+
"react-router-dom": "^4.3.1",
13+
"react-scripts": "2.1.1",
14+
"scroll-into-view-if-needed": "^2.2.20",
15+
"simple-react-router": "^0.0.17",
16+
"yup": "^0.26.6"
917
},
1018
"scripts": {
1119
"start": "react-scripts start",
@@ -14,12 +22,25 @@
1422
"eject": "react-scripts eject"
1523
},
1624
"eslintConfig": {
17-
"extends": "react-app"
25+
"extends": "react-app",
26+
"plugins": [
27+
"react-hooks"
28+
],
29+
"rules": {
30+
"react-hooks/rules-of-hooks": "error"
31+
}
1832
},
1933
"browserslist": [
2034
">0.2%",
2135
"not dead",
2236
"not ie <= 11",
2337
"not op_mini all"
24-
]
38+
],
39+
"devDependencies": {
40+
"eslint-plugin-react-hooks": "^0.0.0"
41+
},
42+
"now": {
43+
"alias": "awesome-hooks.now-sh",
44+
"name": "awesome-hooks"
45+
}
2546
}

src/App.css

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/App.js

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,55 @@
1-
import React, { Component } from 'react';
2-
import logo from './logo.svg';
3-
import './App.css';
1+
import React from 'react';
2+
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
43

5-
class App extends Component {
6-
render() {
7-
return (
8-
<div className="App">
9-
<header className="App-header">
10-
<img src={logo} className="App-logo" alt="logo" />
11-
<p>
12-
Edit <code>src/App.js</code> and save to reload.
13-
</p>
14-
<a
15-
className="App-link"
16-
href="https://reactjs.org"
17-
target="_blank"
18-
rel="noopener noreferrer"
19-
>
20-
Learn React
21-
</a>
22-
</header>
4+
import AccordionScreen from './components/accordion';
5+
import Screen from './components/form-library';
6+
import WindowWidthScreen from './components/window-width';
7+
import TodoList from "./components/todolist";
8+
9+
function App() {
10+
return (
11+
<Router>
12+
<div className="main">
13+
<nav className="sidebar">
14+
<div className="item">
15+
<Link className="link" to="/accordion">
16+
Accordion
17+
</Link>
18+
<span>
19+
Panels scroll into view if not fully visible when toggled.
20+
Using <pre>useImperativeMethods</pre> and <pre>useRef</pre>.
21+
</span>
22+
</div>
23+
<div className="item">
24+
<Link className="link" to="/form-library">
25+
Extremely basic form validation library
26+
</Link>
27+
<span>Pass state deeper using context, then read it using <pre>useContext</pre>. Heavily inspired by <pre>formik</pre>.</span>
28+
</div>
29+
<div className="item">
30+
<Link className="link" to="/window-width">
31+
Window width
32+
</Link>
33+
<span>Multiple <pre>useEffects</pre> are allowed.</span>
34+
</div>
35+
<div className="item">
36+
<Link className="link" to="/todo-list">
37+
Todo-list
38+
</Link>
39+
<span>Look mum, <pre>useReducer</pre> is almost Redux!</span>
40+
</div>
41+
</nav>
42+
43+
<div className="content">
44+
<Route path="/" exact component={() => <div>use navigation on the left</div>}/>
45+
<Route path="/accordion" component={AccordionScreen}/>
46+
<Route path="/form-library" component={Screen}/>
47+
<Route path="/window-width" component={WindowWidthScreen}/>
48+
<Route path="/todo-list" component={TodoList}/>
49+
</div>
2350
</div>
24-
);
25-
}
51+
</Router>
52+
)
2653
}
2754

28-
export default App;
55+
export default App;

src/App.test.js

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React, { useRef, createRef, useImperativeMethods, useState, useEffect } from 'react';
2+
import scrollIntoView from 'scroll-into-view-if-needed';
3+
import FoobarIpsum from 'foobar-ipsum';
4+
5+
function useAccordion(panelsCount) {
6+
const [currentIndex, setCurrentIndex] = useState();
7+
const [refs, setRefs] = useState();
8+
9+
// This part is smelly
10+
// https://github.com/facebook/react/issues/14072
11+
// TODO rewrite
12+
useEffect(() => {
13+
let refs = {};
14+
for (let i = 0; i <= panelsCount; i++) {
15+
refs[i] = createRef();
16+
}
17+
setRefs(refs);
18+
}, []);
19+
20+
useEffect(() => {
21+
// Scroll current accordion panel into view
22+
if (currentIndex !== undefined) {
23+
refs[currentIndex].current.scrollIntoView();
24+
}
25+
}, [currentIndex]);
26+
27+
function setCurrent(newIndex) {
28+
setCurrentIndex(currentIndex === newIndex ? undefined : newIndex);
29+
}
30+
31+
return [currentIndex, setCurrent, refs];
32+
}
33+
34+
const AccordionPanel = React.forwardRef((props, ref) => {
35+
const containerRef = useRef();
36+
const textRef = useRef();
37+
38+
useImperativeMethods(ref, () => ({
39+
scrollIntoView: () => {
40+
scrollIntoView(containerRef.current, { block: 'nearest', scrollMode: 'if-needed' });
41+
}
42+
}));
43+
44+
return <div onClick={props.onClick} ref={containerRef}>
45+
<div className="accordion-label">{props.label}</div>
46+
{props.isOpen &&
47+
<div>{generateRandomText()}</div>}
48+
</div>;
49+
});
50+
51+
function Accordion(props) {
52+
return <div>{props.children}</div>;
53+
}
54+
55+
function generateRandomNumber(max) {
56+
return Math.floor(Math.random() * Math.floor(max));
57+
}
58+
59+
function generateRandomText() {
60+
return new FoobarIpsum({
61+
size: {
62+
sentence: generateRandomNumber(100),
63+
paragraph: generateRandomNumber(10)
64+
}
65+
}).paragraph();
66+
}
67+
68+
export {
69+
useAccordion,
70+
Accordion,
71+
AccordionPanel
72+
};

src/components/accordion/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
3+
import { Accordion, AccordionPanel, useAccordion } from './Accordion';
4+
5+
function AccordionScreen() {
6+
const panels = [...Array(100).keys()].map(e => `Panel number ${e}`);
7+
8+
const [currentIndex, setCurrent, refs] = useAccordion(panels.length);
9+
10+
return <Accordion>
11+
{panels.map((panel, index) => (
12+
<AccordionPanel
13+
ref={refs && refs[index]}
14+
key={index}
15+
label={panel}
16+
isOpen={currentIndex === index}
17+
onClick={() => setCurrent(index)}
18+
/>
19+
))}
20+
</Accordion>;
21+
}
22+
23+
export default AccordionScreen;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, { useState, useEffect, createContext } from "react";
2+
3+
const FormContext = createContext(null);
4+
5+
function MyFormLibrary({ children, initialValues, onValuesChanged, onSubmit, validate }) {
6+
const [values, updateValues] = useState(initialValues);
7+
const [errors, updateErrors] = useState({});
8+
9+
useEffect(() => {
10+
if (typeof onValuesChanged === 'function') {
11+
onValuesChanged(values);
12+
}
13+
}, [values]);
14+
15+
function handleChange(e) {
16+
updateValues({
17+
...values,
18+
[e.target.name]: e.target.value
19+
});
20+
updateErrors({
21+
...errors,
22+
[e.target.name]: undefined
23+
});
24+
}
25+
26+
async function submitForm() {
27+
try {
28+
validate && validate(values);
29+
await onSubmit(values);
30+
} catch (e) {
31+
updateErrors(convertErrors(e));
32+
}
33+
}
34+
35+
function handleSubmit(e) {
36+
e.preventDefault();
37+
submitForm();
38+
}
39+
40+
const ctx = {
41+
values,
42+
errors,
43+
handleChange,
44+
handleSubmit
45+
};
46+
47+
return <FormContext.Provider value={ctx}>
48+
{children}
49+
</FormContext.Provider>;
50+
}
51+
52+
function convertErrors(yupError) {
53+
return yupError.inner
54+
.reduce((acc, cur) => {
55+
acc[cur.path] = cur.message;
56+
return acc;
57+
}, {});
58+
}
59+
60+
export default MyFormLibrary;
61+
export { FormContext };

0 commit comments

Comments
 (0)