Again, The main job of react is rendering UI and reacting to user input.
In addition, React evaluate and render JSX, manage states&props , react to events & input, re-evaluate component upon state & prop changes.
Therefore , side effects will be everything execpt for above react roles.
For example, Storing data, send http requests to server, set & manage times and other similar things.
When state changed, react re-run the code. If we make a side effect directly in root, It will definitely re-run our side effect again and may cause something we don't expect.
From these reasons, we gonna use 'useEffect()' hook.
useState() has two arguments.
First argument is a function which should be executed after every component evaluation if the specified dependencies changed
Second argument is the specified dependencies. These makes the function run only if the dependencies changed.
If there isn't dependencies, it will be executed whenever the app start.
If there is empty dependencies, it will be executed once when the app start.
If there is dependencies list, it will be excuted whenver the dependencies changed (you should add "everything" you use in the effect function as a dependency except for state update functions, built-in API or functions and variables & functions which are defined outside component)
We can return a function in useEffect() and that is called by 'clean-up function'.
Clean-up function is not executed at the first time of side effect execution. After that, it will run before side effect execution run.
Let's take this example.
useEffect(() => {
const identifier = setTimeout(() => {
console.log("checking form validity");
setFormIsValid(
enteredEmail.includes("@") && enteredPassword.trim().length > 6
);
}, 500);
return () => {
console.log("clean up");
clearTimeout(identifier);
};
}, [enteredEmail, enteredPassword]);
In this code, i applied identifier which is prevent from tracking every user input and set timeout with 500 milliseconds. Also, there is a return of function (clean-up) which is clear timeout of our identifier.
As a result, whenever user type inside input, clean-up function continue to clear our identifier and when user stops typing, side effect execution will be run.
This is one of the most important hooks.
multiple states -> buggy code -> alternative needed -> Reducer
For example,when email input state and email validity state are belong together Or if one state depends on other state value, it's time to use reducer.
It looks like:
const [state, dispatchFn] = useReducer(reducerFn, initialState, initFn)
Let's see with this example.
const passwordReducer = (state, action) => {
if (action.type === "USER_INPUT") {
return { value: action.val, isValid: action.val.trim().length > 6 };
}
if (action.type === "INPUT_BLUR") {
return { value: state.value, isValid: state.value.trim().length > 6 };
}
return { value: "", isValid: false };
};
const [passwordState, dispatchPassword] = useReducer(passwordReducer, {
value: "",
isValid: false,
});
const passwordChangeHandler = (event) => {
dispatchPassword({ type: "USER_INPUT", val: event.target.value });
setFormIsValid(emailState.isValid && passwordState.value.trim().length > 6);
};
const validatePasswordHandler = () => {
dispatchPassword({ type: "INPUT_BLUR" });
};
If you see the passwordReducer, There will be two argument(state and action). State will be current state and Action is dispatchFunction.
At the first time , if there isn't any action , Reducer return no value and no valid.
In passwordChangeHandler, i gave an object to dispatchFunction(action in reducer). In object, because type is 'USER_INPUT', when it goes into reducer, it will be catched by condition.
validatePasswordHandler is also same mechanism.
useState -> main state management, If there are independent pieces of state, If states updating are easy and limited to a few kind of updates
useReducer -> If you need more power for complex logic, if you dealing with related state inside component, if you have more complex state updates.
When we use?
In bigger app, There will be definitely demand on data of props between other routes.
For example, In product route, user decide to buy an product and move it to a cart. To move it to a cart, we should send data to cart. While react doing so, there will be unneccesary data forwarding.
In that case, we need to send our data directly to where it is needed.
How we use?
import React from "react";
const AuthContext = React.createContext({
isLoggedIn: false,
});
export default AuthContext;
Give provider the values you want to send
return (
<AuthContext.Provider value={{ isLoggedIn: isLoggedIn }}>
<MainHeader onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</AuthContext.Provider>
);
import AuthContext from "../../context/auth-context";
const Navigation = (props) => {
const ctx = useContext(AuthContext);
return (
<nav className={classes.nav}>
<ul>
{ctx.isLoggedIn && (
<li>
<a href="/">Users</a>
</li>
)}
{ctx.isLoggedIn && (
<li>
<a href="/">Admin</a>
</li>
)}
{ctx.isLoggedIn && (
<li>
<button onClick={props.onLogout}>Logout</button>
</li>
)}
</ul>
</nav>
);
};
props -> To send data components
context -> To get data components in specific case
Or
Build a custom context file and manage your code leaner
import React, { useState, useEffect } from "react";
const AuthContext = React.createContext({
isLoggedIn: false,
onLogout: () => {},
onLogin: (email, password) => {},
});
export const AuthContextProvider = (props) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
const storedLoginData = localStorage.getItem("isLoggedIn");
if (storedLoginData === "1") {
return setIsLoggedIn(true);
}
}, []);
const logoutHandler = () => {
localStorage.setItem("isLoggedIn", "0");
setIsLoggedIn(false);
};
const loginHandler = () => {
localStorage.setItem("isLoggedIn", "1");
setIsLoggedIn(true);
};
return (
<AuthContextProvider
value={{
isLoggedIn: isLoggedIn,
onLogout: logoutHandler,
onLogin: loginHandler,
}}
>
{props.children}
</AuthContextProvider>
);
};
export default AuthContext;
It is not optimized for high frequency changes. -> Redux solve this.