Migrate from redux to context

This commit is contained in:
MD. Ariful Alam
2021-09-16 00:30:22 +06:00
parent d5c715e16e
commit 8f167cc940
21 changed files with 157 additions and 446 deletions

View File

@@ -1,26 +1,25 @@
import axios from "axios";
import { Fragment, useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Fragment, useCallback, useContext, useEffect, useState } from "react";
import AvatarCard from "./components/AvatarCard";
import ErrorPage from "./components/ErrorPage";
import ThemeChanger from "./components/ThemeChanger";
import config from "./config";
import moment from 'moment';
import { setLoading } from "./store/slices/loadingSlice";
import { setProfile } from "./store/slices/profileSlice";
import Details from "./components/Details";
import Skill from "./components/Skill";
import Experience from "./components/Experience";
import Education from "./components/Education";
import Project from "./components/Project";
import { setRepo } from "./store/slices/repoSlice";
import Blog from "./components/Blog";
import MetaTags from "./components/MetaTags";
import { LoadingContext } from "./contexts/LoadingContext";
import { ThemeContext } from "./contexts/ThemeContext";
function App() {
const dispatch = useDispatch();
const theme = useSelector(state => state.theme);
const [theme] = useContext(ThemeContext);
const [, setLoading] = useContext(LoadingContext);
const [profile, setProfile] = useState(null);
const [repo, setRepo] = useState(null);
const [error, setError] = useState(null);
const [rateLimit, setRateLimit] = useState(null);
@@ -43,7 +42,7 @@ function App() {
company: data.company ? data.company : ''
}
dispatch(setProfile(profileData));
setProfile(profileData);
})
.then(() => {
let excludeRepo = ``;
@@ -64,7 +63,7 @@ function App() {
.then(response => {
let data = response.data;
dispatch(setRepo(data.items));
setRepo(data.items);
})
.catch((error) => {
handleError(error);
@@ -74,9 +73,9 @@ function App() {
handleError(error);
})
.finally(() => {
dispatch(setLoading(false));
setLoading(false);
});
}, [dispatch])
}, [setLoading])
useEffect(() => {
loadData();
@@ -104,7 +103,7 @@ function App() {
return (
<Fragment>
<MetaTags/>
<MetaTags profile={profile}/>
<div className="fade-in h-screen">
{
@@ -144,20 +143,20 @@ function App() {
<div className="grid grid-cols-1 gap-6">
{
!config.themeConfig.disableSwitch && (
<ThemeChanger />
<ThemeChanger/>
)
}
<AvatarCard />
<Details />
<Skill />
<Experience />
<Education />
<AvatarCard profile={profile}/>
<Details profile={profile}/>
<Skill/>
<Experience/>
<Education/>
</div>
</div>
<div className="lg:col-span-2 col-span-1">
<div className="grid grid-cols-1 gap-6">
<Project />
<Blog />
<Project repo={repo}/>
<Blog/>
</div>
</div>
</div>

View File

@@ -1,16 +1,17 @@
import { useSelector } from "react-redux";
import { fallbackImage, skeleton } from "../helpers/utils";
import LazyImage from "./LazyImage";
import PropTypes from 'prop-types';
import { useContext } from "react";
import { LoadingContext } from "../contexts/LoadingContext";
const AvatarCard = () => {
const profile = useSelector(state => state.profile);
const loading = useSelector(state => state.loading);
const AvatarCard = (props) => {
const [loading] = useContext(LoadingContext);
return (
<div className="card shadow-lg compact bg-base-100">
<div className="grid place-items-center py-8">
{
loading ? (
(loading || !props.profile) ? (
<div className="avatar opacity-90">
<div className="mb-8 rounded-full w-32 h-32">
{
@@ -27,8 +28,8 @@ const AvatarCard = () => {
<div className="mb-8 rounded-full w-32 h-32 ring ring-primary ring-offset-base-100 ring-offset-2">
{
<LazyImage
src={profile.avatar ? profile.avatar : fallbackImage}
alt={profile.name}
src={props.profile.avatar ? props.profile.avatar : fallbackImage}
alt={props.profile.name}
placeholder={
skeleton({
width: 'w-full',
@@ -45,16 +46,16 @@ const AvatarCard = () => {
<div className="text-center mx-auto px-8">
<h5 className="font-bold text-2xl">
{
loading ? (
(loading || !props.profile) ? (
skeleton({ width: 'w-48', height: 'h-8' })
) : <span className="opacity-70">{profile.name}</span>
) : <span className="opacity-70">{props.profile.name}</span>
}
</h5>
<div className="mt-3 text-base-content text-opacity-60">
{
loading ? (
(loading || !props.profile) ? (
skeleton({ width: 'w-48', height: 'h-5' })
) : profile.bio
) : props.profile.bio
}
</div>
</div>
@@ -63,4 +64,8 @@ const AvatarCard = () => {
)
}
AvatarCard.propTypes = {
profile: PropTypes.object
}
export default AvatarCard;

View File

@@ -1,9 +1,9 @@
import { getDevtoArticle, getMediumArticle } from "article-api";
import moment from "moment";
import { Fragment, useEffect, useState } from "react";
import { Fragment, useContext, useEffect, useState } from "react";
import { CgHashtag } from 'react-icons/cg';
import { useSelector } from "react-redux";
import config from "../config";
import { LoadingContext } from "../contexts/LoadingContext";
import { ga, skeleton } from "../helpers/utils";
import LazyImage from "./LazyImage";
@@ -23,7 +23,7 @@ const displaySection = () => {
const Blog = () => {
const [articles, setArticles] = useState(null);
const loading = useSelector(state => state.loading);
const [loading] = useContext(LoadingContext);
useEffect(() => {
if (displaySection()) {

View File

@@ -5,13 +5,14 @@ import { GrLinkedinOption } from 'react-icons/gr';
import { CgDribbble } from 'react-icons/cg';
import { RiPhoneFill } from 'react-icons/ri';
import { FaBehanceSquare, FaBuilding, FaDev, FaFacebook, FaGlobe } from 'react-icons/fa';
import { useSelector } from 'react-redux';
import config from '../config';
import { skeleton } from '../helpers/utils';
import PropTypes from 'prop-types';
import { useContext } from 'react';
import { LoadingContext } from '../contexts/LoadingContext';
const Details = () => {
const profile = useSelector(state => state.profile);
const loading = useSelector(state => state.loading);
const Details = (props) => {
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
@@ -34,24 +35,24 @@ const Details = () => {
<div className="card-body">
<ul className="menu row-span-3 bg-base-100 text-base-content text-opacity-60">
{
loading ? renderSkeleton() : (
(loading || !props.profile) ? renderSkeleton() : (
<>
{
profile && profile.location && (
props.profile.location && (
<li>
<span>
<MdLocationOn className="mr-2" />
{profile.location}
{props.profile.location}
</span>
</li>
)
}
{
profile && profile.company && (
props.profile.company && (
<li>
<span>
<FaBuilding className="mr-2" />
{profile.company}
{props.profile.company}
</span>
</li>
)
@@ -247,4 +248,8 @@ const Details = () => {
)
}
Details.propTypes = {
profile: PropTypes.object
}
export default Details;

View File

@@ -1,10 +1,11 @@
import { useSelector } from "react-redux";
import config from "../config";
import { GoPrimitiveDot } from 'react-icons/go';
import { skeleton } from "../helpers/utils";
import { useContext } from "react";
import { LoadingContext } from "../contexts/LoadingContext";
const Education = () => {
const loading = useSelector(state => state.loading);
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];

View File

@@ -1,10 +1,11 @@
import { useSelector } from "react-redux";
import config from "../config";
import { GoPrimitiveDot } from 'react-icons/go';
import { skeleton } from "../helpers/utils";
import { useContext } from "react";
import { LoadingContext } from "../contexts/LoadingContext";
const Experience = () => {
const loading = useSelector(state => state.loading);
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
@@ -44,9 +45,11 @@ const Experience = () => {
<li>
<div className="pb-0-important mx-3">
<h5 className="card-title">
{loading ? skeleton({width: 'w-32', height: 'h-8'}) : (
<span className="opacity-70">Experience</span>
)}
{
loading ? skeleton({width: 'w-32', height: 'h-8'}) : (
<span className="opacity-70">Experience</span>
)
}
</h5>
</div>
</li>

View File

@@ -1,17 +1,17 @@
import React, { Fragment } from 'react';
import React, { Fragment, useContext } from 'react';
import { Helmet } from "react-helmet-async";
import { useSelector } from 'react-redux';
import config from '../config';
import { isThemeDarkish } from '../helpers/utils';
import PropTypes from 'prop-types';
import { ThemeContext } from '../contexts/ThemeContext';
const MetaTags = () => {
const profile = useSelector(state => state.profile);
const theme = useSelector(state => state.theme);
const MetaTags = (props) => {
const [theme] = useContext(ThemeContext);
return (
<Fragment>
{
profile && (
props.profile && (
<Helmet>
{
config.googleAnalytics.id && (
@@ -33,25 +33,25 @@ const MetaTags = () => {
</script>
)
}
<title>Portfolio of {profile.name}</title>
<title>Portfolio of {props.profile.name}</title>
<meta name="theme-color" content={isThemeDarkish(theme) ? '#000000' : '#ffffff'}/>
<meta name="description" content={profile.bio} />
<meta name="description" content={props.profile.bio} />
<meta itemprop="name" content={`Portfolio of ${profile.name}`} />
<meta itemprop="description" content={profile.bio} />
<meta itemprop="image" content={profile.avatar} />
<meta itemprop="name" content={`Portfolio of ${props.profile.name}`} />
<meta itemprop="description" content={props.profile.bio} />
<meta itemprop="image" content={props.profile.avatar} />
<meta property="og:url" content={typeof config.social.website !== 'undefined' ? config.social.website : ''} />
<meta property="og:type" content="website" />
<meta property="og:title" content={`Portfolio of ${profile.name}`} />
<meta property="og:description" content={profile.bio} />
<meta property="og:image" content={profile.avatar} />
<meta property="og:title" content={`Portfolio of ${props.profile.name}`} />
<meta property="og:description" content={props.profile.bio} />
<meta property="og:image" content={props.profile.avatar} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={`Portfolio of ${profile.name}`} />
<meta name="twitter:description" content={profile.bio} />
<meta name="twitter:image" content={profile.avatar} />
<meta name="twitter:title" content={`Portfolio of ${props.profile.name}`} />
<meta name="twitter:description" content={props.profile.bio} />
<meta name="twitter:image" content={props.profile.avatar} />
</Helmet>
)
}
@@ -59,4 +59,8 @@ const MetaTags = () => {
)
}
MetaTags.propTypes = {
profile: PropTypes.object
}
export default MetaTags;

View File

@@ -1,12 +1,12 @@
import { Fragment } from "react";
import { useSelector } from "react-redux";
import { Fragment, useContext } from "react";
import { ga, languageColor, skeleton } from "../helpers/utils";
import { AiOutlineStar, AiOutlineFork } from 'react-icons/ai';
import config from "../config";
import PropTypes from 'prop-types';
import { LoadingContext } from "../contexts/LoadingContext";
const Project = () => {
const loading = useSelector(state => state.loading);
const repo = useSelector(state => state.repo);
const Project = (props) => {
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
@@ -51,7 +51,7 @@ const Project = () => {
}
const renderProjects = () => {
return repo.map((item, index) => (
return props.repo.map((item, index) => (
<div
className="card shadow-lg compact bg-base-100 cursor-pointer"
key={index}
@@ -146,7 +146,7 @@ const Project = () => {
</div>
<div className="col-span-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{(loading || !repo) ? renderSkeleton() : renderProjects()}
{(loading || !props.repo) ? renderSkeleton() : renderProjects()}
</div>
</div>
</div>
@@ -155,4 +155,8 @@ const Project = () => {
)
}
Project.propTypes = {
repo: PropTypes.array
}
export default Project;

View File

@@ -1,9 +1,10 @@
import { useSelector } from "react-redux";
import { useContext } from "react";
import config from "../config";
import { LoadingContext } from "../contexts/LoadingContext";
import { skeleton } from "../helpers/utils";
const Skill = () => {
const loading = useSelector(state => state.loading);
const [loading] = useContext(LoadingContext);
const renderSkeleton = () => {
let array = [];
@@ -26,9 +27,11 @@ const Skill = () => {
<div className="card-body">
<div className="mx-3">
<h5 className="card-title">
{loading ? skeleton({width: 'w-32', height: 'h-8'}) : (
<span className="opacity-70">Tech Stack</span>
)}
{
loading ? skeleton({width: 'w-32', height: 'h-8'}) : (
<span className="opacity-70">Tech Stack</span>
)
}
</h5>
</div>
<div className="p-3 flow-root">

View File

@@ -1,17 +1,20 @@
import { useDispatch, useSelector } from 'react-redux';
import { setTheme } from '../store/slices/themeSlice';
import config from '../config';
import { skeleton } from '../helpers/utils';
import { AiOutlineControl } from 'react-icons/ai';
import { useContext } from 'react';
import { ThemeContext } from '../contexts/ThemeContext';
import { LoadingContext } from '../contexts/LoadingContext';
const ThemeChanger = () => {
const dispatch = useDispatch();
const theme = useSelector(state => state.theme);
const loading = useSelector(state => state.loading);
const [theme, setTheme] = useContext(ThemeContext);
const [loading] = useContext(LoadingContext);
const changeTheme = (e, selectedTheme) => {
e.preventDefault();
dispatch(setTheme(selectedTheme));
document.querySelector('html').setAttribute('data-theme', selectedTheme);
localStorage.setItem('ezprofileTheme', selectedTheme);
setTheme(selectedTheme);
}
return (
@@ -26,7 +29,9 @@ const ThemeChanger = () => {
}
</h5>
<span className="text-base-content text-opacity-40 capitalize text-sm">
{loading ? skeleton({ width: 'w-16', height: 'h-5' }) : (theme === config.themeConfig.default ? 'Default' : theme)}
{
loading ? skeleton({ width: 'w-16', height: 'h-5' }) : (theme === config.themeConfig.default ? 'Default' : theme)
}
</span>
</div>
<div className="flex-0">

View File

@@ -0,0 +1,15 @@
import { createContext, useState } from "react";
const initialValue = true;
export const LoadingContext = createContext();
export const LoadingProvider = (props) => {
const [loading, setLoading] = useState(initialValue);
return (
<LoadingContext.Provider value={[loading, setLoading]}>
{props.children}
</LoadingContext.Provider>
);
}

View File

@@ -0,0 +1,16 @@
import { createContext, useState } from "react";
import { getInitialTheme } from "../helpers/utils";
const initialValue = getInitialTheme();
export const ThemeContext = createContext();
export const ThemeProvider = (props) => {
const [theme, setTheme] = useState(initialValue);
return (
<ThemeContext.Provider value={[theme, setTheme]}>
{props.children}
</ThemeContext.Provider>
);
}

View File

@@ -1,7 +1,7 @@
import config from "../config";
import colors from './colors.json';
export const getThemeValue = () => {
export const getInitialTheme = () => {
if (config.themeConfig.disableSwitch) {
return config.themeConfig.default;
}

View File

@@ -2,18 +2,20 @@ import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import App from './App';
import { Provider } from 'react-redux';
import reportWebVitals from './reportWebVitals';
import { store } from './store/store';
import { HelmetProvider } from 'react-helmet-async';
import { ThemeProvider } from './contexts/ThemeContext';
import { LoadingProvider } from './contexts/LoadingContext';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<HelmetProvider>
<App/>
</HelmetProvider>
</Provider>
<ThemeProvider>
<LoadingProvider>
<HelmetProvider>
<App/>
</HelmetProvider>
</LoadingProvider>
</ThemeProvider>
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -1,19 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = true;
export const loadingSlice = createSlice({
name: 'loading',
initialState: initialState,
reducers: {
setLoading: (state, action) => {
state = action.payload;
return state;
}
}
})
export const { setLoading } = loadingSlice.actions;
export default loadingSlice.reducer;

View File

@@ -1,19 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = null;
export const profileSlice = createSlice({
name: 'profile',
initialState: initialState,
reducers: {
setProfile: (state, action) => {
state = action.payload;
return state;
}
}
})
export const { setProfile } = profileSlice.actions;
export default profileSlice.reducer;

View File

@@ -1,19 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = null;
export const repoSlice = createSlice({
name: 'repo',
initialState: initialState,
reducers: {
setRepo: (state, action) => {
state = action.payload;
return state;
}
}
})
export const { setRepo } = repoSlice.actions;
export default repoSlice.reducer;

View File

@@ -1,23 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
import { getThemeValue } from '../../helpers/utils';
const initialState = getThemeValue();
export const themeSlice = createSlice({
name: 'theme',
initialState: initialState,
reducers: {
setTheme: (state, action) => {
state = action.payload;
document.querySelector('html').setAttribute('data-theme', state);
localStorage.setItem('ezprofileTheme', state);
return state;
}
}
})
export const { setTheme } = themeSlice.actions;
export default themeSlice.reducer;

View File

@@ -1,16 +0,0 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import loadingSlice from './slices/loadingSlice';
import profileSlice from './slices/profileSlice';
import repoSlice from './slices/repoSlice';
import themeSlice from './slices/themeSlice';
const rootReducer = combineReducers({
profile: profileSlice,
theme: themeSlice,
loading: loadingSlice,
repo: repoSlice,
})
export const store = configureStore({
reducer: rootReducer
})