The Project Hosted On Netlify: https://shaq-crwn-clothing.netlify.app/
React only re-renders under certain conditions
State Change
( just because you update a key, value in the state, doesn't mean the state changes, it's still the same object in memory )
When you use this.setState it forces the javascript to create a new object in memory, therefore changing the state, causing a re-renderProperties Change
Whenever a prop is change, React will re-render
- Constructor runs first, all you really need to do initialize the state in the ctor
- render() method runs second, mounts the jsx to the DOM determines how the UI will look
- componentDidMount() Lifecycle method will run 3rd
- state changes so render() re-renders
- unlike with Class components, you can't expect a re-render just be using this.setState
- With Functional components the setStateValue() method doesn't cause a re-render, only re-renders if the value is actually different
- There is no Render() method inside of a Functional component, you can look at the whole function as the Render() method
- When state or props change, it will caused a re-render just like with Class components
- When a re-render happens in a Functional component, it runs all the code in the Functional component 'top down'
- Uses Contructors, Lifecycle methods
- has a render() method, with a return statement inside of the render() method
- this.state is created usually inside of the Constructor as a json object
- Uses arrow functions, hooks( replaces Lifecycle methods )
- Functional components can take in props as Arg 1 and forwardRef as Arg 2
// functional components can take in props as Arg 1 and forwardRef as Arg 2
const CardList = (props, forwardRef) => {
// 1st way of destructuring props, you can also do it from the props arg
const { monsters } = props;
return (
<div className="card-list">
{monsters.map((monster) => {
return <Card monster={monster} />;
})}
</div>
)
}
- doesn't have a render() method or Lifecycle methods
- Need to understand functions, pure functions, impure functions and side effects to completely understand the way React handles functional components
- We will be writing Impure Functions in React by using Hooks
- Pure Function ( the return value is determines solely by calculations done inside of the function )
- returns the same thing, everytime if given the same arguments
-
const pureFunction = (a, b) => { return a + b; }
- Impure Function ( the return value isn't determines solely by calculations done inside of the function, there's outside interference )
- relies on some external value so there's a POSSIBILITY that it won't return the same thing event with the same arguments
-
// what if c changes, then this function will not return the same thing even though the args haven't changed const c = 3 const impureFunction = (a, b) => { return a + b + c; }
- Side Effects ( This is when a function creates some kind of effect outside of its scope )
- could change a value of something that lives outside of its scope
-
const c = 3 const sideEffects = (a, b) => { // this function changes the value of c ( a variable that lives outside of its scope ) c = a + b return a * b; }
- You don't store whole state objects in memory anymore for functional components, you use the useState() hook
- const [searchField, setSearchField] = useState(''); // [ value, setValue]
Class Components use render()
method and extend Component
returns jsx inside of the render()
method
you can create state for class components by creating a json object and setting its key, value properties
you can assess the state by using {}
and inside of the curly brackets {this.state.keyName}
const obj1 = { name: 'Shaquille' }\
const obj2 = obj1\
obj1 === obj2 --> ( This is true, because they point to the same place in memory )
const obj3 = Object.assign({}, obj1)\
obj1 === obj3 --> ( This is false, because Object.assign() created a completely different object in memory )
same thing as doing
const obj4 = { name: 'Shaquille' }\
obj1 === obj4 --> ( This is false, because you created a completely different object in memory )
this.state.name = 'Shaquille' --> ( this will not update the state )
this.setState({ name: 'Shaquille' }) --> ( this will update the state )
this.state = { name: { firstName: 'Shaquille', lastName: 'Mandy' }, company: 'Amazon'}
this.setState({ name : { firstName: 'Shaq', lastName: 'Mand'} });
this.setState
only updates the keys that you pass it setState({ name: 'Shaquille' })
, it will keep all the other keys the same
The first argument is the updater callback function. You update the object(the state)
You can also pass a callback to this.setState as the second argument callback function, this callback will run after the 1st callback
for the first callback function, the first argument is state, the second is properties when you pass in the object
Example 1:
this.setState((state, props) => {
return {
name : { firstName: 'Shaquille', lastName: 'Mandy'}
}
}, () => {});
Example 2:
this.setState(
(state, props) => {
return {
name : { firstName: 'Shaquille', lastName: 'Mandy'}
}
},
() => {
// this callback will only run after the first callback finishes
console.log(this.state)
}
);
Array.map() takes a callback function, the 1st argument of the callback is the current element of the array
The callback function applys itself to every element in the array
In this case it creates a H1 element with the monsters name for each monster
class App extends Component {
constructor() {
// calling ctor for Component
super();
// state is always a json object
this.state = {
// the elements have a id(key), so react can re-render, recognize different components and optimize better
monsters: [
{ name: 'Linda', id: '12ae33ca' },
{ name: 'Frank', id: '12ae33cb' },
{ name: 'Jacky', id: '12ae33cc' },
{ name: 'Lizzy', id: '12ae33cd' },
],
};
}
render () {
return (
<div className="App">
{this.state.monsters.map((monster) => {
return <h1 key={monster.id}>{monster.name}</h1>;
})}
</div>
);
}
}
the elements have a id(key), so react can re-render, recognize different components and optimize better
Reacts only changes what it needs to change and it does it w/o having to constantly refresh the entire webpage
Once the webpage load, React has all the code and can intelligently CRUD components
mounted is when the component is first placed(mounted) on the DOM
this is a good place to add api fetches that need to be there on page load
- Don't use setStateValue inside of a Fetch call because it will cause a
INFINITE RE-RENDER LOOP
- RETURNING AN ARRAY W/ A DIFFERENT MEMORY LOCATION EVERYTIME IT FETCHES
EVEN THOUGH THE VALUES ARE THE SAME IN THE ARRAY IT UPDATES STATE BC THE MEMORY LOCATION IS DIFFERENT
- RETURNING AN ARRAY W/ A DIFFERENT MEMORY LOCATION EVERYTIME IT FETCHES
-
// The 1st Arg is a Callback function, and it runs onces automatically upon mounting the component // 2nd Arg is a Array of Dependencies Array of Dependencies is usually a state or prop value // and if it changes it will run the 1st Arg Callback Function // because we don't ever want this Fetch call to re-run we won't put anything in the array of dependencies // this way the Fetch will never get re-ran from the 1st arg callback function, because there are no values // to update in the array of dependencies, if you only want something to run once, set array of dependencies to a empty array [] useEffect(() => { fetch('https://jsonplaceholder.typicode.com/users') .then((response) => response.json()) .then((users) => setMonsters(users)); }, []);
filter loops through each element, if the condition is true it keeps the element in the array
if the condition is false it removes the element from the array
Because Array.filter() modifies the array in place it's best practice to create a new array
Example of using Array.filter()
<input
className='search-box'
type='search'
placeholder='search monsters'
onChange={(event) => {
// event.target.value contains the text inside of the search box
// make the search case insensitive
const searchString = event.target.value.toLocaleLowerCase();
// filter loops through each element, if the condition is true it keeps the element in the array
// if the condition is false it removes the element from the array
// array.filter() returns a new array
const filteredMonsters = this.state.monsters.filter((monster) => {
return monster.name.toLocaleLowerCase().includes(searchString);
});
this.setState((state, props) => {
return { monsters: filteredMonsters };
})
}}
/>
Once state starts getting more complicated and you have multiple things being updating at once, that is a good time to start using useReducer() instead of useState()
to use useReducer() you need a reducer function that takes as a argument a 'state object' and 'action object'
// reducers only store readable values, not functions(setters)
const cartReducer = (state, action) => {
const { type, payload } = action;
switch(type) {
case CART_ACTION_TYPES.SET_CART_ITEMS:
return {
...state,
...payload
}
case CART_ACTION_TYPES.SET_IS_CART_OPEN:
return {
...state,
isCartOpen: payload
}
default:
throw new Error(`Unhandled type ${action.type} in userReducer`)
}
}
to use useReducer() you also need a INITIAL_STATE object
const INITIAL_STATE = {
isCartOpen: false,
cartItems: [],
cartCount: 0,
cartTotal: 0
}
once you have both of these you can call the useReducer() function
the useReducer() function return a array with the 1st element being a state object, the 2nd element is a dispatch() function
const [ state, dispatch ] = useReducer(cartReducer, INITIAL_STATE);
// you can destructure from the state variable sense it's a object
const { cartItems, isCartOpen, cartCount, cartTotal } = state;
// when you call the dispatch() function, it takes a 'ACTION object' as its only argument
// everytime the dispatch() function is called, it executes the SWITCH logic in the reducer() function
// the ACTION object has a key called 'type' and a optional 2nd key called 'payload'
dispatch({
type: CART_ACTION_TYPES.SET_CART_ITEMS,
payload: {
cartItems: newCartItems,
cartCount: newCartCount,
cartTotal: newCartTotal
}
})
This could make the state storage more complicated, because state would be stored in 2 different places
in Redux, when dispatch() is called, every reducer gets invoked. This does not happen with useReducer()
meaning that every reducer should return the previous state at the end of the switch statement
using createSelector will mitigate some of the unnecessary re-renders and improve performance caused by redux sending out a dispatch to every reducer
usually you will have the code split up into 3 - 4 files
- reducer
- contains the const for INITIAL_STATE, the INITIAL_STATE will build upon the state that the rootReducer has
- contains a start empty action object
- contains the switch statement or other conditional logic that allows state to be altered based on a specific action.type
- actions
- everything in this file should return a action object with the properties 'type' and a optional property 'payload'
- types
- just a object that we create so that we can utilize dot notation for the ACTION_TYPES, less prone to human error if done this way
- selector
- this is where we pull the data off of the state from. The data can be transformed inside of this file, so that when you call useSelector and give it this selector, you get the data the way that you are expecting.
dispatch() along with a reducer will update the state
you call dispatch and give it a action object with a 'type' and optional 'payload'
dispatch calls every reducer and sends this action object to the reducers
the state gets updated with the logic that you have inside of your reducer for that specific action.type
// it's getting the state from the rootReducers state obj then going deeper into that specific
// reducer's state object defined in that specific reducers INITIAL_STATE
export const selectCategoriesMap = (state) => (state.categories.categoriesMap)
// then when you need to access that specific state, you can use the useSelector hook
import { useSelector } from "react-redux";
// then choose the selector that has the state that you want access to
import { selectCategoriesMap } from "../../store/categories/categories.selector"
// now you have access to this state
const categoriesMap = useSelector(selectCategoriesMap)
// this action returns a action object with the 'type' and optional 'payload' properties
export const setIsCartOpen = (boolean) => ({
type: CART_ACTION_TYPES.SET_IS_CART_OPEN,
payload: boolean,
});
// just a object that was created in order to help prevent human error when typing out the types that will be dispatched
export const CART_ACTION_TYPES = {
SET_IS_CART_OPEN: "cart/SET_IS_CART_OPEN",
SET_CART_ITEMS: "cart/SET_CART_ITEMS",
};
The reducer will need a INITIAL_STATE and action
import { CART_ACTION_TYPES } from './cart.types';
export const CART_INITIAL_STATE = {
isCartOpen: false,
cartItems: [],
};
export const cartReducer = (state = CART_INITIAL_STATE, action = {}) => {
const { type, payload } = action;
switch (type) {
case CART_ACTION_TYPES.SET_CART_ITEMS:
return {
...state,
cartItems: payload,
};
case CART_ACTION_TYPES.SET_IS_CART_OPEN:
return {
...state,
isCartOpen: payload,
};
default:
return state;
}
};
This will allow you to save state temporarily to user's browser using localStorage
Can be very helpful with keeping users cart data without having to store that in a database
Docs:
// the 3 imports that you will need
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
- persistReducer(config, reducer) -> takes in a persist config with required properties of key and storage, 2nd arg is a reducer, typically root
const persistConfig = {
key: "root",
storage: storage,
blacklist: ["user"],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
-
persistStore(store, [config, callback]) -> takes in a
store
object that you want to be persisted, the other array of args is optional -
Now when you create the store you are going to use the persisted objects
// create the store with the persisted reducer
export const store = createStore(
persistedReducer,
undefined,
composedEnhancers
);
export const persistor = persistStore(store);
- Add the PersistGate to your app, under the provider with these properties
<PersistGate loading={null} persistor={persistor}>
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<App />
</BrowserRouter>
</PersistGate>
</Provider>
</React.StrictMode>
);