JSX
State Vs Props
Event Handling
Form Handling
React Router
Context
To quote from the react documentation
In our experience, thinking about how the UI should look at any given moment, rather than how to change it over time, eliminates a whole class of bugs.
So, instead of thinking in terms of changes
function updateClock() {
const clockEl = document.getElementById("clock");
clockEl.innerHTML = new Date().toLocaleTimeString();
}
You just describe the clock:
function tick() {
const element = <span id="clock">{new Date().toLocaleTimeString()}.</span>;
ReactDOM.render(element, document.getElementById("root"));
}
setInterval(tick, 1000);
In React, everything that appears on screen is a Component.
Components are composed of other components. A good example.
Each component should be stand-alone. That is to say, a component knows about the DOM elements and components that make it up, but it doesn't know anything about the context in which it is being rendered.
Components always return JSX.
Though optional in some specific cases, it's best to always include these parenthesis
const element = (
<h1>Hello, world!</h1>
<p>I'd like to welcome you to the world of React</p>
);
From the simple
const name = "Ian";
const element = <h1>Hello, {name}</h1>;
To the complex
const firstName = "Ian";
const lastName = "Bentley";
const element = (
<h1>
Hello, {firstName} {lastName === undefined ? "Smith" : lastName}
</h1>
);
- Don’t put quotes around curly braces when embedding a JavaScript expression in an attribute. You should either use quotes (for string values) or curly braces (for expressions), but not both in the same attribute.
// Yes!
const element = <div tabIndex="0"></div>;
// Yes!
const element = <img src={user.avatarUrl}></img>;
// No!
const element = <img src="{user.avatarUrl}"></img>;
- By default, React DOM escapes any values embedded in JSX before rendering them
To pass down props into a component you define the prop wherever the component
is being rendered. In this example, name
is a prop with the value of James
.
function App() {
return <Welcome name="James" />;
}
To access the props, you have to pass them into the component as a parameter - the simplest function components are defined as:
function Welcome(props) {
return <h1> Hello, {props.name}</h1>;
}
props is an object that can be destructured. It is good practice to destructure the props in the parameter. Here is 3 ways of getting the name key from props.
const Welcome = (props) => {
return <h1> Hello, {props.name}</h1>;
};
const Welcome = (props) => {
const { name } = props;
return <h1> Hello, {name}</h1>;
};
const Welcome = ({ name }) => {
return <h1> Hello, {name}</h1>;
};
State is similar to props, but it is private and fully controlled by the component.
props
don't change over time, butstate
does.
In order to use state in function components, you need to import useState from 'react'.
import { useState } from "react";
// Notice you are not using props in this component so you don't have to add it as a parameter if you don't need it.
const Clock = () => {
const [date, setDate] = useState(new Date());
return <h2>It is {date.toLocaleTimeString()}.</h2>;
};
While props are immutable, state changes over time. With every change to state, react will re-render the component.
You should never modify state
directly. Doing so will not result in a render,
and it is bad practice. Instead use the updater function given from useState
.
To learn more about the updater function and what is returned from useState, click here
import { useState } from "react";
const Person = () => {
const [age, setAge] = useState(27);
const increaseAge = () => setAge(age + 1)
return (
<div> I am {age} years old <div>
<button onClick={increaseAge}> Increase Age </button>
)
};
In the above example, calling setAge updates the age slice of state.
You can do it this way but if the new state is computed using the previous state, you should pass a function to setState that accepts the previous states value as the parameter to the function to prevent any potential bugs. The function will receive the previous value, and return an updated value.
// Never!!
age = age + 1;
// better
setAge(age + 1);
// best
setAge((previousAge) => previousAge + 1);
Other instances of the same component will have isolated state's, the parent of a component don't have access to a child component's state, etc.
Utilize map
in order to create an array of elements, which you can then
reference in the full JSX.
Make sure that you assign a key to each element in the list. Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) => (
<li key={number.toString()}>
{number}
</li>
);
);
return (
<ul>{listItems}</ul>
);
}
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys:
const todoItems = todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort. Do not use index for the keys if the order of items may change. This can negatively impact performance and may cause issues with component state.
const todoItems = todos.map((todo, index) => <li key={index}>{todo.text}</li>);
- Keys only make sense in the context of the surrounding array. For example, if
you extract a
ListItem
component, you should keep the key on the<ListItem />
elements in the array rather than on the<li>
element in the ListItem itself. For example:
const listItems = numbers.map(number => (
<ListItem key={number.toString()} number={number}>
))
- Keys must be unique among their siblings, but do not need to be globally unique.
The useEffect
hook lets you perform side effects in your function components.
To learn more about useEffect I encourage you to read this
page on the react docs.
I also encourage you to read over all the React docs when you can find the time. As of the writing of this markdown file, the react core team is currently rewriting the entire docs to focus on function components rather than class components. The hooks section of the docs will be very useful to you!
In react, you generally don't use addEventListener
, instead just provide an
inline listener in your JSX.
In vanilla JavaScript, you might do:
const button = document.getElementById("button");
button.addEventListener("click", handleClick);
While in react you would do:
function MyComponent() {
const handleClick = () => {
console.log("button was clicked!");
};
return (
<button id="button" onClick={handleClick}>
myButton
</button>
);
}
or
return (
<button id="button" onClick={() => console.log("button was clicked!")}>
myButton
</button>
);
In HTML a textarea
element specifies it's value by populating it's
innerText
:
<textarea>
This child content is equivalent to the _value_ attribute of a normal input.
</textarea>
In another exception, select
elements specify their value by setting the
selected
attribute on an option
element:
<select>
<option value="visa">Visa</option>
<option value="mc" selected>MasterCard</option>
<option value="amex">AmericanExpress</option>
</select>
React makes this behaviour more consistent, by supporting a value
attribute on
select
and textarea
fields, so:
import { useState } from "react";
const EssayForm = () => {
const [value, setValue] = useState("Please write an essay");
return (
<form>
<label>
Essay:
<textarea value={value} onChange={(e) => setValue(e.target.value)} />
</label>
</form>
);
};
and
import { useState } from "react";
function PaymentForm() {
const [value, setValue] = useState("amex");
return (
<select value={value} onChange={(e) => setValue(e.target.value)}>
<option value="visa">Visa</option>
<option value="mc">MasterCard</option>
<option value="amex">AmericanExpress</option>
</select>
);
}
You will use react-router-dom
as your routing library. This will allow you to
control what components to display using the browser location.
import {
BrowserRouter
Switch,
Route,
NavLink
} from "react-router-dom";
<BrowserRouter></BrowserRouter>
enables the use of the other react-router-dom
components and passes routing information to all its descendant components. All
other Browser related components must be children of the BrowserRouter
component.
<Switch></Switch>
wraps several <Route .../>
components, rendering just the
first matched Route, and no others.
<Route path="/about"> <About/> </Route
connects specific URLs to specific
components to render.
<NavLink to="/about" activeClassName="selected">About</NavLink>
works like an
anchor tag, updating the URL in the browser. It adds an additional styling
attribute when the current URL matches the path. If you do not define an
activeClassName, then .active
is the default.
import { BrowserRouter, Route, Switch, NavLink } from "react-router-dom";
const Routes = () => {
return (
<BrowserRouter>
<div>
<nav>
<ul>
<li>
<NavLink to="/about">About</NavLink>
</li>
<li>
<NavLink to="/blog">Blog</NavLink>
</li>
</ul>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/blog">
<Blog />
</Route>
</Switch>
</nav>
</div>
</BrowserRouter>
);
};
<Route path="/users/:userId">
<UserShow />
</Route>
When a route is matched, the values passed through the url parameters (:userId
for example) are accessible through the useParams
hook from
react-router-dom
.
import {useParams} from 'react-router-dom'
const UserShow = () = {
const {userId} = useParams()
return (
<h1>Hello User {userId}</h1>
);
}
React Contexts act like topic
queues, in that any component can hook into the
context to receive its value. That is to say, anything passed to the Provider's
value
will be available to any component that uses the useContext
hook for a
particular context. It is common to have the value be an object containing what
you want to be accessible by your consumers.
This will create a Context wrapper. You are using state in this component to store your user object that is then passed into your context providers value so that your other components can have access to both the user and the updater function to update the user object.
/src/context/UserContext.js
import { useState, createContext } from "react";
export const UserContext = createContext();
function UserContextWrapper({ children }) {
const [user, setUser] = useState({ name: "James", age: 27 });
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
export default UserContextWrapper;
In order to allow your component to utilize the properties passed through the
context all you have to do is use the useContext
hook from react
, passing in
the context you want to use. Make sure that the components that need access to
to a particular context are children of the UserContextWrapper
component. Like
so:
import UserContextWrapper from "./context/UserContext";
const App = () => (
// By wrapping Consumer inside of the UserContextWrapper component, Consumer now has access to the user object!
<UserContextWrapper>
<Consumer />
</UserContextWrapper>
);
So now the Consumer component can hook into the context to receive the user object!
Consumer.js
import { useContext } from "react";
import { UserContext } from "./UserContext";
const Consumer = () => {
const { user, setUser } = useContext(UserContext);
const updateUserAge = () => {
const newAge = user.age + 1
// Notice here that you are creating a new object (Never mutate state), spreading out the user keys,
// and then changing the age key value to be newAge. This will overwrite the previous age from the spread operation.
setUser({...user, age: newAge})
}
return (
<div>
Hello my name is {user.name} and I am {user.age} years old.{" "}
</div>
<button onClick={updateUserAge}> Update Age </button>
);
};