I am trying to create a system where I can easily click a given sentence on the page and have it toggle to a different sentence with a different color upon click. I am new to react native and trying to figure out the best way to handle it. So far I have been able to get a toggle working but having trouble figuring out how to change the class as everything is getting handled within a single div.
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<div className="textline" onClick={() => setStatus(!status)}>
{`${status ? 'state 1' : 'state 2'}`}
</div>
);
};
How can I make state 1 and state 2 into separate return statements that return separate texts + classes but toggle back and forth?
you can just create a component for it, create a state to track of toggle state and receive style of text as prop
in React code sandbox : https://codesandbox.io/s/confident-rain-e4zyd?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function ToggleText({ text1, text2, className1, className2 }) {
const [state, toggle] = useState(true);
const className = `initial-style ${state ? className1 : className2}`;
return (
<div className={className} onClick={() => toggle(!state)}>
{state ? text1 : text2}
</div>
);
}
in React-Native codesandbox : https://codesandbox.io/s/eloquent-cerf-k3eb0?file=/src/ToggleText.js:0-465
import React, { useState } from "react";
import { Text, View } from "react-native";
import styles from "./style";
export default function ToggleText({ text1, text2, style1, style2 }) {
const [state, toggle] = useState(true);
return (
<View style={styles.container}>
<Text
style={[styles.initialTextStyle, state ? style1 : style2]}
onPress={() => toggle(!state)}
>
{state ? text1 : text2}
</Text>
</View>
);
}
This should be something you're looking for:
import React from "react"
const Sentence = ({ className, displayValue, setStatus }) => {
return (
<div
className={className}
onClick={() => setStatus((prevState) => !prevState)}
>
{displayValue}
</div>
);
};
const ButtonExample = () => {
const [status, setStatus] = React.useState(false);
return status ? (
<Sentence
className="textLine"
displayValue="state 1"
setStatus={setStatus}
/>
) : (
<Sentence
className="textLineTwo"
displayValue="state 2"
setStatus={setStatus}
/>
);
};
You have a Sentence component that takes in three props. One for a different className, one for a different value to be displayed and each will need access to the function that will be changing the status state. Each setter from a hook also has access to a function call, where you can get the previous (current) state value, so you don't need to pass in the current state value.
Sandbox
Related
In my app I want to make my element always scrolled to bottom after getting new logs.
For some reason my logsRef.current.scrollTop has value of zero all the time. My logs do show on screen and in console. I am not sure why is this not working, I've tried to use different approaches using useLyaoutEffect() but nothing made logsRef.current.scrollTop value change, it stayed zero all the time.
//my Logs.jsx component
import { useEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`}>
<div ref={logsRef}>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also, I do render my Logs.jsx in BattlePhase.jsx component where I do my attack logic on click and I save logs using useState() hook.
//parts where i do save my logs in BattlePhase.jsx
const [logs, setLogs] = useState([]);
const attackHandler = () => {
//logs where pokemon on left attacked pokemon on right
setLogs((prevLogs) => [
...prevLogs,
`${pokemonDataOne.name} attacked ${
pokemonDataTwo.name
} for ${attack.toFixed(2)} dmg`,
`${pokemonDataTwo.name} died`,
])
}
...
<Attack className={isActiveArrow}>
<Button onClick={attackHandler}>Attack!</Button>
</Attack>
Slight long shot but it's possible that the ref is attached to the wrong element. Are you sure the element with the CSS property that makes it scrollable (overflow) isn't on <Container>?
//my Logs.jsx component
import { useLayoutEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useLayoutEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`} ref={logsRef}>
<div>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also to confirm, you do need useLayoutEffect here.
For example I have this code.
And I want to use CSS transitionfor Button when showButton and when !showButton. Now it's just removed and add Button when showButton changes.
{showButton && (
<Button
onClick={() => setShowMessage(true)}
size="lg"
>
Show Message
</Button>
)}
Is it possible make by some events or appending classNames like active?
Append the className with the ternary operator.
But, for example, this code will only adjust the class of the button specified (effectively doing the same thing you described, hiding & showing the button):
import React, { useState } from 'react';
export const Component = () => {
const [showButton, setShowButton] = useState(false);
const handleClick = () => {
setShowButton(true);
}
return (
<button
onClick={handleClick}
className={showButton ? 'showButtonClass' : 'hideButtonClass'}
>
Show Message
</button>
);
};
For content to show once the button is clicked, you'll need something like:
import React, { useState } from 'react';
export const Component = () => {
const [showMessage, setShowMessage] = useState(false);
const handleClick = () => {
setShowMessage(true);
}
return (
<div>
<button
onClick={handleClick}
>
Show Message
</button>
{showMessage && <h1>
The message you'll see when clicking!
</h1>}
</div>
);
};
Why the input only taking inputs from second input only?
import React, { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
let toDoIs = document.getElementById("toDoInput");
const [ToDo, setToDoIs] = useState("d");
const [ToDoArray, setToDoArray] = useState([]);
return (
<div>
<h1>ToDo</h1>
<input
id="toDoInput"
onChange={() => {
setToDoIs(toDoIs.value);
}}
type="text"
/>
<button
onClick={() => {
setToDoArray([...ToDoArray, { text: ToDo }]);
toDoIs.value = "";
}}
>
Add
</button>
<Item push={ToDoArray} />
</div>
);
}
export default ToDo;
Why the second input only works, which means whenever I use submit the value from second input only stored and displayed. I don't know why this happens.
There's a few problems here...
Don't use DOM methods in React. Use state to drive the way your component renders
Your text input should be a controlled component
When updating state based on the current value, make sure you use functional updates
import { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
// naming conventions for state typically use camel-case, not Pascal
const [toDo, setToDo] = useState("d");
const [toDoArray, setToDoArray] = useState([]);
const handleClick = () => {
// use functional update
setToDoArray((prev) => [...prev, { text: toDo }]);
// clear the `toDo` state via its setter
setToDo("");
};
return (
<div>
<h1>ToDo</h1>
{/* this is a controlled component */}
<input value={toDo} onChange={(e) => setToDo(e.target.value)} />
<button type="button" onClick={handleClick}>
Add
</button>
<Item push={toDoArray} />
</div>
);
}
export default ToDo;
Having issues with React style not being applied. I have no idea why it is not working as it was before.
See code below:
Accordion.js
import React, {useState} from 'react'
import { risk_assessment } from '../data/questions';
import AccordionItem from '../components/AccordionItem';
import Question from '../components/Question';
const Accordion = props => {
const [active, setActive] = useState("0")
return (
<ul className="accordion">
{risk_assessment.map((question, index) => (
<AccordionItem
key={index}
itemTitle={question.question}
itemContent={<Question options={question.options} name={question.name} />}
toggle={() => setActive(index)}
active={active == index} />
))}
</ul>
)
}
export default Accordion
AccordionItem.js
import React, {useRef, useEffect} from 'react'
const AccordionItem = ({ itemTitle, itemContent, toggle, active }) => {
const accordionContent = useRef()
let contentHeight = {}
useEffect(() => {
contentHeight = active ? {height: accordionContent.current.scrollHeight} : {height: "0px"}
})
return (
<li className="accordion_item">
<button className="button" onClick={toggle}>
{itemTitle}
<span className="control">{active ? "—" : "+"}</span>
</button>
<div className="answer_wrapper" ref={accordionContent} style={contentHeight} >
<div className="answer">{itemContent}</div>
</div>
</li>
)
}
export default AccordionItem
Question.js simply renders the data inside the Accordion Item.
Here is the output from Chrome developer tools.
I have tried messing with the useEffect hook to no success. Changed it to run on every render, only on the first render, added the ref as a dependency etc.
I need to use the useRef hook to get the height of the content area dynamically.
Any help would be appreciated.
In your case when the component re-renders the value of your variable will be lost. Try putting contentHeight in a state.
const [contentHeight, setContentHeight] = useState({})
useEffect(() => {
setContentHeight(active ? {height: accordionContent.current.scrollHeight} : {height: "0px"});
}, [active])
You can find more information in this post.
As I'm new to React I have some difficulties to make a functional component which essentially should change dynamically the render of an icon based on a type getting from data.
The table itself already exist and it is mapped and getting inside all the data I need.
In one row/cell I have to include an Icon component which pass the type and also I need to have onClick function here
<TableCell>
<Icon rowType={row.type} onClick={<some function>}/>
</TableCell>
The row.type is a type of a specific data I'm getting and I need to focus on 2 types
SLOT
SITE
Based on this 2 types I have to render a SLOT_ICON and a SITE_ICON in the table when in a row I have one of those two.
I thought to make an if/elseif but I actually don't know how to render and how also to include then the onClick event based on which icon I'm clicking.
A pseudo code example:
const Icon = rowType => {
if (rowType === 'SLOT') {
SLOT;
} else if (rowType === 'SITE') {
SITE;
}
<-- onClick logic somewhere here? -->
return Icon
};
You can use conditional rendering in react.
const Icon = ({ type, onClick }) => {
if (type === "SLOT") return <img src={"slot"} onClick={onClick} />;
else if (type === "SITE") return <img src={"site"} onClick={onClick} />;
else return <img src={"icon"} onClick={onClick} />;
};
You can use Icon as follows
<Icon type="SLOT" onClick={() => {}} />
The general idea is you will probably end up having something like this (please notice I made quite a lot of assumptions about your code therefore you might have to tune the example provided):
parent component:
import React, { useCallback } from 'react';
import TableCell from '/path/to/component/';
import Icon from '/path/to/component/';
const ParentComponent = ({row, ...props}) => {
//...
const onIconClick = useCallback(() => {
//do whatever you have to do
}, [ /*dependencies*/ ]);
return (
<TableCell>
<Icon rowType={row.type} onClick={onIconClick} />
</TableCell>
);
}
Icon component:
import React, { useMemo, useCallback } from 'react';
const Icon = ({rowType, onClick}) => {
const iconData = useMemo(() => {
const basePath = '/path/to/image/folder/';
switch (rowType) {
case 'SLOT':
return {
src: basePath + 'slotFile.ext',
alt: 'slot alternative text'
};
case 'SITE':
return {
src: basePath + 'siteFile.ext',
alt: 'site alternative text'
};
default:
return null;
}
}, [rowType]);
const handleClick = useCallback(() => {
// 1. do whatever additional operation you have to do (if any)
// ...
// 2. execute the function passed by the parent
onClick();
}, []);
return iconData ? (
<img
src={iconData.src}
alt={iconData.alt}
onClick={() => handleClick()}
/>
) : null;
}
export default Icon;