Skip to content

Commit

Permalink
🚀 Implement useLocalStorage exercise (#104)
Browse files Browse the repository at this point in the history
Co-authored-by: Rohan Gupta <rohan.gupta@gameskraft.com>
  • Loading branch information
DemonDaddy22 and Rohan Gupta authored Nov 25, 2023
1 parent 0a2f64f commit a7da268
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 1,091 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
35. [Toasts](./src/components/Toasts/README.md): You're given HTML and CSS files for a simple toast system, and you need to make the toast system functional using JavaScript.
36. [Sudoku](./src/components/Sudoku/README.md): You're given HTML and CSS files for a Sudoku game based on The [New York Times Number Puzzles](https://www.nytimes.com/puzzles/sudoku), and you need to make the game functional using JavaScript.
37. [UseFetch](./src/components/UseFetch/README.md): Write a `useFetch` custom hook that takes in a required `url` as a string or URL object. This parameter should be directly passed to the native JavaScript `fetch` function.
38. [UseLocalStorage](./src/components/UseLocalStorage/README.md): Write a `useLocalStorage` custom hook that takes in a required `key` as a string, and an optional `initialValue`.

<br />
<br />
Expand Down
1,113 changes: 26 additions & 1,087 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
"@types/react-dom": "^18.0.6",
"axios": "^1.6.0",
"lodash": "^4.17.21",
"node-sass": "^9.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"react-syntax-highlighter": "^15.5.0",
"sass": "^1.63.4",
"sass-loader": "^13.3.2",
"typescript": "^4.8.3",
"web-vitals": "^2.1.4"
},
Expand Down
4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';
import './App.scss';
import UseFetch from './components/UseFetch';
import UseLocalStorage from './components/UseLocalStorage';
import ThemeContextProvider from './context/ThemeContext';

