Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rizovs committed Nov 3, 2018
1 parent fe5ef6b commit 0c1c40f
Show file tree
Hide file tree
Showing 19 changed files with 882 additions and 307 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

/.idea
/ignored
44 changes: 0 additions & 44 deletions README.md

This file was deleted.

31 changes: 26 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.6.0",
"react-dom": "^16.6.0",
"react-scripts": "2.1.1"
"foobar-ipsum": "^1.0.3",
"immer": "^1.7.4",
"node-sass": "^4.9.4",
"normalize.css": "^8.0.0",
"react": "16.7.0-alpha.0",
"react-dom": "16.7.0-alpha.0",
"react-router-dom": "^4.3.1",
"react-scripts": "2.1.1",
"scroll-into-view-if-needed": "^2.2.20",
"simple-react-router": "^0.0.17",
"yup": "^0.26.6"
},
"scripts": {
"start": "react-scripts start",
Expand All @@ -14,12 +22,25 @@
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
"extends": "react-app",
"plugins": [
"react-hooks"
],
"rules": {
"react-hooks/rules-of-hooks": "error"
}
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
],
"devDependencies": {
"eslint-plugin-react-hooks": "^0.0.0"
},
"now": {
"alias": "awesome-hooks.now-sh",
"name": "awesome-hooks"
}
}
32 changes: 0 additions & 32 deletions src/App.css

This file was deleted.

75 changes: 51 additions & 24 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,55 @@
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
import AccordionScreen from './components/accordion';
import Screen from './components/form-library';
import WindowWidthScreen from './components/window-width';
import TodoList from "./components/todolist";

function App() {
return (
<Router>
<div className="main">
<nav className="sidebar">
<div className="item">
<Link className="link" to="/accordion">
Accordion
</Link>
<span>
Panels scroll into view if not fully visible when toggled.
Using <pre>useImperativeMethods</pre> and <pre>useRef</pre>.
</span>
</div>
<div className="item">
<Link className="link" to="/form-library">
Extremely basic form validation library
</Link>
<span>Pass state deeper using context, then read it using <pre>useContext</pre>. Heavily inspired by <pre>formik</pre>.</span>
</div>
<div className="item">
<Link className="link" to="/window-width">
Window width
</Link>
<span>Multiple <pre>useEffects</pre> are allowed.</span>
</div>
<div className="item">
<Link className="link" to="/todo-list">
Todo-list
</Link>
<span>Look mum, <pre>useReducer</pre> is almost Redux!</span>
</div>
</nav>

<div className="content">
<Route path="/" exact component={() => <div>use navigation on the left</div>}/>
<Route path="/accordion" component={AccordionScreen}/>
<Route path="/form-library" component={Screen}/>
<Route path="/window-width" component={WindowWidthScreen}/>
<Route path="/todo-list" component={TodoList}/>
</div>
</div>
);
}
</Router>
)
}

export default App;
export default App;
9 changes: 0 additions & 9 deletions src/App.test.js

This file was deleted.

72 changes: 72 additions & 0 deletions src/components/accordion/Accordion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { useRef, createRef, useImperativeMethods, useState, useEffect } from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import FoobarIpsum from 'foobar-ipsum';

function useAccordion(panelsCount) {
const [currentIndex, setCurrentIndex] = useState();
const [refs, setRefs] = useState();

// This part is smelly
// https://github.com/facebook/react/issues/14072
// TODO rewrite
useEffect(() => {
let refs = {};
for (let i = 0; i <= panelsCount; i++) {
refs[i] = createRef();
}
setRefs(refs);
}, []);

useEffect(() => {
// Scroll current accordion panel into view
if (currentIndex !== undefined) {
refs[currentIndex].current.scrollIntoView();
}
}, [currentIndex]);

function setCurrent(newIndex) {
setCurrentIndex(currentIndex === newIndex ? undefined : newIndex);
}

return [currentIndex, setCurrent, refs];
}

const AccordionPanel = React.forwardRef((props, ref) => {
const containerRef = useRef();
const textRef = useRef();

useImperativeMethods(ref, () => ({
scrollIntoView: () => {
scrollIntoView(containerRef.current, { block: 'nearest', scrollMode: 'if-needed' });
}
}));

return <div onClick={props.onClick} ref={containerRef}>
<div className="accordion-label">{props.label}</div>
{props.isOpen &&
<div>{generateRandomText()}</div>}
</div>;
});

function Accordion(props) {
return <div>{props.children}</div>;
}

function generateRandomNumber(max) {
return Math.floor(Math.random() * Math.floor(max));
}

function generateRandomText() {
return new FoobarIpsum({
size: {
sentence: generateRandomNumber(100),
paragraph: generateRandomNumber(10)
}
}).paragraph();
}

export {
useAccordion,
Accordion,
AccordionPanel
};
23 changes: 23 additions & 0 deletions src/components/accordion/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

import { Accordion, AccordionPanel, useAccordion } from './Accordion';

function AccordionScreen() {
const panels = [...Array(100).keys()].map(e => `Panel number ${e}`);

const [currentIndex, setCurrent, refs] = useAccordion(panels.length);

return <Accordion>
{panels.map((panel, index) => (
<AccordionPanel
ref={refs && refs[index]}
key={index}
label={panel}
isOpen={currentIndex === index}
onClick={() => setCurrent(index)}
/>
))}
</Accordion>;
}

export default AccordionScreen;
61 changes: 61 additions & 0 deletions src/components/form-library/FormLibrary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { useState, useEffect, createContext } from "react";

const FormContext = createContext(null);

function MyFormLibrary({ children, initialValues, onValuesChanged, onSubmit, validate }) {
const [values, updateValues] = useState(initialValues);
const [errors, updateErrors] = useState({});

useEffect(() => {
if (typeof onValuesChanged === 'function') {
onValuesChanged(values);
}
}, [values]);

function handleChange(e) {
updateValues({
...values,
[e.target.name]: e.target.value
});
updateErrors({
...errors,
[e.target.name]: undefined
});
}

async function submitForm() {
try {
validate && validate(values);
await onSubmit(values);
} catch (e) {
updateErrors(convertErrors(e));
}
}

function handleSubmit(e) {
e.preventDefault();
submitForm();
}

const ctx = {
values,
errors,
handleChange,
handleSubmit
};

return <FormContext.Provider value={ctx}>
{children}
</FormContext.Provider>;
}

function convertErrors(yupError) {
return yupError.inner
.reduce((acc, cur) => {
acc[cur.path] = cur.message;
return acc;
}, {});
}

export default MyFormLibrary;
export { FormContext };
Loading

0 comments on commit 0c1c40f

Please sign in to comment.