Deal with arranging other components on a page
import styled from 'styled-components'
const Container = styled.div`
display: flex;
`
const Pane = styled.div`
flex: ${props => props.weight}
`
export const SplitScreen = ({
children,
leftWeight = 1,
rightWeight = 1
}) => {
const [left, right] = children
return (
<Container>
<Pane weight={leftWeight}>
{left}
</Pane>
<Pane weight={rightWeight}>
{right}
</Pane>
</Container>
)
}
export const RegularList = ({
items,
resourceName,
itemComponent: ItemComponent,
}) => {
return (
<>
{items.map((item, i) => (
<ItemComponent key={i} {...{ [resourceName]: item }} />
))}
</>
)
}
export const NumberedList = ({
items,
resourceName,
itemComponent: ItemComponent,
}) => {
return (
<>
{items.map((item, i) => (
<>
<h3>{i + 1}</h3>
<ItemComponent key={i} {...{ [resourceName]: item }} />
</>
))}
</>
)
}
import { useState } from 'react'
import styled from 'styled-components'
const ModalBackground = styled.div`
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
`
const ModalBody = styled.div`
background-color: white;
margin: 10% auto;
padding: 20px;
width: 50%;
`
export const Modal = ({ children }) => {
const [shouldShow, setShouldShow] = useState(false)
return (
<>
<button onClick={() => setShouldShow(true)}>Show Modal</button>
{shouldShow && (
<ModalBackground onClick={() => setShouldShow(false)}>
<ModalBody onClick={() => e.stopPropagation()}>
<button onClick={() => setShouldShow(false)}>Hide Modal</button>
{children}
</ModalBody>
</ModalBackground>
)}
</>
)
}
Data loading and management
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export const ResourceLoader = ({ resourceUrl, resourceName, children }) => {
const [state, setState] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourceUrl);
setState(response.data);
})();
}, [resourceUrl]);
return (
<>
{React.Children.map(children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { [resourceName]: state });
}
return child;
})}
</>
);
}
import axios from 'axios';
import { DataSource } from './DataSource';
const getServerData = url => async () => {
const response = await axios.get(url);
return response.data;
}
const getLocalStorageData = key => {
return localStorage.getItem(key)
}
const Text = ({ message }) => <h1>{message}</h1>
function App() {
return (
<>
<DataSource getDataFunc={getServerData('/users/123')} resourceName="user">
<UserInfo />
</DataSource>
<DataSource getDataFunc={getLocalStorageData('message')} resourceName="message">
<Text />
</DataSource>
</>
);
}
export default App;
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export const ResourceLoader = ({ resourceUrl, resourceName, children }) => {
const [state, setState] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourceUrl);
setState(response.data);
})();
}, [resourceUrl]);
return (
<>
{React.Children.map(children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { [resourceName]: state });
}
return child;
})}
</>
);
}
export const UserInfo = ({ user }) => {
const { name, age, hairColor, hobbies } = user || {};
return user ? (
<>
<h3>{name}</h3>
<p>Age: {age} years</p>
<p>Hair Color: {hairColor}</p>
<h3>Hobbies:</h3>
<ul>
{hobbies.map(hobby => <li key={hobby}>{hobby}</li>)}
</ul>
</>
) : <p>Loading...</p>;
}
export const ProductInfo = ({ product }) => {
const { name, price, description, rating } = product || {};
return product ? (
<>
<h3>{name}</h3>
<p>{price}</p>
<h3>Description:</h3>
<p>{description}</p>
<p>Average Rating: {rating}</p>
</>
) : <p>Loading...</p>;
}
import { ResourceLoader } from './ResourceLoader';
import { ProductInfo } from './ProductInfo';
import { UserInfo } from './UserInfo';
function App() {
return (
<>
<ResourceLoader resourceUrl="/users/123" resourceName="user">
<UserInfo />
<ResourceLoader />
<ResourceLoader resourceUrl="/products/1234" resourceName="product">
<ProductInfo />
<ResourceLoader />
</>
);
}
export default App;
Component keeps track of internal state and only releases data when some event occurs
export const UncontrolledForm = () => {
const nameInput = React.createRef();
const ageInput = React.createRef();
const hairColorInput = React.createRef();
const handleSubmit = e => {
console.log(nameInput.current.value);
console.log(ageInput.current.value);
console.log(hairColorInput.current.value);
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<input name="name" type="text" placeholder="Name" ref={nameInput} />
<input name="age" type="number" placeholder="Age" ref={ageInput} />
<input name="hairColor" type="text" placeholder="Hair Color" ref={hairColorInput} />
<input type="submit" value="Submit" />
</form>
);
}
import { useState } from 'react';
import styled from 'styled-components';
const ModalBackground = styled.div`
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
`;
const ModalBody = styled.div`
background-color: white;
margin: 10% auto;
padding: 20px;
width: 50%;
`;
export const UncontrolledModal = ({ children }) => {
const [shouldShow, setShouldShow] = useState(false);
return (
<>
<button onClick={() => setShouldShow(true)}>Show Modal</button>
{shouldShow && (
<ModalBackground onClick={() => setShouldShow(false)}>
<ModalBody onClick={e => e.stopPropagation()}>
<button onClick={() => setShouldShow(false)}>Hide Modal</button>
{children}
</ModalBody>
</ModalBackground>
)}
</>
);
}
import React, { useState } from 'react';
export const UncontrolledOnboardingFlow = ({ children, onFinish }) => {
const [onboardingData, setOnboardingData] = useState({});
const [currentIndex, setCurrentIndex] = useState(0);
const goToNext = stepData => {
const nextIndex = currentIndex + 1;
const updatedData = {
...onboardingData,
...stepData,
};
console.log(updatedData);
if (nextIndex < children.length) {
setCurrentIndex(nextIndex);
} else {
onFinish(updatedData);
}
setOnboardingData(updatedData);
}
const currentChild = React.Children.toArray(children)[currentIndex];
if (React.isValidElement(currentChild)) {
return React.cloneElement(currentChild, { goToNext });
}
return currentChild;
}
import { UncontrolledOnboardingFlow } from './UncontrolledOnboardingFlow';
const StepOne = ({ goToNext }) => (
<>
<h1>Step 1</h1>
<button onClick={() => goToNext({ name: 'John Doe' })}>Next</button>
</>
);
const StepTwo = ({ goToNext }) => (
<>
<h1>Step 2</h1>
<button onClick={() => goToNext({ age: 100 })}>Next</button>
</>
);
const StepThree = ({ goToNext }) => (
<>
<h1>Step 3</h1>
<button onClick={() => goToNext({ hairColor: 'brown' })}>Next</button>
</>
);
function App() {
return (
<UncontrolledOnboardingFlow onFinish={data => {
console.log(data);
alert('Onboarding complete!');
}}>
<StepOne />
<StepTwo />
<StepThree />
</UncontrolledOnboardingFlow>
);
}
export default App;
Component does not keep track of internal state and passes data with props
import { useState, useEffect } from 'react';
export const ControlledForm = () => {
const [nameInputError, setNameInputError] = useState('');
const [name, setName] = useState('');
const [age, setAge] = useState();
const [hairColor, setHairColor] = useState('');
useEffect(() => {
if (name.length < 2) {
setNameInputError('Name must be two or more characters');
} else {
setNameInputError('');
}
}, [name])
return (
<form>
{nameInputError && <p>{nameInputError}</p>}
<input
name="name"
type="text"
placeholder="Name"
value={name}
onChange={e => setName(e.target.value)} />
<input
name="age"
type="number"
placeholder="Age"
value={age}
onChange={e => setAge(Number(e.target.value))} />
<input
name="hairColor"
type="text"
placeholder="Hair Color"
value={hairColor}
onChange={e => setHairColor(e.target.value)} />
<button>Submit</button>
</form>
)
}
import styled from 'styled-components';
const ModalBackground = styled.div`
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
`;
const ModalBody = styled.div`
background-color: white;
margin: 10% auto;
padding: 20px;
width: 50%;
`;
export const ControlledModal = ({ shouldShow, onRequestClose, children }) => {
return shouldShow ? (
<ModalBackground onClick={onRequestClose}>
<ModalBody onClick={e => e.stopPropagation()}>
<button onClick={onRequestClose}>Hide Modal</button>
{children}
</ModalBody>
</ModalBackground>
) : null;
}
export const ControlledOnboardingFlow = ({ children, onFinish, currentIndex, onNext }) => {
const goToNext = stepData => {
onNext()
}
const currentChild = React.Children.toArray(children)[currentIndex];
if (React.isValidElement(currentChild)) {
return React.cloneElement(currentChild, { goToNext });
}
return currentChild;
}
import { useState } from 'react';
import { ControlledOnboardingFlow } from './ControlledOnboardingFlow';
const StepOne = ({ goToNext }) => (
<>
<h1>Step 1</h1>
<button onClick={() => goToNext({ name: 'John Doe' })}>Next</button>
</>
);
const StepTwo = ({ goToNext }) => (
<>
<h1>Step 2</h1>
<button onClick={() => goToNext({ age: 100 })}>Next</button>
</>
);
const StepThree = ({ goToNext }) => (
<>
<h1>Step 3</h1>
<p>Congratulations! You qualify for our senior discount</p>
<button onClick={() => goToNext({})}>Next</button>
</>
);
const StepFour = ({ goToNext }) => (
<>
<h1>Step 4</h1>
<button onClick={() => goToNext({ hairColor: 'brown' })}>Next</button>
</>
);
function App() {
const [onboardingData, setOnboardingData] = useState({})
const [currentIndex, setCurrentIndex] = useState(0)
const onNext = stepData => {
setOnboardingData({ ...onboardingData, ...stepData });
setCurrentIndex(nextIndex + 1);
}
return (
<ControlledOnboardingFlow
currentIndex={currentIndex}
onNext={onNext}
>
<StepOne />
<StepTwo />
{onboardingData.age >= 62 && <StepThree />}
<StepFour />
</ControlledOnboardingFlow>
);
}
export default App;
A component that returns another component instead of JSX
export const printProps = Component => {
return (props) => {
console.log(props);
return <Component {...props} />
}
}
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export const withUser = (Component, userId) => {
return props => {
const [user, setUser] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(`/users/${userId}`);
setUser(response.data);
})();
}, []);
return <Component {...props} user={user} />
}
}
export const UserInfo = ({ user }) => {
const { name, age, hairColor, hobbies } = user || {};
return user ? (
<>
<h3>{name}</h3>
<p>Age: {age} years</p>
<p>Hair Color: {hairColor}</p>
<h3>Hobbies:</h3>
<ul>
{hobbies.map(hobby => <li key={hobby}>{hobby}</li>)}
</ul>
</>
) : <p>Loading...</p>;
}
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export const withEditableUser = (Component, userId) => {
return props => {
const [originalUser, setOriginalUser] = useState(null);
const [user, setUser] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(`/users/${userId}`);
setOriginalUser(response.data);
setUser(response.data);
})();
}, []);
const onChangeUser = changes => {
setUser({ ...user, ...changes });
}
const onSaveUser = async () => {
const response = await axios.post(`/users/${userId}`, { user });
setOriginalUser(response.data);
setUser(response.data);
}
const onResetUser = () => {
setUser(originalUser);
}
return <Component {...props}
user={user}
onChangeUser={onChangeUser}
onSaveUser={onSaveUser}
onResetUser={onResetUser} />
}
}
import { withEditableUser } from "./withEditableUser";
export const UserInfoForm = withEditableUser(({ user, onChangeUser, onSaveUser, onResetUser }) => {
const { name, age, hairColor } = user || {};
return user ? (
<>
<label>
Name:
<input value={name} onChange={e => onChangeUser({ name: e.target.value })} />
</label>
<label>
Age:
<input type="number" value={age} onChange={e => onChangeUser({ age: Number(e.target.value) })} />
</label>
<label>
Hair Color:
<input value={hairColor} onChange={e => onChangeUser({ hairColor: e.target.value })} />
</label>
<button onClick={onResetUser}>Reset</button>
<button onClick={onSaveUser}>Save Changes</button>
</>
) : <p>Loading...</p>;
}, '123');
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
export const withEditableResource = (Component, resourcePath, resourceName) => {
return props => {
const [originalUser, setOriginalData] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourcePath);
setOriginalData(response.data);
setData(response.data);
})();
}, []);
const onChange = changes => {
setData({ ...data, ...changes });
}
const onSave = async () => {
const response = await axios.post(resourcePath, { [resourceName]: data });
setOriginalData(response.data);
setData(response.data);
}
const onReset = () => {
setData(originalData);
}
const resourceProps = {
[resourceName]: data,
[`onChange${capitalize}`]: onChange,
[`onSave${capitalize}`]: onSave,
[`onReset${capitalize}`]: onReset,
}
return <Component {...props} {...resourceProps} />
}
}
resourcePath
and resourceName
have to be passed and new import (otherwise the same).
import { withEditableResource } from "./withEditableResource";
export const UserInfoForm = withEditableResource(({ user, onChangeUser, onSaveUser, onResetUser }) => {
const { name, age, hairColor } = user || {};
return user ? (
<>
<label>
Name:
<input value={name} onChange={e => onChangeUser({ name: e.target.value })} />
</label>
<label>
Age:
<input type="number" value={age} onChange={e => onChangeUser({ age: Number(e.target.value) })} />
</label>
<label>
Hair Color:
<input value={hairColor} onChange={e => onChangeUser({ hairColor: e.target.value })} />
</label>
<button onClick={onResetUser}>Reset</button>
<button onClick={onSaveUser}>Save Changes</button>
</>
) : <p>Loading...</p>;
}, '/users/123', 'user');
Special hooks we define ourselves, and that usually combine the functionality of one or more existing React hooks like "useState" or "useEffect"
import { useState, useEffect } from 'react';
import axios from 'axios';
export const useCurrentUser = () => {
const [user, setUser] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get('/current-user');
setUser(response.data);
})();
}, []);
return user;
}
import { useState, useEffect } from 'react';
import axios from 'axios';
export const useUser = userId => {
const [user, setUser] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(`/users/${userId}`);
setUser(response.data);
})();
}, [userId]);
return user;
}
import { useState, useEffect } from 'react';
import axios from 'axios';
export const useResource = resourceUrl => {
const [resource, setResource] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourceUrl);
setResource(response.data);
})();
}, [resourceUrl]);
return resource;
}
import { useState, useEffect } from 'react';
import axios from 'axios';
export const useDataSource = getResourceFunc => {
const [resource, setResource] = useState(null);
useEffect(() => {
(async () => {
const result = await getResourceFunc();
setResource(result.data);
})();
}, [resourceUrl]);
return resource;
}
const isObject = x => typeof x === 'object' && x !== null;
export const RecursiveComponent = ({ data }) => {
if (!isObject(data)) {
return (
<li>{data}</li>
);
}
const pairs = Object.entries(data);
return (
<>
{pairs.map(([key, value]) => (
<li>
{key}:
<ul>
<RecursiveComponent data={value} />
</ul>
</li>
))}
</>
);
}
export const Button = ({ size, color, text, ...props }) => {
return (
<button style={{
padding: size === 'large' ? '32px' : '8px',
fontSize: size === 'large' ? '32px' : '16px',
backgroundColor: color,
}} {...props}>{text}</button>
);
}
export const DangerButton = props => {
return (
<Button {...props} color="red" />
);
}
export const BigSuccessButton = props => {
return (
<Button {...props} size="large" color="green" />
);
}
export const partiallyApply = (Component, partialProps) => {
return props => {
return <Component {...partialProps} {...props} />
}
}
export const Button = ({ size, color, text, ...props }) => {
return (
<button style={{
padding: size === 'large' ? '32px' : '8px',
fontSize: size === 'large' ? '32px' : '16px',
backgroundColor: color,
}} {...props}>{text}</button>
);
}
export const DangerButton = partiallyApply(Button, { color: 'red' })
export const BigSuccessButton = partiallyApply(Button, { color: 'green', size: 'large' })