function App() {
return (
<ThemeContextProvider>
<div className='container'>
<UseFetch />
<UseLocalStorage />
</div>
</ThemeContextProvider>
);
Expand Down
24 changes: 24 additions & 0 deletions src/components/UseLocalStorage/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# useLocalStorage

_Tag_: **React Hooks**

Write a `useLocalStorage` custom hook that takes in a required `key` as a string, and an optional `initialValue`.

Calling `useLocalStorage` in a component should save the `initialValue` in localStorage at the given `key` when the component first mounts. If a value already exists at that `key`, the `initialValue` parameter should be ignored.

The `useLocalStorage` function should return an array with the current value as the first element and a setter function as the second element. The setter function should take in a new value as a parameter and update localStorage at the original `key`.

When the setter function is called, the component should re-render, just as it would when a standard piece of state is updated.

Any value added to localStorage should first be passed to `JSON.stringify`. When reading the value from localStorage, `JSON.parse` should be used to parse the original value.

For simplicity, you can asssume the `key` parameter will not change between renders.

### Sample Usage

```jsx
function SaveValues() {
const [value, setValue] = useLocalStorage('name', '');
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
```
32 changes: 32 additions & 0 deletions src/components/UseLocalStorage/codeBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const sampleUsage = `function SaveValues() {
const [value, setValue] = useLocalStorage('name', '');
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}`;

export const solutionCode = `// useLocalStorage hook
import { useCallback, useEffect, useState } from 'react';
const useLocalStorage = (key: string, initialValue?: any) => {
const [value, setValue] = useState<any>(initialValue);
const setValueInStorage = useCallback((valueToSet: any) => {
const stringifiedValue = JSON.stringify(valueToSet);
localStorage.setItem(key, stringifiedValue);
setValue(valueToSet);
}, [key]);
useEffect(() => {
const parsedValue = JSON.parse(localStorage.getItem(key) ?? 'null');
if (!parsedValue) {
localStorage.setItem(key, JSON.stringify(initialValue));
} else {
setValue(parsedValue);
}
}, [key, initialValue]);
return [value, setValueInStorage];
};
export default useLocalStorage;
`;
61 changes: 61 additions & 0 deletions src/components/UseLocalStorage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import classes from './styles.module.scss';
import ProjectPage from '../ProjectPage';
import QuestionDetails from '../QuestionDetails';
import { PROJECT_COLORS } from '../../constants/theme';
import CodeBlock from '../UI/CodeBlock';
import { sampleUsage, solutionCode } from './codeBlock';

const UseLocalStorage: React.FC<Props> = () => {
return (
<ProjectPage background={PROJECT_COLORS.PROJECT38.background} containerClassName={classes.pageContainer}>
<QuestionDetails
title='Use Local Storage'
titleClassName={classes.questionTitle}
containerClassName={classes.questionDetails}
descriptionClassName={classes.questionDescription}>
<p>
Write a <span className={classes.questionDescriptionHighlight}>useLocalStorage</span> custom hook that takes
in a required <span className={classes.questionDescriptionHighlight}>key</span> as a string, and an optional{' '}
<span className={classes.questionDescriptionHighlight}>initialValue</span>.
</p>
<p>
Calling <span className={classes.questionDescriptionHighlight}>useLocalStorage</span> in a component should
save the <span className={classes.questionDescriptionHighlight}>initialValue</span> in localStorage at the
given <span className={classes.questionDescriptionHighlight}>key</span> when the component first mounts. If a
value already exists at that <span className={classes.questionDescriptionHighlight}>key</span>, the{' '}
<span className={classes.questionDescriptionHighlight}>initialValue</span> parameter should be ignored.
</p>
<p>
The <span className={classes.questionDescriptionHighlight}>useLocalStorage</span> function should return an
array with the current value as the first element and a setter function as the second element. The setter
function should take in a new value as a parameter and update localStorage at the original{' '}
<span className={classes.questionDescriptionHighlight}>key</span>.
</p>
<p>
When the setter function is called, the component should re-render, just as it would when a standard piece of
state is updated.
</p>
<p>
Any value added to localStorage should first be passed to{' '}
<span className={classes.questionDescriptionHighlight}>JSON.stringify</span>. When reading the value from
localStorage, <span className={classes.questionDescriptionHighlight}>JSON.parse</span> should be used to parse
the original value.
</p>
<p>
For simplicity, you can asssume the <span className={classes.questionDescriptionHighlight}>key</span>{' '}
parameter will not change between renders.
</p>
<div className={classes.codeWrapper}>
<h3 className={classes.codeBlockHeader}>Sample Usage</h3>
<CodeBlock codeString={sampleUsage} />
</div>
</QuestionDetails>
<section className={classes.solutionContainer}>
<CodeBlock codeString={solutionCode} containerClassName={classes.fullHeight} hideCode />
</section>
</ProjectPage>
);
};

export default UseLocalStorage;
74 changes: 74 additions & 0 deletions src/components/UseLocalStorage/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
@import '../../constants/theme.scss';
@import '../../utils/mixins.scss';

.pageContainer {
@media screen and (min-width: 992px) {
overflow-y: hidden;
}
}

.questionDetails {
@media screen and (min-width: 992px) {
max-width: 40rem;
max-height: 85vh;
overflow-y: auto;
}
}

.questionDescription {
@include spacing (0 0.25rem);
word-break: break-word;
white-space: break-spaces;
}

.questionTitle {
color: $project-header-38;
}

.break {
@include spacing ($margin: 0.5rem 0);
}

.questionDescriptionHighlight {
@include spacing (0 0.25rem);
background-color: $project-highlight-38;
border-radius: 0.25rem;
}

.codeBlock {
@include spacing (0.5rem 0.75rem);
overflow-x: auto;
border-radius: 0.5rem;

@media screen and (min-width: 992px) {
@include spacing (0.75rem 1rem);
}
}

.codeWrapper {
@include spacing (0.5rem 0);
}

.codeBlockHeader {
@include spacing (0.5rem 0);
color: $white;
}

.solutionContainer {
@include spacing (1rem);
border: 1px dotted rgba($color: $white, $alpha: 0.5);
border-radius: 0.25rem;
align-self: stretch;
flex: 1;

@media screen and (min-width: 992px) {
overflow-y: auto;
max-width: 40rem;
max-height: 85vh;
}
}

.fullHeight {
@include spacing (0, 0);
height: 100%;
}
5 changes: 5 additions & 0 deletions src/constants/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,8 @@ $project-36-candidates: #061802;
$project-bg-37: linear-gradient(40deg, #241408 0%, $black 100%);
$project-header-37: #EE7E3D;
$project-highlight-37: #6D1F0C;

// project-38
$project-bg-38: linear-gradient(40deg, #130729 0%, $black 100%);
$project-header-38: #D789F5;
$project-highlight-38: #451168;
3 changes: 3 additions & 0 deletions src/constants/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,7 @@ export const PROJECT_COLORS: { [key: string]: { [key: string]: string } } = {
PROJECT37: {
background: `linear-gradient(40deg, #241408 0%, ${COLORS.BLACK} 100%)`,
},
PROJECT38: {
background: `linear-gradient(40deg, #130729 0%, ${COLORS.BLACK} 100%)`,
},
};
24 changes: 24 additions & 0 deletions src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useCallback, useEffect, useState } from 'react';

const useLocalStorage = (key: string, initialValue?: any) => {
const [value, setValue] = useState<any>(initialValue);

const setValueInStorage = useCallback((valueToSet: any) => {
const stringifiedValue = JSON.stringify(valueToSet);
localStorage.setItem(key, stringifiedValue);
setValue(valueToSet);
}, [key]);

useEffect(() => {
const parsedValue = JSON.parse(localStorage.getItem(key) ?? 'null');
if (!parsedValue) {
localStorage.setItem(key, JSON.stringify(initialValue));
} else {
setValue(parsedValue);
}
}, [key, initialValue]);

return [value, setValueInStorage];
};

export default useLocalStorage;
2 changes: 1 addition & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LOREM_IPSUM } from "../constants";
import { LOREM_IPSUM } from '../constants';

/**
*
Expand Down

0 comments on commit a7da268

Please sign in to comment.