I'm trying to maintain a track of use inputs with react's use state.
for some reason this is quite difficult.
the function i'm using right now is,
function handleClick(e) {
setAnswers([...answers, answer ]);
// setTheArray([, newElement]);
setAnswer()
setTime(true);
setStage(Stage + 1);
e.preventDefault();
console.log("The link was clicked.");
console.log(answers);
}
the input section looks like this,
<div className=" items-center ">
<label for="answer" className="sr-only font-thin px-2 pb-4 text-lg">
Answer
</label>
<input
onChange={(e) => setAnswer(e.target.value)}
type="text"
value={answer}
name="answer"
id="answer"
className=" items-center shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
placeholder="Enter Here"
/>
</div>
and the button
<button
onClick={handleClick}
className="bg-blue-500 hover:bg-blue-700shadow-xl font-semibold rounded-full fixed right-8 bottom-20 text-xl px-8 py-3 sm:right-16 sm:bottom-16 text-white sm:text-xl sm:py-3 sm:px-7 flex items-center focus:outline-none"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="-ml-1 mr-2 h-5 w-5"
>
<circle cx="12" cy="12" r="10"></circle>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>
</svg>
<span>Submit </span>
</button>
Essentially I want to keep track of the user inputs over time - I could just create a class based component, but i'd rather just use functions if possible.
This is the entire component,
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { requireAuth } from "util/auth.js";
import Sidebar_Users from "components/dashboard/Sidebar/Sidebar_Users";
import MindVaultSection from "components/dashboard/MindVault/MindVaultSection";
import BottomNavigation from "../../components/dashboard/BottomNavigation/BottomNavigation";
/*
This page takes a measurement of someone's working memory,
being how many digits they
are able to remember in a set time period.
To do this there needs to be:
- Digits made randomly,
- Timer,
- Input for user answers based on the progress through the task,
- Record of medication, or lack thereof
Ideally doing 6 tests total - sending data at each point if possible, perhaps just as a total query at end though.
User Flow
- Initial Component
- Component for remembering Text
- Component for entering
- Rest Component
*/
function makeid(length) {
var result = "";
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function ButtonBaseline(props) {
const { time, Colour, handleClick, Stage, onSubmit } = props;
if (time == false)
return (
<button
onClick={handleClick}
className="bg-blue-500 hover:bg-blue-700shadow-xl font-semibold rounded-full fixed right-8 bottom-20 text-xl px-8 py-3 sm:right-16 sm:bottom-16 text-white sm:text-xl sm:py-3 sm:px-7 flex items-center focus:outline-none"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="-ml-1 mr-2 h-5 w-5"
>
<circle cx="12" cy="12" r="10"></circle>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>
</svg>
<span>Get Started</span>
</button>
);
else if (time == "input" && Stage < 6)
return (
<button
onClick={handleClick}
className="bg-blue-500 hover:bg-blue-700shadow-xl font-semibold rounded-full fixed right-8 bottom-20 text-xl px-8 py-3 sm:right-16 sm:bottom-16 text-white sm:text-xl sm:py-3 sm:px-7 flex items-center focus:outline-none"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="-ml-1 mr-2 h-5 w-5"
>
<circle cx="12" cy="12" r="10"></circle>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>
</svg>
<span>Next Step </span>
</button>
);
else if (time == "input" && Stage == 6)
return (
<button
onClick={handleClick}
className="bg-blue-500 hover:bg-blue-700shadow-xl font-semibold rounded-full fixed right-8 bottom-20 text-xl px-8 py-3 sm:right-16 sm:bottom-16 text-white sm:text-xl sm:py-3 sm:px-7 flex items-center focus:outline-none"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="-ml-1 mr-2 h-5 w-5"
>
<circle cx="12" cy="12" r="10"></circle>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>
</svg>
<span>Submit </span>
</button>
);
else
return (
<div className="bg-blue-500 hover:bg-blue-700shadow-xl font-semibold rounded-full fixed right-8 bottom-20 text-xl px-8 py-3 sm:right-16 sm:bottom-16 text-white sm:text-xl sm:py-3 sm:px-7 flex items-center focus:outline-none">
Waiting!
</div>
);
}
function Baseline(props) {
const [Stage, setStage] = useState(0); // walkthrough Status
const [time, setTime] = useState(false); //timer off, on, or in input stage.
const [answer, setAnswer] = useState();
const [answers, setAnswers] = useState([]);
// RandomString generates a random string of length n
var numbers = makeid(0 + 2 * Stage);
//qSLeD9D0PlSE
useEffect(() => {
//this sets the time to wait, being 3 seconds
if (time) {
const timeout = setTimeout(() => {
// do anything, this block runs after the timeout has "expired"
// could even set state
setTime("input");
console.log("The timeout was called.");
}, 3000); // timeout expires in 6000 ms
// make sure to clear the timeout on component unmount to avoid memory loss issues
return () => clearTimeout(timeout);
// this callback function runs only on component unmount, not re-renders
}
}, [time]);
function handleOnChange(e) {
setAnswer(e.target.value);
}
function handleClick(e) {
setAnswers([...answers, answer ]);
// setTheArray([, newElement]);
setAnswer()
setTime(true);
setStage(Stage + 1);
e.preventDefault();
console.log("The link was clicked.");
console.log(answers);
}
let testInput;
if (time == true) {
testInput = (
<>
<div>Stage {Stage}</div>
<div className="underline font-light items-center justify-center">
Hold the numbers in your head!
</div>
<div className="uppercase bg-white tracking-widest text-center font-bold text-5xl shadow p-4 items-center">
{numbers}
</div>
</>
);
} else if (time == "input") {
testInput = (
<>
<div>Stage {Stage}</div>
<div className="font-thin px-2 pb-4 text-lg">Enter Numbers</div>
<div className="bg-white rounded-lg shadow p-4 items-center justify-center">
<div className=" items-center ">
<label for="answer" className="sr-only font-thin px-2 pb-4 text-lg">
Answer
</label>
<input
onChange={handleOnChange}
type="text"
value={answer}
name="answer"
id="answer"
className=" items-center shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
placeholder="Enter Here"
/>
</div>
</div>
</>
);
} else if (time == false) {
testInput = (
<>
<p className="text-lg leading-7 text-gray-500 mb-5">
You are about to do a small short term memory test. A few letters will
flash on your computer monitor for 3 seconds. Your job is to write
down as many letters as you can remember
</p>
</>
);
}
return (
<>
<Sidebar_Users dashboard={"Progress Map"}>
<div className="py-8 min-h-full container mx-auto ">
<div className="mt-2 mb-8 text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:leading-10">
Working Memory Test
</div>
{testInput}
<ButtonBaseline
time={time}
Stage={Stage}
handleClick={handleClick}
// onSubmit={onSubmit}
/>
</div>
</Sidebar_Users>
</>
);
}
export default Baseline;
Cheers!
Here is how you would keep track of all your answers. Since you don't have all your code dealing with stages I can't make this answer more specific
but, if this is not enough letme know where to elaborate.
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [answers, setAnswers] = useState([]);
const [answer, setAnswer] = useState("");
const [isSubmit, setIsSubmit] = useState(false);
useEffect(() => {
if (answers.length === 6) {
//here you would fire your submit to server
setIsSubmit(true);
}
}, [answers]);
function handleClick(e) {
if (!isSubmit) setAnswers([...answers, answer]);
}
function handleOnChange(e) {
setAnswer(e.target.value);
}
return (
<div className="App">
<input value={answer} onChange={handleOnChange} />
<button onClick={(e) => handleClick(e)}>Submit</button>
<ul>
{answers.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
{isSubmit ? "Has Submitted" : "Has not Submitted"}
</div>
);
}
Update: Added some stage logic
Related
I have a Rails 7 app, where users can upload photos to a post. I've used Javascript (client side) to create the drag and drop upload file upload field, but it doesn't work when the page is loaded the first time. If you refresh the page, it works as expected.
I think this is something to do with Turbo, but I don't know how to fix it. I have tried adding <meta name="turbo-visit-control" content="reload"> but with no success.
Any help would be appreciated.
my file upload form and javascript:
<p class="text-2xl font-bold mb-4 text-blue-600 xl:pl-9">Add Some Photos</p>
<p class="text-l font-semibold mb-7 xl:pl-9">You can upload up to ten photos of your item.</p>
<article aria-label="File Upload Modal" class="xl:h-2/5 relative flex flex-col bg-white shadow-xl rounded-md xl:m-7" ondrop="dropHandler(event);" ondragover="dragOverHandler(event);" ondragleave="dragLeaveHandler(event);" ondragenter="dragEnterHandler(event);">
<div id="overlay" class="w-full h-full absolute top-0 left-0 pointer-events-none z-50 flex flex-col items-center justify-center rounded-md">
<i>
<svg class="fill-current w-12 h-12 mb-3 text-blue-700" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M19.479 10.092c-.212-3.951-3.473-7.092-7.479-7.092-4.005 0-7.267 3.141-7.479 7.092-2.57.463-4.521 2.706-4.521 5.408 0 3.037 2.463 5.5 5.5 5.5h13c3.037 0 5.5-2.463 5.5-5.5 0-2.702-1.951-4.945-4.521-5.408zm-7.479-1.092l4 4h-3v4h-2v-4h-3l4-4z" />
</svg>
</i>
<p class="text-lg text-blue-700">Drop files to upload</p>
</div>
<section class="h-full overflow-auto p-8 w-full h-full flex flex-col">
<header class="border-dashed border-2 border-gray-400 py-12 flex flex-col justify-center items-center">
<p class="mb-3 font-semibold flex flex-wrap justify-center">
<span>Drag and drop your</span> <span>files anywhere or</span>
</p>
<%= f.file_field :photos, multiple: true, class: "hidden", id: "hidden-input", name: "part[photos][]" %>
<button id="button" type="button" class="mt-2 rounded-lg px-3 py-1 bg-blue-600 hover:bg-blue-700 font-bold text-white focus:shadow-outline focus:outline-none">
Upload a file
</button>
</header>
<h1 class="pt-8 pb-3 font-semibold sm:text-lg text-blue-600">
To Upload
</h1>
<ul id="gallery" class="flex flex-1 flex-wrap -m-1">
<li id="empty" class="h-full w-full text-center flex flex-col items-center justify-center items-center">
<img class="mx-auto w-32" src="https://user-images.githubusercontent.com/507615/54591670-ac0a0180-4a65-11e9-846c-e55ffce0fe7b.png" alt="no data" />
<span class="text-small">No files selected.</span>
</li>
</ul>
</section>
</article>
<template id="file-template">
<li class="block p-1 w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/6 xl:w-1/8 h-24">
<article tabindex="0" class="group w-full h-full rounded-md focus:outline-none focus:shadow-outline elative bg-gray-100 cursor-pointer relative shadow-sm">
<img alt="upload preview" class="img-preview hidden w-full h-full sticky object-cover rounded-md bg-fixed" />
<section class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
<h1 class="flex-1 group-hover:text-blue-800"></h1>
<div class="flex">
<span class="p-1 text-blue-800">
<i>
<svg class="fill-current w-4 h-4 ml-auto pt-1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M15 2v5h5v15h-16v-20h11zm1-2h-14v24h20v-18l-6-6z" />
</svg>
</i>
</span>
<p class="p-1 size text-xs text-gray-700"></p>
<button class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md text-gray-800">
<svg class="pointer-events-none fill-current w-4 h-4 ml-auto" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path class="pointer-events-none" d="M3 6l3 18h12l3-18h-18zm19-4v2h-20v-2h5.711c.9 0 1.631-1.099 1.631-2h5.316c0 .901.73 2 1.631 2h5.711z" />
</svg>
</button>
</div>
</section>
</article>
</li>
</template>
<template id="image-template">
<li class="block p-1 w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/6 xl:w-1/8 h-24">
<article tabindex="0" class="group hasImage w-full h-full rounded-md focus:outline-none focus:shadow-outline bg-gray-100 cursor-pointer relative text-transparent hover:text-white shadow-sm">
<img alt="upload preview" class="img-preview w-full h-full sticky object-cover rounded-md bg-fixed" />
<section class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
<h1 class="flex-1"></h1>
<div class="flex">
<span class="p-1">
<i>
<svg class="fill-current w-4 h-4 ml-auto pt-" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M5 8.5c0-.828.672-1.5 1.5-1.5s1.5.672 1.5 1.5c0 .829-.672 1.5-1.5 1.5s-1.5-.671-1.5-1.5zm9 .5l-2.519 4-2.481-1.96-4 5.96h14l-5-8zm8-4v14h-20v-14h20zm2-2h-24v18h24v-18z" />
</svg>
</i>
</span>
<p class="p-1 size text-xs"></p>
<button class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md">
<svg class="pointer-events-none fill-current w-4 h-4 ml-auto" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path class="pointer-events-none" d="M3 6l3 18h12l3-18h-18zm19-4v2h-20v-2h5.711c.9 0 1.631-1.099 1.631-2h5.316c0 .901.73 2 1.631 2h5.711z" />
</svg>
</button>
</div>
</section>
</article>
</li>
</template>
<script>
const photos = [];
// Get a reference to the file input element
const fileInput = document.getElementById('hidden-input');
// Listen for changes to the file input element
fileInput.addEventListener('change', function() {
// Get the list of selected files
const files = fileInput.files;
// Loop through the selected files
for (let i = 0; i < files.length; i++) {
// Add the file to the photos array
photos.push(files[i]);
}
});
const fileTempl = document.getElementById("file-template"),
imageTempl = document.getElementById("image-template"),
empty = document.getElementById("empty");
// use to store pre selected files
let FILES = {};
// check if file is of type image and prepend the initialied
// template to the target element
function addFile(target, file) {
const isImage = file.type.match("image.*"),
objectURL = URL.createObjectURL(file);
const clone = isImage
? imageTempl.content.cloneNode(true)
: fileTempl.content.cloneNode(true);
clone.querySelector("h1").textContent = file.name;
clone.querySelector("li").id = objectURL;
clone.querySelector(".delete").dataset.target = objectURL;
clone.querySelector(".size").textContent =
file.size > 1024
? file.size > 1048576
? Math.round(file.size / 1048576) + "mb"
: Math.round(file.size / 1024) + "kb"
: file.size + "b";
isImage &&
Object.assign(clone.querySelector("img"), {
src: objectURL,
alt: file.name
});
empty.classList.add("hidden");
target.prepend(clone);
FILES[objectURL] = file;
}
const gallery = document.getElementById("gallery"),
overlay = document.getElementById("overlay");
// click the hidden input of type file if the visible button is clicked
// and capture the selected files
const hidden = document.getElementById("hidden-input");
document.getElementById("button").onclick = () => hidden.click();
hidden.onchange = (e) => {
for (const file of e.target.files) {
addFile(gallery, file);
}
};
// use to check if a file is being dragged
const hasFiles = ({ dataTransfer: { types = [] } }) =>
types.indexOf("Files") > -1;
// use to drag dragenter and dragleave events.
// this is to know if the outermost parent is dragged over
// without issues due to drag events on its children
let counter = 0;
// reset counter and append file to gallery when file is dropped
function dropHandler(ev) {
ev.preventDefault();
for (const file of ev.dataTransfer.files) {
addFile(gallery, file);
overlay.classList.remove("draggedover");
counter = 0;
}
}
// only react to actual files being dragged
function dragEnterHandler(e) {
e.preventDefault();
if (!hasFiles(e)) {
return;
}
++counter && overlay.classList.add("draggedover");
}
function dragLeaveHandler(e) {
1 > --counter && overlay.classList.remove("draggedover");
}
function dragOverHandler(e) {
if (hasFiles(e)) {
e.preventDefault();
}
}
// event delegation to caputre delete events
// fron the waste buckets in the file preview cards
gallery.onclick = ({ target }) => {
if (target.classList.contains("delete")) {
const ou = target.dataset.target;
document.getElementById(ou).remove(ou);
gallery.children.length === 1 && empty.classList.remove("hidden");
delete FILES[ou];
}
};
function addFilesToPhotosArray() {
// Get the gallery element
const gallery = document.getElementById("gallery");
// Get the selected files from the gallery
const selectedFiles = gallery.querySelectorAll(".selected");
// Get the photos array from the input field
const photosArray = document.getElementById("hidden-input").value;
// Add the selected files to the photos array
for (const selectedFile of selectedFiles) {
photosArray.push(selectedFile);
}
// Update the input field with the new photos array
document.getElementById("hidden-input").value = photosArray;
}
// clear entire selection
document.getElementById("cancel").onclick = () => {
while (gallery.children.length > 0) {
gallery.lastChild.remove();
}
FILES = {};
empty.classList.remove("hidden");
gallery.append(empty);
};
</script>
</div>
I am attempting to set up slot machine-style animation using Vue 3 TailwindCSS and HeadlessUI. So far, I have a basic green square set up to slide in from top and slide out the bottom based on cycles in a for-loop called when clicking the "click to transition" button. The resetIsShowing() function also handles the randomization of a sequential array (1-10). My goal is to display one of the 10 random numbers on the green square on each cycle. To do this, I refactored the main div displaying the square to pass a random number on each cycle:
<div v-for="number in numbers" :key="number.id" class="h-full w-full rounded-md bg-green-500 shadow-lg">
{{ number }}
</div>
But when I add list property to the above div, then I get the following error:
The current component <TransitionChild /> is rendering a "template".
However we need to passthrough the following props:
- ref
If I change "template" to "ref" in the TransitionRoot, then I lose the slide down effect that I am trying to achieve. How can I set this up so that numbers appear on the square on each cycle?
Here is the full code:
<template>
<div class="flex flex-col items-center py-16">
<div class="h-72 w-72">
<div class="flex justify-center items-center w-full h-full p-4 text-9xl rounded-md shadow-lg border-solid border-2 border-sky-500">
<TransitionRoot
appear
:show="isShowing"
as="template"
enter="transition ease-in-out duration-300 transform"
enter-from="-translate-y-full opacity-0"
enter-to="translate-y-0 opacity-100"
leave="transition ease-in-out duration-300 transform"
leave-from="translate-y-0 opacity-100"
leave-to="translate-y-full opacity-0"
>
<div v-for="number in numbers" :key="number.id" class="h-full w-full rounded-md bg-green-500 shadow-lg">
{{ number }}
</div>
</TransitionRoot>
</div>
</div>
<button
#click="resetIsShowing"
class="mt-8 flex transform items-center rounded-full bg-black bg-opacity-20 px-3 py-2 text-sm font-medium text-white transition hover:scale-105 hover:bg-opacity-30 focus:outline-none active:bg-opacity-40"
>
<svg viewBox="0 0 20 20" fill="none" class="h-5 w-5 opacity-70">
<path
d="M14.9497 14.9498C12.2161 17.6835 7.78392 17.6835 5.05025 14.9498C2.31658 12.2162 2.31658 7.784 5.05025 5.05033C7.78392 2.31666 12.2161 2.31666 14.9497 5.05033C15.5333 5.63385 15.9922 6.29475 16.3266 7M16.9497 2L17 7H16.3266M12 7L16.3266 7"
stroke="currentColor"
stroke-width="1.5"
/>
</svg>
<span class="ml-3">Click to transition</span>
</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { TransitionRoot } from '#headlessui/vue'
const isShowing = ref(true)
const numbers = ref([])
const numberArray = ref(Array.from({length: 10}, (e, i)=> i + 1))
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function resetIsShowing() {
for (let i = 0; i < numberArray.value.length; i++) {
const index = Math.floor(Math.random() * numberArray.value.length)
await sleep(800)
isShowing.value = false
setTimeout(() => {
isShowing.value = true
}, 500)
numbers.value = numberArray.value.filter(r => r === (index + 1))
}
}
</script>
RESOLVED
<template>
<div class="flex flex-col items-center py-16">
<div class="h-72 w-72">
<div class="flex justify-center items-center w-full h-full p-4 rounded-md shadow-lg border-solid border-2 border-sky-500">
<TransitionRoot
appear
:show="isShowing"
as="template"
enter="transition ease-in-out duration-[400ms]"
enter-from="-translate-y-full opacity-0"
enter-to="translate-y-0 opacity-100"
leave="transition ease-in-out duration-[400ms]"
leave-from="translate-y-0 opacity-100"
leave-to="translate-y-full opacity-0"
>
<div class="h-full w-full rounded-md pt-16">
<span v-if="numbers > 0" class="text-9xl">
{{ numbers }}
</span>
</div>
</TransitionRoot>
</div>
</div>
<button
#click="resetIsShowing"
class="mt-8 flex transform items-center rounded-full bg-black bg-opacity-20 px-3 py-2 text-sm font-medium text-white transition hover:scale-105 hover:bg-opacity-30 focus:outline-none active:bg-opacity-40"
>
<svg viewBox="0 0 20 20" fill="none" class="h-5 w-5 opacity-70">
<path
d="M14.9497 14.9498C12.2161 17.6835 7.78392 17.6835 5.05025 14.9498C2.31658 12.2162 2.31658 7.784 5.05025 5.05033C7.78392 2.31666 12.2161 2.31666 14.9497 5.05033C15.5333 5.63385 15.9922 6.29475 16.3266 7M16.9497 2L17 7H16.3266M12 7L16.3266 7"
stroke="currentColor"
stroke-width="1.5"
/>
</svg>
<span class="ml-3">Click to transition</span>
</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { TransitionRoot } from '#headlessui/vue'
const isShowing = ref(true)
const numbers = ref([])
const numberArray = ref(Array.from({length: 10}, (e, i)=> i + 1))
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function resetIsShowing() {
for (let i = 0; i < numberArray.value.length; i++) {
await sleep(1200)
const index = Math.floor(Math.random() * numberArray.value.length)
// const index = numberArray.value[i]
numbers.value = numberArray.value.filter(r => r === (index + 1))[0]
// numbers.value = numberArray.value.filter(r => r === (index))[0]
console.log(numbers.value)
isShowing.value = false
setTimeout(() => {
isShowing.value = true
}, 500)
}
}
</script>
Using Map method to with function to Set State to Change url of video. So whenever i click on any video from list it change the state hence my videos are change in Player .
The problem is that i want to have two button next and Previous to Show previous & next video, i dont have any idea how to do that Here is My Code
// import React from "react";
import React, { useState } from "react";
import ReactPlayer from "react-player";
const List = [
{
title: "The Shawshank Redemption",
innerList: [
{
innertitle: "1 Two imprisoned",
describe:"1 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662112952/How%20to%20smart%20money%20concepts/videoplayback_2_uoihtg.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2m",
},
{
innertitle: "2 Two imprisoned",
describe:"2 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662099142/How%20to%20smart%20money%20concepts/videoplayback_dvjulu.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2h 22m",
},
{
innertitle: "3 Two imprisoned",
describe:"3 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662112893/How%20to%20smart%20money%20concepts/videoplayback_aw5zoh.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2h 22m",
},
],
},
{
title: "The Shawshank Redemption",
innerList: [
{
innertitle: "1 Two imprisoned",
describe:"1 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662112952/How%20to%20smart%20money%20concepts/videoplayback_2_uoihtg.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2h 22m",
},
{
innertitle: "2 Two imprisoned",
describe:"2 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662099142/How%20to%20smart%20money%20concepts/videoplayback_dvjulu.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2h 22m",
},
{
innertitle: "3 Two imprisoned",
describe:"3 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662112893/How%20to%20smart%20money%20concepts/videoplayback_aw5zoh.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2h 22m",
},
],
},
{
title: "The Shawshank Redemption",
innerList: [
{
innertitle: "1 Two imprisoned",
describe:"1 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662112952/How%20to%20smart%20money%20concepts/videoplayback_2_uoihtg.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2h 22m",
},
{
innertitle: "2 Two imprisoned",
describe:"2 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662099142/How%20to%20smart%20money%20concepts/videoplayback_dvjulu.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2h 22m",
},
{
innertitle: "3 Two imprisoned",
describe:"3 Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://res.cloudinary.com/netdesignr-ltd/video/upload/v1662112893/How%20to%20smart%20money%20concepts/videoplayback_aw5zoh.mp4",
poster:
"https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE#._V1_.jpg",
time: "2h 22m",
},
],
},
];
// when click on previous button show previous video
export function WatchList() {
const [previos, setPrevios] = useState(false);
const [list, setList] = React.useState({
title: "The Shawshank Redemption",
innerTitle: "Two imprisoned",
describe:"Client has methods for creating new data and offers a type-safe way to do it. In this lesson, we take a look at how to add new records in our database by hitting an API routeRehan1",
url: "https://www.youtube.com/watch?v=6hB3S9bIaco",
time: "2h 22m",
});
const handleVideoData = (data: any,index:any) => {
setList(data);
};
const previousVideo = () => {
}
return (
<div className="mx-auto w-full bg-smart-60 p-2">
<div className="mx-auto flex flex-col md:flex-row">
<div className="p-8 md:grid-cols-3">
<div className="h-96 w-full overflow-auto bg-smart-50 px-4 md:h-screen md:w-[320px] ">
<div>
<div className="mt-5 flex justify-center">
<img src="/logo.webp" alt="logo" width={200} height={200} />
</div>
<div className="-m-4 mb-4 border-b border-blue-900 px-4 py-6"></div>
</div>
{List.map((data, i) => (
<div className="mb-6" key={i}>
<div className="p-2">
<p className="font-semibold tracking-wider text-smart-70">
{data.title}
</p>
</div>
{data.innerList.map((data, i) => (
<ul key={i}>
<button
className="w-full"
onClick={() => {
handleVideoData(data,i);
}}
>
<li className="group mb-2 rounded-lg border border-transparent bg-gradient-to-br from-transparent to-transparent px-2 py-3 transition duration-200 ease-in-out hover:from-blue-500 hover:to-blue-900">
<div className="flex">
<img
className="m-auto mr-4 w-24 rounded-md"
src="/thumbnail.webp"
alt="imageslist"
/>
<div className="w-full text-left">
<div className="">
<p className="mr-2 text-smart-70">
{data.innertitle}
</p>
</div>
<p className="inline-block text-sm text-gray-500 group-hover:text-gray-300">
{data.time}
</p>
</div>
</div>
</li>
</button>
</ul>
))}
</div>
))}
</div>
</div>
<div className="grid md:grid-cols-9 ">
<div className="col-span-12 lg:col-span-9">
<section className="sticky top-0 mb-6">
<nav className="bg-mid-deep flex flex-col justify-between rounded-xl bg-opacity-30 px-6 py-4 text-gray-200 md:flex-row">
<section className="my-auto">
<h1>
<span className="font-semibold text-gray-200">
{list.title}
</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
className="inline-block w-5"
>
<path
fillRule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clipRule="evenodd"
></path>
</svg>
<span className="text-gray-400">
{list.innerTitle}
</span>
</h1>
</section>
<section className="mt-2 flex w-full md:mt-0 md:justify-end xl:w-1/3">
<button className="mr-2 flex flex-row rounded-xl border border-blue-900 px-4 py-2 text-left text-xs text-gray-200 hover:text-blue-300 sm:text-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
className="my-auto mr-2 inline-block w-4"
>
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"></path>
<path
fillRule="evenodd"
d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z"
clipRule="evenodd"
></path>
</svg>
<span className="my-auto">Show Transcripts</span>
</button>
<button className="flex rounded-xl border border-blue-900 px-4 py-2 text-sm text-gray-200 hover:text-blue-300">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
aria-hidden="true"
className="my-auto mr-2 inline-block w-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
></path>
</svg>
<span className="my-auto">Log in</span>
</button>
</section>
</nav>
</section>
<div className="sticky top-20 grid grid-cols-9 gap-6">
<section className="col-span-9">
<div className="sticky top-20">
<section className="w-full rounded-xl">
<section className="flex">
<ReactPlayer
url={list.url}
controls={true}
width="100%"
height="600px"
/>
</section>
<section className="flex justify-between bg-smart-50 px-4 py-8">
<div className="rounded-md ">
<p className="text-xl text-white sm:text-3xl">
{list.innerTitle}
</p>
<p className="mt-1 text-gray-500">{list.time}</p>
<div className="mt-4">
<p className="text-sm text-gray-400 sm:text-lg">
{list.describe}
</p>
</div>
</div>
<div className="my-auto flex ">
<button>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
aria-hidden="true"
className="mr-2 inline-block w-8 cursor-pointer text-gray-400 hover:text-red-400"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
></path>
</svg>
</button>
<span className="my-auto text-sm text-gray-400">0</span>
</div>
</section>
</section>
<section className="mt-4 rounded-md bg-smart-50">
<section className="bg-mid-deep flex justify-between rounded-xl bg-opacity-30 px-6 py-4">
<button onClick={() => previousVideo()} className="rounded-xl border border-blue-500 px-4 py-2 text-sm text-gray-200 hover:text-blue-300">
← Previous
</button>
<button className="rounded-xl border border-blue-500 px-4 py-2 text-sm text-gray-200 hover:text-blue-300">
Next →
</button>
</section>
</section>
</div>
</section>
</div>
</div>
</div>
</div>
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I think this task can be accomplished using a simple index:
const [index, setIndex] = useState(0);
const previousVideo = () => {
const newIndex = index > 0 ? index - 1 : 0;
setIndex(newIndex);
setList(List[index]);
};
Of course, you will have to adapt the data from List to the one in list, but that should be easy.
I'm a React newbie, came across an interesting challenge to create "fake ecommerce page" with this api.
My Products.js file is this. I get api response and save it to a state(console logging it in one line to make sure it's working). You click on a SVG icon of + sign and that item gets "added" to the cart(don't want to add whole item for now, but just make it seems like it's adding it with setCartItems(cartItems + 1) so only number increases). Only thing is that the cart icon is in Header.js component, and I need to pass it there somehow. I've tried with props but couldn't get it to work.
Any help is very much appreciated since I want to learn from your code.
import React from 'react'
import {useState, useEffect} from 'react'
import axios from 'axios'
import plus from '../images/plus.svg'
export default function Products(props) {
const [products, setProducts] = useState([])
const [cartItems, setCartItems] = useState(0)
const updateCart = () => {
setCartItems(cartItems + 1)
console.log(cartItems)
}
useEffect(() => {
axios({
method: 'GET',
url: 'https://api.escuelajs.co/api/v1/products'
}).then((response) => {
setProducts(response.data)
console.log(response.data)
})
}, [])
return (
<section className="bg-white py-8">
<div className="container mx-auto flex items-center flex-wrap pt-4 pb-12">
<nav id="store" className="w-full z-30 top-0 px-6 py-1">
<div
className="w-full container mx-auto flex flex-wrap items-center justify-between mt-0 px-2 py-3">
<a className="uppercase tracking-wide no-underline hover:no-underline font-bold text-gray-800 text-xl "
href="#">
Store
</a>
<div className="flex items-center" id="store-nav-content">
<a className="pl-3 inline-block no-underline hover:text-black" href="#">
<svg className="fill-current hover:text-black" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24">
<path d="M7 11H17V13H7zM4 7H20V9H4zM10 15H14V17H10z"></path>
</svg>
</a>
<a className="pl-3 inline-block no-underline hover:text-black" href="#">
<svg className="fill-current hover:text-black" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24">
<path
d="M10,18c1.846,0,3.543-0.635,4.897-1.688l4.396,4.396l1.414-1.414l-4.396-4.396C17.365,13.543,18,11.846,18,10 c0-4.411-3.589-8-8-8s-8,3.589-8,8S5.589,18,10,18z M10,4c3.309,0,6,2.691,6,6s-2.691,6-6,6s-6-2.691-6-6S6.691,4,10,4z"></path>
</svg>
</a>
</div>
</div>
</nav>
{
products.slice(0, 20).map((product) => {
return (
<div className="w-full md:w-1/3 xl:w-1/4 p-6 flex flex-col" key={product.id}>
<div>
<img className="hover:grow hover:shadow-lg" src={product.category.image}
alt={product.description}/>
<div className="pt-3 flex items-center justify-between">
<p className="uppercase font-bold text-lg">{product.title}</p>
<img src={plus} width='20px' onClick={updateCart} alt={product.description}/>
</div>
<p className="pt-1 text-left text-gray-900">{product.price} €</p>
</div>
</div>
)
}
)
}
</div>
</section>
)
}
This is my Header.js file.
import React from 'react'
export default function Header() {
return (
<nav id="header" className="w-full z-30 top-0 py-1">
<div className="w-full container mx-auto flex flex-wrap items-center justify-between mt-0 px-6 py-3">
<label className="cursor-pointer md:hidden block">
<svg className="fill-current text-gray-900" xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 20 20">
<title>menu</title>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
</svg>
</label>
<input className="hidden" type="checkbox" id="menu-toggle" />
<div className="hidden md:flex md:items-center md:w-auto w-full order-3 md:order-1" id="menu">
<nav>
<ul className="md:flex items-center justify-between text-base text-gray-700 pt-4 md:pt-0">
<li><a className="inline-block no-underline hover:text-black hover:underline py-2 px-4" href="#">Shop</a></li>
<li><a className="inline-block no-underline hover:text-black hover:underline py-2 px-4" href="#">About</a></li>
</ul>
</nav>
</div>
<div className="order-1 md:order-2">
<a className="flex items-center tracking-wide no-underline hover:no-underline font-bold text-gray-800 text-xl " href="#">
<svg className="fill-current text-gray-800 mr-2" xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 24 24">
<path d="M5,22h14c1.103,0,2-0.897,2-2V9c0-0.553-0.447-1-1-1h-3V7c0-2.757-2.243-5-5-5S7,4.243,7,7v1H4C3.447,8,3,8.447,3,9v11 C3,21.103,3.897,22,5,22z M9,7c0-1.654,1.346-3,3-3s3,1.346,3,3v1H9V7z M5,10h2v2h2v-2h6v2h2v-2h2l0.002,10H5V10z"></path>
</svg>
NORDICS
</a>
</div>
<div className="order-2 md:order-3 flex items-center" id="nav-content">
<a className="inline-block no-underline hover:text-black" href="#">
<svg className="fill-current hover:text-black" xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 24 24">
<circle fill="none" cx="12" cy="7" r="3"></circle>
<path d="M12 2C9.243 2 7 4.243 7 7s2.243 5 5 5 5-2.243 5-5S14.757 2 12 2zM12 10c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3S13.654 10 12 10zM21 21v-1c0-3.859-3.141-7-7-7h-4c-3.86 0-7 3.141-7 7v1h2v-1c0-2.757 2.243-5 5-5h4c2.757 0 5 2.243 5 5v1H21z"></path>
</svg>
</a>
<a className="pl-3 inline-block no-underline hover:text-black" href="#">
<svg className="fill-current hover:text-black" xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 24 24">
<path d="M21,7H7.462L5.91,3.586C5.748,3.229,5.392,3,5,3H2v2h2.356L9.09,15.414C9.252,15.771,9.608,16,10,16h8 c0.4,0,0.762-0.238,0.919-0.606l3-7c0.133-0.309,0.101-0.663-0.084-0.944C21.649,7.169,21.336,7,21,7z M17.341,14h-6.697L8.371,9 h11.112L17.341,14z"></path>
<circle cx="10.5" cy="18.5" r="1.5"></circle>
<circle cx="17.5" cy="18.5" r="1.5"></circle>
</svg>
<div className="inline-flex absolute top-4 justify-center items-center w-5 h-5 text-xs font-bold text-white bg-red-500 rounded-full">NUMBER OF ITEMS IN CART GOES HERE</div>
</a>
</div>
</div>
</nav>
)
}
And finally, if needed, this is my App.js file.
import './App.css';
import About from './components/About';
import Header from './components/Header';
import Hero from './components/Hero';
import Products from './components/Products';
function App() {
return (
<div className="App">
<Header />
<Hero />
<Products />
<About />
</div>
);
}
export default App;
I provide two methods for your refrence:
1.By using props:
just put your state and set function at their father element, in your situation you can put in App.js, then props to any components you want:
App.js
import './App.css';
import About from './components/About';
import Header from './components/Header';
import Hero from './components/Hero';
import Products from './components/Products';
import axios from 'axios'
function App() {
const [products, setProducts] = useState([]) // put here
const [cartItems, setCartItems] = useState(0) // put here
// get your api here
useEffect(() => {
axios({
method: 'GET',
url: 'https://api.escuelajs.co/api/v1/products'
}).then((response) => {
setProducts(response.data)
})
}, [])
return (
<div className="App">
<Header setCartItems={setCartItems} cartItems={cartItems}/>
<Hero />
<Products cartItems={cartItems} setCartItems={setCartItems} products={products} setProducts={setProducts}/>
<About />
</div>
);
}
export default App;
after then in your Products.js and Header.js can get props data, and can get props set feunction to update state.
But I think put global state is better,
I recommend useContext or zustand to do it!
2.Put in zustand:
create file named store.js, then npm install zustand
store.js
import create from 'zustand';
// this is our useStore hook that we can use in our components to get parts of the store and call actions
const useStore = create((set, get) => ({
cartItems: 0,
setCartItems: (value) =>
set((state) => ({
cartItems: value,
})),
}));
export default useStore;
Product.js and Header.js add below code:
import useStore from './store'; // import the store you writed before
export default function Products(props) {
// get your store state and function
const { cartItems, setCartItems } = useStore();
// update your store state
const updateCart = () => {
setCartItems(cartItems + 1)
}
const minusCart = () => {
setCartItems(cartItems - 1)
}
//...your other code
}
You either need to use global state management like redux or pass a callback function from the header to the products component to update the state. React is unidirectional data flow downward, so you can't pass props back up like you can from parent to child.
I need to update my component after user set like, what methods should I use for this? I m newbie in react-redux and even dont know some basics things.
I also have redux-thunk in my project but im even don`t know how to use it for async responses.
PostCard code.
import * as React from "react";
//Redux
import { useDispatch } from "react-redux";
import { likePostById } from "../Redux/Slice/PostSlice";
import { GetPostByInterests } from "../Redux/Slice/PostSlice";
export const PostCard = (props) => {
const posts = props.posts;
const dispatch = useDispatch();
const LikePost = (id, is_user_liked) => {
const is_liked = !is_user_liked;
dispatch(likePostById(id, is_liked))
}
return (
<div className="flex bg-white shadow-lg rounded-lg mx-4 md:mx-auto max-w-md md:max-w-2xl mt-2">
<div className="flex items-start px-4 py-6">
<img
className="w-12 h-12 rounded-full object-cover mr-4 shadow"
src={posts?.author.avatar.url}
alt="avatar"
></img>
<div className="">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-gray-900 -mt-1">
{posts.author.first_name} {posts.author.last_name}
</h2>
<small className="text-sm text-gray-700">{}</small>
</div>
<p className="mt-3 text-gray-700 text-sm">{posts.text}</p>
<div className="mt-4 flex items-center">
<div className="flex mr-2 text-gray-700 text-sm mr-3">
{posts.is_user_liked ? (
<svg
fill="#f01616"
viewBox="0 0 24 24"
className="w-4 h-4 mr-1"
stroke="currentColor"
onClick={() => {LikePost(posts.id, posts.is_user_liked)}}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
) : (
<svg
fill="none"
viewBox="0 0 24 24"
className="w-4 h-4 mr-1"
stroke="currentColor"
onClick={() => {LikePost(posts.id, posts.is_user_liked)}}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
)}
<span>{posts.likes_count}</span>
</div>
<div className="flex mr-2 text-gray-700 text-sm mr-8">
<svg
fill="none"
viewBox="0 0 24 24"
className="w-4 h-4 mr-1"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"
/>
</svg>
<span>{posts.comments_count}</span>
</div>
</div>
</div>
</div>
</div>
);
};
export default PostCard;
Redux PostSlice
Server Post answer
id(pin): 143
text(pin): "some text"
topic(pin): "ANDROID_DEVELOPMENT"
publish_time(pin): "2022-06-02T23:28:18.415933"
is_user_liked(pin): true
likes_count(pin): 2
comments_count(pin): 0
const initialState = {
posts: [],
};
const post = createSlice({
name: "post",
initialState,
reducers: {
getPosts(state, action) {
state.posts = action.payload.posts;
},
likePost(state, action){
state.posts.is_user_liked = action.payload.is_user_liked;
},
},
});
const { likePost } = post.actions;
export function likePostById(id, is_liked) {
return async (dispatch) => {
try {
await API.post("/posts/" + id + "/like", null, {
params: {
is_liked: is_liked,
},
});
dispatch(
likePost({
is_user_liked: !is_liked,
})
)
} catch (e) {
console.log(e);
}
};
}